eBPF - Cannot read argv and envp from tracepoint sys_enter_execve - linux

I am learning BPF for my own fun, and I am having a hard time figuring out how to read argv and envp from the context passed to my eBPF program for sys_enter_execve
I will show my BPF program here and then explain in more details later what I am trying to accomplish.
Here's my code:
#include <linux/bpf.h>
#include <bpf_helpers.h>
struct
{
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, char[300]);
__uint(max_entries, 1);
} mymap SEC(".maps");
// Based on /sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format
struct execve_args {
short common_type;
char common_flags;
char common_preempt_count;
int common_pid;
int __syscall_nr;
char *filename;
const char *const *argv;
const char *const *envp;
};
SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(struct execve_args *ctx) {
__u32 index = 0;
__u64 *value = bpf_map_lookup_elem(&mymap, &index);
// An array of length 300 is purely arbitrary here
char fn[300];
// null check for the value fetched from the map
if (value){
// trying here to get the first env var passed to the process
// started with execve
const char *const first_env_value = ctx->envp[0];
// null check
if (!first_env_value){
return 0;
}
// trying to safely read the value pointed by first_env_value
bpf_probe_read_user_str(fn, sizeof(fn), first_env_value);
bpf_map_update_elem(&mymap, &index, fn, BPF_ANY);
return 0;
}
return 0;
}
char _license[] SEC("license") = "GPL";
What I want, here, is to ultimately read the first environments variable referenced by ctx->envp and save it in the map.
Building the program succeeds, but it fails when I try to load it into the kernel:
8: (15) if r0 == 0x0 goto pc+15
R0_w=map_value(id=0,off=0,ks=4,vs=300,imm=0) R6_w=ctx(id=0,off=0,imm=0) R10=fp0 fp-8=mmmm????
; const char *const first_env_value = ctx->envp[0];
9: (79) r1 = *(u64 *)(r6 +32)
; const char *const first_env_value = ctx->envp[0];
10: (79) r3 = *(u64 *)(r1 +0)
R1 invalid mem access 'inv'
processed 10 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
I use bpf2go from Cilium project to load the BPF program into the kernel. And I use a Go program to read there BPF map.
Can someone give me some hints as to what am I doing wrong?
Maybe it is the double pointer that confuses me (const char *const *envp), maybe I misunderstand the sys_enter_execve system call and the tracepoint inputs, etc.
Any hint would be appreciated!
I'm not a kernel developer. I mostly code in Go and Python, but I really want to learn how to write BPF programs in pure C, just for the fun of it.
Thanks in advance

TL;DR. You are trying to read arbitrary kernel memory. You need to use bpf_probe_read for that.
Let's have a look at the error logs:
The invalid memory access is on a load from r1. The value in r1 was loaded from memory using the address in r6 as the base. According to the second line, the verifier associates type ctx to r6.
So r6 points to your variable ctx. That variable is special (hence why the verifier has a special ctx type for it). Your BPF program is allowed to access memory pointed by that variable as long as its bounded (the exact bound depends on the program type).
8: (15) if r0 == 0x0 goto pc+15
R0_w=map_value(id=0,off=0,ks=4,vs=300,imm=0) R6_w=ctx(id=0,off=0,imm=0) R10=fp0 fp-8=mmmm????
; const char *const first_env_value = ctx->envp[0];
9: (79) r1 = *(u64 *)(r6 +32)
; const char *const first_env_value = ctx->envp[0];
10: (79) r3 = *(u64 *)(r1 +0)
R1 invalid mem access 'inv'
However, the value you retrieve from ctx->envp (the value stored in r1) is not part of ctx and may point to arbitrary kernel memory. The BPF verifier thus can't ensure ahead-of-time the safety of that access and rejects your program.
You need to use a BPF helper, bpf_probe_read, to access that memory. That helper will perform runtime checks to ensure the memory access is safe. If it's unsafe, it will return a negative error.

