[0001] Fixing NULL for the entire system for fun and profit
Prelude
NULL pointers are the scourge of C programming. The very idea to dedicate a special value inside the pointer address space seems disastrous in the hindsight, and even Tony Hoare agrees with this sentiment, calling null references his “billion dollar miskate”. Fortunately, as far as operating systems are concerned, zero address is no different than any other address and we can actually use memory at that location.
Fixing NULL for thee
First, a bit of C magic. Consider the following program.
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
#define LEN 4096
int main(void) {
char *addr = mmap(NULL, LEN, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED|MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
perror("mmap");
return -1;
}
if (addr != NULL) {
fprintf(stderr, "Mapped addr (%p) is not NULL\n", addr);
goto end;
}
strcpy(NULL, "The NULL has been desecrated");
printf("%s\n", NULL);
end:
munmap(addr, LEN);
return 0;
}
Surprisingly to many, it works just fine. Albeit, on modern Linux systems it would require either root privileges or setting vm/mmap_min_addr=0 in your sysctl.conf. The reason for the latter is to prevent exploitation of NULL dereference bugs in the kernel. In any way, running this snippet will output the desired piece of text.
Make it so!
Since our plan is to have this piece of code run in virtually all processes in the system, the next step is turning this code into a shared library. Our slightly modified C code:
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <sys/mman.h>
static const size_t len = 4096;
static bool success = false;
void __attribute__((constructor)) desecrate_null(void) {
const char null_words[] = "The NULL is no longer sacred";
char *addr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED|MAP_ANONYMOUS, -1, 0);
if (addr != NULL && addr != MAP_FAILED) {
munmap(addr, len);
return;
}
success = true;
strcpy(NULL, null_words);
memset(NULL + sizeof(null_words), 'E', len - sizeof(null_words));
}
void __attribute__((destructor)) reconsecrate_null(void) {
if (success) {
munmap(NULL, len);
success = false;
}
}
The sole difference is that our main() is now turned into constructor function run when this shared library is loaded into a process. We can verify that this works with a simple program like this:
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("%s\n", NULL);
return 0;
}
In a normal environment, this would obviously receive SIGSEGV and crash. In our case, however…
$ LD_PRELOAD=./libnull.so ./test
The NULL is no longer sacred
Spread it all over the system
You can put a path to libnull.so into your /etc/ld.so.preload. Or you can use your distro tooling for this. But as we can see, fixing NULL pointer dereference for the entire system is not that hard.
Conclusion
Obviously, this entire article is a joke. While you certainly can do something like this, you shouldn’t. Turning NULL into a valid memory address with mapped contents turns easy to debug NULL errors into much harder to debug memory corruption errors, potentially compromises the security of your system, and tarnishes your karma.