Reputation: 406
Check this code:
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
int main(void) {
char *addr = mmap(NULL, 6, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED|MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
perror("mmap");
return 1;
}
strcpy(addr, "abcxyz");
printf("Mapped into addr=0x%X\n", addr);
printf("%s\n", addr);
printf("%s\n", NULL);
}
fd
is file descriptor to a file which contain string abcxyz
I expected the program should recieve SIGSEGV upon accessing to NULL pointer
But ironically, I got
Mapped into addr=0x0
abcxyz
abcxyz
What is happened? Why is it possible to map a accessible data to address NULL? With this behavior, how can I use it to exploit the system?
Note that: The program has to run under root privilege.
Note2: The behavior is produced on
Linux XXXXXX 4.4.0-174-generic #204-Ubuntu SMP Wed Jan 29 06:41:01 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
Upvotes: 4
Views: 1298
Reputation: 48612
What is happened?
Usually, there's no page mapped at 0x0
, so dereferencing a null pointer segfaults. But you explicitly mapped a page there, so it doesn't segfault anymore.
Why is it possible to map a accessible data to address NULL?
You answered your own question without realizing it:
Note that: The program has to run under root privilege.
The vm.mmap_min_addr
sysctl normally prevents this by being set to something above 0. However, that sysctl doesn't apply to processes running as root.
With this behavior, how can I use it to exploit the system?
You can't, because if you can do that mmap
, then you're already on the other side of the airtight hatchway.
By the way, your code is technically Undefined Behavior, since you're still dereferencing NULL even though there is something there. And in fact, with most common libcs, it's only the compiler's optimization of printf
to puts
that makes it work at all.
Upvotes: 5
Reputation: 181
What is happened?
mmap()
mapped the file you specified at address 0, like you asked.
Obviously, you shouldn't do that (supply NULL
as the first parameter, with MAP_FIXED
in flags in the fourth parameter), because then you break all kinds of expectations.
(MAP_FIXED
is a footgun: when it is not specified, the kernel will honor the address request, even if already mapped, steamrolling over any existing mappings. Only when you do not use MAP_FIXED
, will the kernel make sure the mapping does not overlap existing mappings and so on, avoiding the zero address if at all possible.)
Linux does not try to stop an user from shooting themselves in the foot; it assumes the user knows what they're trying to accomplish, and only ensures privilege separation and so on. That is why it lets you do things that at first sight do not seem to have any use at all.
(A library might map the first few pages, even as PROT_NONE, and install a SIGBUS handler to catch NULL pointer dereferences without crashing the program. So, there are use cases for this kind of stuff, even if we might not think of them. Conversely, some features can be used in a dangerous/not-useful/self-foot-mutilating manner; the kernel does not judge.)
Also note, as mentioned in comments to the question already, that you are running Linux on an LP64 architecture, where int
is 32-bit, and long
and pointers 64-bit. This means that %x
or %X
only prints the low 32 bits of the unsigned integer representation of a value. Use %p
for pointers. For other types, include <inttypes.h>
(or just <stdint.h>
for the types without their conversion specifiers), and use specific-width or minimum-width types, like uint32_t
or uint_fast32_t
. At minimum, remember to use size_t
for in-memory sizes, like string lengths, or array element counts.
Upvotes: 2