Thanks a lot #pchaigno, you were absolutely right. To show other people how I solved my problem, here is the solution I have, based on pchaigno answer.
#include <linux/bpf.h>
#include <bpf_helpers.h>
struct
{
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, char[300]);
__uint(max_entries, 1);
} mymap SEC(".maps");
// Based on /sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format
struct execve_args {
short common_type;
char common_flags;
char common_preempt_count;
int common_pid;
int __syscall_nr;
char *filename;
const char *const *argv;
const char *const *envp;
};
SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(struct execve_args *ctx) {
__u32 index = 0;
// Here we reserve a pointer to the first env var
char *first_env_var;
// Here we attempt to read the value pointed by ctx->envp[0] and store it in *first_env_var
long res = bpf_probe_read(&first_env_var, sizeof(first_env_var), &ctx->envp[2]);
// For demo purposes, simply return from the program
// if there is an error with bpf_probe_read
if (res != 0){
return 0;
}
// Read the value pointed by the (now) safe pointer *first_env_var
// and store the value in 'value'
char value[300];
bpf_probe_read_str(value, sizeof(value), first_env_var);
// Copy the value to the map
bpf_map_update_elem(&mymap, &index, &value, BPF_ANY);
return 0;
}
char _license[] SEC("license") = "GPL";

Related

C Function to return a String resulting in corrupted top size

I am trying to write a program that calls upon an [external library (?)] (I'm not sure that I'm using the right terminology here) that I am also writing to clean up a provided string. For example, if my main.c program were to be provided with a string such as:
asdfFAweWFwseFL Wefawf JAWEFfja FAWSEF
it would call upon a function in externalLibrary.c (lets call it externalLibrary_Clean for now) that would take in the string, and return all characters in upper case without spaces:
ASDFFAWEWFWSEFLWEFAWFJAWEFFJAFAWSEF
The crazy part is that I have this working... so long as my string doesn't exceed 26 characters in length. As soon as I add a 27th character, I end up with an error that says
malloc(): corrupted top size.
Here is externalLibrary.c:
#include "externalLibrary.h"
#include <ctype.h>
#include <malloc.h>
#include <assert.h>
#include <string.h>
char * restrict externalLibrary_Clean(const char* restrict input) {
// first we define the return value as a pointer and initialize
// an integer to count the length of the string
char * returnVal = malloc(sizeof(input));
char * initialReturnVal = returnVal; //point to the start location
// until we hit the end of the string, we use this while loop to
// iterate through it
while (*input != '\0') {
if (isalpha(*input)) { // if we encounter an alphabet character (a-z/A-Z)
// then we convert it to an uppercase value and point our return value at it
*returnVal = toupper(*input);
returnVal++; //we use this to move our return value to the next location in memory
}
input++; // we move to the next memory location on the provided character pointer
}
*returnVal = '\0'; //once we have exhausted the input character pointer, we terminate our return value
return initialReturnVal;
}
int * restrict externalLibrary_getFrequencies(char * ar, int length){
static int freq[26];
for (int i = 0; i < length; i++){
freq[(ar[i]-65)]++;
}
return freq;
}
the header file for it (externalLibrary.h):
#ifndef LEARNINGC_EXTERNALLIBRARY_H
#define LEARNINGC_EXTERNALLIBRARY_H
#ifdef __cplusplus
extern "C" {
#endif
char * restrict externalLibrary_Clean(const char* restrict input);
int * restrict externalLibrary_getFrequencies(char * ar, int length);
#ifdef __cplusplus
}
#endif
#endif //LEARNINGC_EXTERNALLIBRARY_H
my main.c file from where all the action is happening:
#include <stdio.h>
#include "externalLibrary.h"
int main() {
char * unfilteredString = "ASDFOIWEGOASDGLKASJGISUAAAA";//if this exceeds 26 characters, the program breaks
char * cleanString = externalLibrary_Clean(unfilteredString);
//int * charDist = externalLibrary_getFrequencies(cleanString, 25); //this works just fine... for now
printf("\nOutput: %s\n", unfilteredString);
printf("\nCleaned Output: %s\n", cleanString);
/*for(int i = 0; i < 26; i++){
if(charDist[i] == 0){
}
else {
printf("%c: %d \n", (i + 65), charDist[i]);
}
}*/
return 0;
}
I'm extremely well versed in Java programming and I'm trying to translate my knowledge over to C as I wish to learn how my computer works in more detail (and have finer control over things such as memory).
If I were solving this problem in Java, it would be as simple as creating two class files: one called main.java and one called externalLibrary.java, where I would have static String Clean(string input) and then call upon it in main.java with String cleanString = externalLibrary.Clean(unfilteredString).
Clearly this isn't how C works, but I want to learn how (and why my code is crashing with corrupted top size)
The bug is this line:
char * returnVal = malloc(sizeof(input));
The reason it is a bug is that it requests an allocation large enough space to store a pointer, meaning 8 bytes in a 64-bit program. What you want to do is to allocate enough space to store the modified string, which you can do with the following line:
char *returnVal = malloc(strlen(input) + 1);
So the other part of your question is why the program doesn't crash when your string is less than 26 characters. The reason is that malloc is allowed to give the caller slightly more than the caller requested.
In your case, the message "malloc(): corrupted top size" suggests that you are using libc malloc, which is the default on Linux. That variant of malloc, in a 64-bit process, would always give you at least 0x18 (24) bytes (minimum chunk size 0x20 - 8 bytes for the size/status). In the specific case that the allocation immediately precedes the "top" allocation, writing past the end of the allocation will clobber the "top" size.
If your string is larger than 23 (0x17) you will start to clobber the size/status of the subsequent allocation because you also need 1 byte to store the trailing NULL. However, any string 23 characters or shorter will not cause a problem.
As to why you didn't get an error with a string with 26 characters, to answer that one would have to see that exact program with the string of 26 characters that does not crash to give a more precise answer. For example, if the program provided a 26-character input that contained 3 blanks, this would would require only 26 + 1 - 3 = 24 bytes in the allocation, which would fit.
If you are not interested in that level of detail, fixing the malloc call to request the proper amount will fix your crash.

