Question

In a mac OS X network kernel extension, I have noticed that if I have a statically allocated buffer rather than a dynamic one, this leads to kernel panic when calling API functions such as printf() or send(), ctl_enqueuedata(), as well as many others. It is as if the statically allocated buffers can't be read or written from outside of my code.

For instance:

// This is OK
static char* somevar = NULL;
somevar = OSMalloc(50, myOSMallocTag);
bzero(somevar, 50);

// This will create a kernel panic when used outside my code
static char somevar[50];
bzero(somevar, 50);

Why is that?

Edit: I was about to post the code, but it is lengthy and the only difference between the version that works and the one that causes panic is as above. What I have in mind is the difference in memory location between a static variable and one allocated with OSMalloc. Can code within ctl_enqueuedata() access both ?

Here is what happened:

panic(cpu 0 caller 0xffffff802eeb7e95): Kernel trap at 0xffffff802ee28896, type 14=page fault, registers:
CR0: 0x0000000080010033, CR2: 0x0000000000000031, CR3: 0x000000024fbac0a7, CR4: 0x00000000001606e0
RAX: 0x000000007fffff01, RBX: 0x0000000000000000, RCX: 0x0000000000000010, RDX: 0xffffff7fb0d4d573
RSP: 0xffffff811f6fbae0, RBP: 0xffffff811f6fbbe0, RSI: 0x000000007fffffff, RDI: 0x0000000000000073
R8:  0x0000000000000000, R9:  0x0000000000000031, R10: 0x0000000000000000, R11: 0x0000000000000000
R12: 0x0000000000000000, R13: 0x0000000000000019, R14: 0xffffff811f6fbd01, R15: 0x0000000000000031
RFL: 0x0000000000010246, RIP: 0xffffff802ee28896, CS:  0x0000000000000008, SS:  0x0000000000000010
Fault CR2: 0x0000000000000031, Error code: 0x0000000000000000, Fault CPU: 0x0

Backtrace (CPU 0), Frame : Return Address
0xffffff811f6fb780 : 0xffffff802ee1d626 
0xffffff811f6fb7f0 : 0xffffff802eeb7e95 
0xffffff811f6fb9c0 : 0xffffff802eecd4dd 
0xffffff811f6fb9e0 : 0xffffff802ee28896 
0xffffff811f6fbbe0 : 0xffffff802f174a62 
0xffffff811f6fbc00 : 0xffffff7fb0d4cead 
0xffffff811f6fbd40 : 0xffffff7fb0d46101 
0xffffff811f6fbdf0 : 0xffffff802f150525 
0xffffff811f6fbe40 : 0xffffff802f1990b2 
0xffffff811f6fbef0 : 0xffffff802f1a04f2 
0xffffff811f6fbf50 : 0xffffff802f1e063a 
0xffffff811f6fbfb0 : 0xffffff802eecdd23 
Was it helpful?

Solution

It's not quite clear to me what you mean by "outside of [your] code", so if this answer doesn't help, please elaborate. The literal code you have supplied will work, I'm guessing you've reduced it from something that actually fails?

I can think of 2 likely problems in this context:

Allocation Lifetime

Memory for static variables is allocated when the kext is loaded and freed when it is unloaded. Are you sure whatever is using your memory is definitely not using it past the unloading of your kext? If it's an IOKit kext, the kernel will unload it automatically fairly soon after loading unless one of the personalities matches. This might not be what you and your code are expecting.

Threading issues

Essentially all kernel code is multithreaded, and you can't escape it. Static/global variables are particularly vulnerable to race conditions. If one thread is writing to the buffer while another is attempting to read it via printf(), you're asking for trouble. You need to ensure you're serialising access to buffers appropriately, or use a different strategy for managing buffer memory. If the buffers are supposed to be temporary, allocating them on the stack (non-static within a function) might be a better idea, depending on size. As @Merlin069 mentions, the kernel stack is very small (<16kiB), so avoid anything bigger than maybe a few hundred bytes. The 50 byte buffer in your example should be fine though unless it's a recursive function.

Update:

Regarding your sub-question of "What I have in mind is the difference in memory location between a static variable and one allocated with OSMalloc. Can code within ctl_enqueuedata() access both ?"

Yes.

Accessing memory allocated within the kernel is much like doing so in a regular program. The kernel_task has its own memory map, which is active whenever running in kernel mode. The kernel is monolithic, so a pointer that is valid in one kext is also valid in another. It's only when you want to access user space memory from the kernel, or kernel space from user space, that you have to deal with the mappings explicitly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top