Is the sscanf function in the Linux kernel susceptible to buffer overflow attacks?

From what I understand, a typical buffer overflow attack occurs when an attack overflows a buffer of memory on the stack, thus allowing the attacker to inject malicious code and rewrite the return address on the stack to point to that code.
This is a common concern when using functions (such as sscanf) that blindly copy data from one area to another, checking one for a termination byte:
char str[8]; /* holds up to 8 bytes of data */
char *buf = "lots and lots of foobars"; /* way more than 8 bytes of data */
sscanf(buf, "%s", str); /* buffer overflow occurs here! */
I noticed some sysfs_ops store functions in the Linux kernel are implemented with the Linux kernel's version of the sscanf function:
static char str[8]; /* global string */
static ssize_t my_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
sscanf(buf, "%s", str); /* buf holds more than 8 bytes! */
return size;
}
Suppose this store callback function is set to a writable sysfs attribute. Would a malicious user be able to intentionally overflow the buffer via a write call?
Normally, I would expect guards against buffer overflow attacks -- such as limiting the number of bytes read -- but I see none in a good number of functions (for example in drivers/scsi/scsi_sysfs.c).
Does the implementation of the Linux kernel version of sscanf protect against buffer overflow attacks; or is there another reason -- perhaps buffer overflow attacks are impossible given how the Linux kernel works under the hood?
The Linux sscanf() is vulnerable to buffer overflows; inspection of the source shows this. You can use width specifiers to limit the amount a %s is allowed to write. At some point your str must have had copy_from_user() run on it as well. It is possible the user space to pass some garbage pointer to the kernel.
In the version of Linux you cited, the scsi_sysfs.c does have a buffer overflow. The latest version does not. The committed fix should fix the issue you see.
Short answer:
sscanf, when well called, will not cause buffer overflow, especially in sysfs xxx_store() function. (There are a lot sscanf in sysfs XXX_store() examples), because Linux kernel add a '\0' (zero-terminated) byte after the string (buf[len] = 0;) for your XXX_store() function.
Long answer:
Normally, sysfs are defined to have a strict formatted data. Since you expect 8 bytes at most, it's reasonable to limit the size you get like this:
static char str[8]; /* global string */
static ssize_t my_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (size > 8) {
printk("Error: Input size > 8: too large\n");
return -EINVAL;
}
sscanf(buf, "%s", str); /* buf holds more than 8 bytes! */
return size;
}
(Note: use 9 rather than 8, if you expect a 8-bytes string plus '\n')
(Note that you do reject some inputs such as those with many leading white spaces. However, who would send a string with many leading white spaces? Those who want to break your code, right? If they don't follow your spec, just reject them.)
Note that Linux kernel purposely inserts a '\0' at offset len (i.e. buf[len] = 0;) when the user write len bytes to sysfs purposely for safe sscanf, as said in a comment in kernel 2.6: fs/sysfs/file.c:
static int
fill_write_buffer(struct sysfs_buffer * buffer, const char __user * buf, size_t count)
{
int error;
if (!buffer->page)
buffer->page = (char *)get_zeroed_page(GFP_KERNEL);
if (!buffer->page)
return -ENOMEM;
if (count >= PAGE_SIZE)
count = PAGE_SIZE - 1;
error = copy_from_user(buffer->page,buf,count);
buffer->needs_read_fill = 1;
/* if buf is assumed to contain a string, terminate it by \0,
so e.g. sscanf() can scan the string easily */
buffer->page[count] = 0;
return error ? -EFAULT : count;
}
...
static ssize_t
sysfs_write_file(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
struct sysfs_buffer * buffer = file->private_data;
ssize_t len;
mutex_lock(&buffer->mutex);
len = fill_write_buffer(buffer, buf, count);
if (len > 0)
len = flush_write_buffer(file->f_path.dentry, buffer, len);
if (len > 0)
*ppos += len;
mutex_unlock(&buffer->mutex);
return len;
}
Higher kernel version keeps the same logic (though already completely rewritten).

Arena Allocator Allocation Method & Violation Writing Issue

Basically I'm trying to create an Arena Allocator without using structs, classes, or the new operator to manually manage memory. I have a defined size, a character pool, an allocation method and a freeMemory display method.
Note that pool[0] is my index which will keep track of where the memory has last been filled.
const int size = 50000;
char pool[size];
void start() {
pool[0] = 1;
}
int freeMemory(void) {
int freemem = 0;
for(int i = 0; i < size; i++) {
if(pool[i] == NULL) {
freemem++;
}
}
return freemem;
}
void* allocate(int aSize)
{
if(freeMemory() == 0)
{
out();
}
else
{
char* p = NULL;
int pos = pool[0];
pool[pos] = (char) a;
p = &pool[pos];
pool[0] += a;
return((void*) &pool[pos]);
}
}
In the main.cpp:
start();
long* test1 = (long *) allocate(sizeof(long));
cout << freeMemory() << endl; //Returns 49999
*test1 = 0x8BADF00D; //Breaks here
cout << freeMemory() << endl;
It breaks when I try to use 0x8BADF00D and I believe I'm having issues initializing some of these variables too.
Unhandled exception at 0x000515f7 in MemoryManagerC.exe: 0xC0000005: Access violation writing location 0x00000004 on 0x8BADF00D
The code below has numerous bugs.
char* pointer;
for(int i = 0; i < size; i++)
{
*pointer = pool[i];
if(pointer != NULL)
{
pointer = (char*) a;
return((void*) i); //return the pointer
}
}
This line copies a character to an unknown memory location. Since pointer has never been initialized, we can only guess where it's pointing
*pointer = pool[i];
You probably meant to copy a pointer.
pointer = &pool[i];
Although if you did mean to copy a pointer from the pool array, this will always be true. None of the elements in that array reside at address NULL.
if(pointer != NULL)
Now this code changes pointer to point to...more invalid addresses. When a is sizeof(long), that size is reinterpreted to be a memory address. Memory address 0x00000004 most likely.
pointer = (char*) a;
And then this will return the address 0x00000000, in your case. Because i is 0.
return((void*) i); //return the pointer
There are some problems with allocate:
char* pointer = NULL;
int pos = pool[0];
pool[0] is a char. It's not big enough to store indexes to all members of the array.
pool[pos] = (char) a;
I'm not sure what you're storing here, or why. You seem to be storing the size of the allocation in the space that you're allocating.
pointer = &pool[pos + a];
I think you're constructing a pointer to the memory after the allocated portion. Is that right?
pool[0] += a;
And here you're incrementing the offset that shows how much of the pool is allocated, except that a single char isn't going to be big enough for more than a tiny quantity of allocations.
return((void*) &pointer);
And now you're returning the address of the pointer variable. That's going to be an address on the stack, and unsafe to use. Even if you just the contents of pointer instead of its address, I think it would point after the region you just allocated in your pool.
There are also problems with freeMemory. It compares the contents of the pool (char elements) with NULL. This suggests you think it contains pointers, but they are just chars. It's not clear why unallocated parts of the pool would be 0. Do you even allow deallocation within the pool?
Perhaps you could explain how you intend the allocator to work? There's obviously a gap between what you think it should do and what it actually does, but it's not clear what you think it should do, so it's hard to give advice. How do you apportion space in the array? Do you allow deallocation? What information is supposed to be encoded where?
I just realised that allocate uses the undefined variable a. Is that supposed to be the same thing as the parameter aSize? That's what I assume here.
a possible problem with your code might be here.
char* pointer;
for(int i = 0; i < size; i++)
{
*pointer = pool[i];
The thing here is this might work on some compilers (it shouldn't in my opinion).
pointer here is not pointing to anything allocated. So when you do
*pointer = pool[i];
Where should pool[i] be copied to?
For example let's say we delclared pointer like this.
char* pointer = NULL;
now it is clear that
*pointer = pool[i];
is wrong.
g++ (I have noticed) initializes pointers to NULL. So your code will segfault. VC++ might work because it didn't NULL initialize pointer. But you are writing to a memory location that's not yours.

Buffer Overflow When Declaring Char Array

I've never done any C++/COM work before, so I'm trying to hijack an existing solution and just alter it to my needs. The project was written and successfully compiled with VC 6 and I'm attempting to work with it now in 2010. I had to change a few references to get it to compile, but for some reason, the dll I generate is causing an exception on my system (the original works fine). Doing some research on the error, it looks like I'm getting a buffer overflow when I try to declare a char array.
bool CFile::simpleWrite(char* cData)
{
try{
// temp result variable
BOOL bResult = 0;
// file handle
HANDLE hFile = INVALID_HANDLE_VALUE;
// get the CMain singleton
CMain* m_pMain = CMain::GetInstance();
// this point gets synchronization to ensure we get unique file name...
char cDirFilename[MAX_PATH + 1];
GetLogFileName(cDirFilename, MAX_PATH);
// sanity check
if(strcmp(cDirFilename, "c:\\") == 0) assert(0);
// try and create a file
hFile = CreateFile( cDirFilename, GENERIC_WRITE, FILE_SHARE_READ,NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
// if have a good file handle
if(hFile != INVALID_HANDLE_VALUE){
size_t lenFileData = strlen(cData) + 72;
char* cFileData = new char[lenFileData];
_snprintf(cFileData, lenFileData, "<?xml version=\"1.0\"?>\r\n<RootElement>\r\n%s</RootElement>\r\n\0", cData);
...
Here is the declaration/assignment for cData (cXML in the calling method).
char cXML[EVENT_LOG_MAX_MESSAGE];
// get the CMain singleton
CMain* pMain = CMain::GetInstance();
long lThreadID = GetCurrentThreadId();
// put the parameters into XML format
pMain->BuildXML(cXML, EVENT_LOG_MAX_MESSAGE,errLogLevel,userActivityID,methodName,lineNumber,className,AppID,errorDescription,errorID,lThreadID);
// write the data to file
if(!simpleWrite(cXML))
...
BuildXML is doing a _snprintf into cXML and returning it.
Here is the stacktrace going from my call into some of the VC files.
Test.dll!_heap_alloc_base(unsigned int size) Line 55 C
Test.dll!_heap_alloc_dbg_impl(unsigned int nSize, int nBlockUse, const char * szFileName, int nLine, int * errno_tmp) Line 431 + 0x9 bytes C++
Test.dll!_nh_malloc_dbg_impl(unsigned int nSize, int nhFlag, int nBlockUse, const char * szFileName, int nLine, int * errno_tmp) Line 239 + 0x19 bytes C++
Test.dll!_nh_malloc_dbg(unsigned int nSize, int nhFlag, int nBlockUse, const char * szFileName, int nLine) Line 302 + 0x1d bytes C++
Test.dll!malloc(unsigned int nSize) Line 56 + 0x15 bytes C++
Test.dll!operator new(unsigned int size) Line 59 + 0x9 bytes C++
Test.dll!operator new[](unsigned int count) Line 6 + 0x9 bytes C++
Test.dll!CFile::simpleWrite(char * cData) Line 87 + 0xc bytes C++
I'm sure there is some stupid basic mistake, but I can't seem to get it figured out.
Your error is most likely somewhere else where you end up corrupting the heap. I notice you use strlen here without adding one for the terminating zero. Have a look in your code to see if you use strlen for allocating memory and copying somewhere, because I think you're corrupting the heap by allocating one byte too little and then strcpy'ing to it.
As I suspected, this was all my own stupidity. This COM application had a dependency that I wasn't aware of. Never saw anything trying to look for it in the process monitor and the project had a local copy of the file for compile purposes apparently. Thanks for the input though.

Pointer initialization doubt

We could initialize a character pointer like this in C.
char *c="test";
Where c points to the first character(t).
But when I gave code like below. It gives segmentation fault.
#include<stdio.h>
#include<stdlib.h>
main()
{
int *i=0;
printf("%d",*i);
}
Also when I give
#include<stdio.h>
#include<stdlib.h>
main()
{
int *i;
i=(int *)malloc(2);
*i=0;
printf("%d",*i);
}
It worked(gave output 0).
When I gave malloc(0), it worked(gave output 0).
Please tell what is happening
Your first example is seg faulting because you are trying to de-reference a null pointer which you have created with the line:
int *i=0;
You can't de-reference a pointer that doesn't point to anything and expect good things to happen. =)
The second code segment works because you have actually assigned memory to your pointer using malloc which you may de-reference. I would think it's possible for you to get values other than zero depending on the memory adjacent to the address you're allocated with malloc. I say this because typically an int is 4 bytes and you've only assigned 2. When de-referencing the int pointer, it should return the value as an int based on the 4 bytes pointed to. In your case, the first 2 bytes being what you received from the malloc and the adjacent 2 bytes being whatever is there which could be anything and whatever it is will be treated as if it was an int. You could get strange behavior like this and you should malloc the size of memory needed for the type you are trying to use/point at.
(i.e. int *i = (int *) malloc(sizeof(int)); )
Once you have the pointer pointing at memory that is of the correct size, you can then set the values as such:
#include <stdlib.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
int *i = (int *)malloc(sizeof(int));
*i = 25;
printf("i = %d\n",*i);
*i = 12;
printf("i = %d\n",*i);
return 0;
}
Edit based on comment:
A pointer points to memory, not to values. When initializing char *ptr="test"; You're not assigning the value of "test", you're assigning the memory address of where the compiler is placing "test" which is placed in your processes data segment and is read only. It you tried to modify the string "test", you program would likely seg-fault. What you need to realize about a char * is that it points at a single (i.e. the first) character in the string. When you de-reference the char *, you will see 1 character and one character only. C uses null terminated strings, and notice that you do not de-reference ptr when calling printf, you pass it the pointer itself and that points at just the first character. How this is displayed depends on the format passed to printf. When printf is passed the '%c' format, it will print the single character ptr points at, if you pass the format '%p' it will print the address that ptr points. To get the entire string, you pass '%s' as the format. What this makes printf do is to start at the pointer you passed in and read each successive byte until a null is reached. Below is some code that demonstrates these.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main (int argc, char *argv[])
{
// Initialize to data segement/read only string
char *ptr = "test";
printf("ptr points at = %p\n", ptr); // Prints the address ptr points to
printf("ptr dereferenced = %c\n", *ptr); // Prints the value at address ptr
printf("ptr value = %s\n", ptr); // Prints the string of chars pointed to by ptr
// Uncomment this to see bad behavior!
// ptr[1] = 'E'; // SEG FAULT -> Attempting to modify read-only memory
printf("--------------------\n");
// Use memory you have allocated explicitly and can modify
ptr = malloc(10);
strncpy(ptr, "foo", 10);
printf("ptr now points at = %p\n", ptr); // Prints the address ptr points to
printf("ptr dereferenced = %c\n", *ptr); // Prints the value at address ptr
printf("ptr value = %s\n", ptr); // Prints the string of chars pointed to by ptr
ptr[1] = 'F'; // Change the second char in string to F
printf("ptr value (mod) = %s\n", ptr);
return 0;
}

Resources