diff --git a/.gitattributes b/.gitattributes index 44c20fbe564448f3c2ba5742c5a68db34f605c3d..e2cd211cdea7ccac509ac318fb01222fd5678c77 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,7 +11,10 @@ non_overlapping_heap/tests/Makefile -text non_overlapping_heap/tests/inputfile -text non_overlapping_heap/tests/test1.c -text non_overlapping_heap/tests/test2.c -text +non_overlapping_libraries/Notes -text +non_overlapping_libraries/eglibc-2.19-debian-ubuntu-base.tar.gz -text non_overlapping_libraries/ld-linux-x86-64.so.2 -text +non_overlapping_libraries/nol_eglibc-2.19.patch -text non_overlapping_libraries/patchelf -text /postgres_setup.sh -text /set_command_envs -text diff --git a/non_overlapping_libraries/Notes b/non_overlapping_libraries/Notes new file mode 100644 index 0000000000000000000000000000000000000000..adacb4b3a38d8ee4c6e0fc4b0b30c06fdbf22d3b --- /dev/null +++ b/non_overlapping_libraries/Notes @@ -0,0 +1,42 @@ +Hello there! + +This is the file set for the non-overlapping libraries implementation. The core +concept behind this pass is to modify ld-linux.so for the target binary, so the +load process for the libraries can take advantage of the full non-overlapping-x +concept to prevent exploitable addressees from being common. + +Patching ld means that we end up having to do a build in the context of glibc6. +This is somewhat of a drag - it's not a very fast thing to build, and plenty of +odd things happen in the various contexts in which it works. In order to ensure +that you can build and patch glibc later, I've included, along with the library +binary, the source code for eglibc-2.19 and the patch that can be applied to it +to make the binary. To build, untar and enter the eglibc-2.19 directory, and do +the command "make -f debian/rules build -j 16" (replace 16 with something which +makes sense for your system - I've run it with up to 64 without problems). This +will generate the target binary in build-tree/amd64-libc/elf/. + +Next comes the issue of using it in the MVEE properly. Unfortunately for us, we +cannot use the existing alias infrastructure to override ld.so for a binary. It +is suspected that this is due to the actual loading of the ld binary being done +by the kernel, and thereby avoiding the alias machinery. In any case, we cannot +simply replace the library blob. Instead, we need to use patchelf, which should +handle changing the interpreter section of your target binary's ELF headers. In +doing this, we can target each variant at a different ld.so. Unfortunately, the +different ld.sos have to all be extremely similar (you can't mix no-nol and nol +ld.sos), since they still need to have the same syscall usage patterns. For the +diversity to happen in the memory mapping, nol uses a set of config files. This +file, defined to be at "/variant_specific/nolnoh_config" (but aliased by config +generation to be in each variant's directory). Each config contains at most two +integers - [numvariants [variantindex]]. If neither is provided, nol assumes 64 +variants - this is a sort of fallback mode, in case the config file doesn't get +generated. The base config file for each variant contains just the total number +of variants. In this case, nol randomly chooses a variant index for each __mmap +call overridden in ld, but limits it to that number of variants total, allowing +for probabalistic and structured variants to be run together. When both numbers +are specified in the config file, ld uses that exact variant index in every one +of the allocations, allowing for the provable guarantees to be established. + +The use of a config file is a bit at odds with the design of other mvee bits. I +only did so because, unfortunately, getting the environment variables for info, +while normally good, is not possible in the very early stages of the loader. In +this case, we have almost nothing initialized, mapped, or loaded yet. diff --git a/non_overlapping_libraries/eglibc-2.19-debian-ubuntu-base.tar.gz b/non_overlapping_libraries/eglibc-2.19-debian-ubuntu-base.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..c898a6aa334917b293886f591b170b37e6630560 Binary files /dev/null and b/non_overlapping_libraries/eglibc-2.19-debian-ubuntu-base.tar.gz differ diff --git a/non_overlapping_libraries/nol_eglibc-2.19.patch b/non_overlapping_libraries/nol_eglibc-2.19.patch new file mode 100644 index 0000000000000000000000000000000000000000..d5d0fa869003d2f455efaa609bc94c81d69c6f18 --- /dev/null +++ b/non_overlapping_libraries/nol_eglibc-2.19.patch @@ -0,0 +1,260 @@ +diff -Naur original_glibc/eglibc-2.19/elf/dl-cfar-nol.h eglibc-2.19/elf/dl-cfar-nol.h +--- original_glibc/eglibc-2.19/elf/dl-cfar-nol.h 1969-12-31 19:00:00.000000000 -0500 ++++ eglibc-2.19/elf/dl-cfar-nol.h 2016-03-30 11:32:53.671941958 -0400 +@@ -0,0 +1,5 @@ ++#ifndef DL_CFAR_NOL_H ++#define DL_CFAR_NOL_H ++void * weak_function ++__cfar_nol_mmap(void* address, size_t length, int protect, int flags, int filedes, off_t offset); ++#endif +diff -Naur original_glibc/eglibc-2.19/elf/dl-load.c eglibc-2.19/elf/dl-load.c +--- original_glibc/eglibc-2.19/elf/dl-load.c 2016-03-29 17:32:07.000000000 -0400 ++++ eglibc-2.19/elf/dl-load.c 2016-03-29 17:07:41.111316062 -0400 +@@ -38,6 +38,7 @@ + #include <stap-probe.h> + + #include <dl-dst.h> ++#include <dl-cfar-nol.h> + + /* On some systems, no flag bits are given to specify file mapping. */ + #ifndef MAP_FILE +@@ -1290,7 +1291,7 @@ + - MAP_BASE_ADDR (l)); + + /* Remember which part of the address space this object uses. */ +- l->l_map_start = (ElfW(Addr)) __mmap ((void *) mappref, maplength, ++ l->l_map_start = (ElfW(Addr)) __cfar_nol_mmap ((void *) mappref, maplength, + c->prot, + MAP_COPY|MAP_FILE, + fd, c->mapoff); +diff -Naur original_glibc/eglibc-2.19/elf/dl-minimal.c eglibc-2.19/elf/dl-minimal.c +--- original_glibc/eglibc-2.19/elf/dl-minimal.c 2014-01-03 12:51:28.000000000 -0500 ++++ eglibc-2.19/elf/dl-minimal.c 2016-03-30 12:18:25.867143040 -0400 +@@ -18,6 +18,7 @@ + + #include <errno.h> + #include <limits.h> ++#include <fcntl.h> + #include <string.h> + #include <tls.h> + #include <unistd.h> +@@ -28,6 +29,7 @@ + #include <_itoa.h> + + #include <assert.h> ++#include <dl-cfar-nol.h> + + /* Minimal `malloc' allocator for use while loading shared libraries. + No block is ever freed. */ +@@ -45,6 +47,202 @@ + char **endptr, int base); + + ++#define PROB_NUM_VARIANTS 64 ++int nthisvar = 0; ++int nnumvar = 1; ++int randfd = 0; ++int pagesize = 0; ++int initialized = 0; ++ ++// rounding up to alignments is important and useful ++size_t rounduptomultiple(size_t length, int roundto) { ++ size_t remainder = length % roundto; ++ if(remainder == 0) { ++ return length; ++ } ++ return length + roundto - remainder; ++} ++ ++// we need page alignment for a few things - mmap works much better with it ++size_t rounduptopagemultiple(size_t length) { ++ if(pagesize == 0) { ++ pagesize = getpagesize(); ++ } ++ return rounduptomultiple(length, pagesize); ++} ++ ++bool can_open_config(void) { ++ int fd = __open("/variant_specific/nolnoh_config",O_RDONLY); ++ if(fd == -1) ++ return false; ++ __close(fd); ++ return true; ++} ++ ++int atoi_micro(const char* str) { ++ int ret = 0; ++ while(*str >= '0' && *str <= '9') { ++ ret = ret * 10; ++ ret += *str - '0'; ++ ++str; ++ } ++ return ret; ++} ++ ++int get_config_vars(void) { ++ int fd = __open("/variant_specific/nolnoh_config",O_RDONLY); ++ if(fd == -1) ++ return -1; ++ int bufsize = 17; ++ char buf[bufsize]; ++ int i; ++ for(i=0;i<bufsize;i++) { ++ buf[i]='\0'; ++ } ++ ssize_t nr = __read(fd, buf, bufsize-1); ++ if(nr == 0) { ++ // we read 0 of the tokens from the file, since we read absolutely nothing ++ __close(fd); ++ return 0; ++ } ++ nnumvar = atoi_micro(buf); ++ // note that this currently caps at 512 variants, increase this number if necessary to support more ++ if(nnumvar <= 0 || nnumvar > 512) { ++ // wasn't a valid value ++ nnumvar = 0; ++ __close(fd); ++ return 0; ++ } ++ int mode = 0; ++ // get the second number ++ for(i=0;i<bufsize;i++) { ++ if(buf[i] == '\0') { ++ __close(fd); ++ return 1; ++ } ++ if(buf[i] == ' ' || buf[i] == '\t' || buf[i] == '\r' || buf[i] == '\n') { ++ mode = 1; ++ } ++ if(mode == 1 && buf[i] >= '0' && buf[i] <= '9') { ++ nthisvar = atoi_micro(buf+i); ++ // now sanity check it ++ if(nthisvar >= nnumvar || nthisvar < 0) { ++ nthisvar = 0; ++ __close(fd); ++ return 1; ++ } ++ __close(fd); ++ return 2; ++ } ++ } ++ // we reached the end of the string in an unexpected manner; just say that the thing we have works ++ __close(fd); ++ return 1; ++} ++ ++void * weak_function ++__cfar_nol_mmap(void* address, size_t length, int protect, int flags, int filedes, off_t offset) { ++ if((initialized == 0) || (initialized == 1 && can_open_config())) { ++ int parsed = get_config_vars(); ++ if(parsed <= 0) { ++ //neither nnumvar nor nthivar are set ++ // if we don't know how many variants, try doing randomization of up to 64 ++ if(randfd == 0) { ++ // run in probabalistic mode, since we didn't find the arguments for proper indexing ++ randfd = __open("/dev/cfar_urandom",O_RDONLY); ++ } ++ // we're either going to use nthisvar in the ultimate failsafe mode where we don't do the randomization, or we're going to ignore it; either way, it's 0 ++ nthisvar = 0; ++ // now check if it's still 0, or if it actually set up properly this time ++ if(randfd != 0) { ++ // we don't use nthisvar, just nnumvar and a random choice ++ nnumvar = PROB_NUM_VARIANTS; ++ } else { ++ // if we don't have our randomness source, and we don't have structured info, just behave as normal mmap ++ // (with the two extra mprotect calls, so syscall alignment is preserved) ++ nnumvar = 1; ++ } ++ } else if (parsed == 1) { ++ //nnumvar is set, but not nthisvar ++ // if we don't know how many variants, try doing randomization of up to 64 ++ if(randfd == 0) { ++ // run in probabalistic mode, since we didn't find the arguments for proper indexing ++ randfd = __open("/dev/cfar_urandom",O_RDONLY); ++ } ++ // we're either going to use nthisvar in the ultimate failsafe mode where we don't do the randomization, or we're going to ignore it; either way, it's 0 ++ nthisvar = 0; ++ // if randfd is still 0, we just default to the safe case; in any case, we're done here ++ } else if (parsed == 2) { ++ //nthisvar and nnumvar have been set, we're done here ++ } ++ initialized++; ++ } ++#ifdef DEBUG ++ printf("entering mmap call\n"); ++#endif ++ // figure out what index we are in a randomized context ++ ++ if( ++ (address && (flags & MAP_FIXED)) // must use normal mmap, since the program is now guaranteed the destination address or failure ++ || (flags & MAP_SHARED) // we're sharing between multiple programs, which is complex enough as it is - alignment is important, and we'd have to do funky stuff to ensure non-overlappingness with this regardless ++ ) { ++ // don't modify the arguments - we unfortunately can't touch this much, since it's going to be mapped in a way that we can't nicely diversify (yet) ++ return __mmap(address, length, protect, flags, filedes, offset); ++ } else { ++ // we're diversified now - do fancier stuff ++ size_t alignedlength = length; ++ void* new_mapping = MAP_FAILED; ++ // get the nthisvar for this run ++ if(randfd != 0) { ++ // we're in probabalistic mode ++ uint32_t target = 0; ++ read(randfd, &target, 4); ++ nthisvar = (int)(target % nnumvar); ++ } ++ // branch on whether this is a file-backed allocation or not ++ if (flags & MAP_ANONYMOUS) { ++ // if it's a non-file mapping, just allocate a larger area, and then use part of that ++#if defined(MAP_ALIGN) ++ // gotta round up to the next barrier, but this only matters on solaris (in theory) ++ // support it as soon as it actually comes up ++ length = rounduptomultiple(length, address); ++#endif ++ // we want to allocate a bit more space - this is so that each variant gets its own part of the allocated segment, in a non-overlapping fashion ++ // let's keep page alignment the same ++ alignedlength = rounduptopagemultiple(length); ++ length = alignedlength * nnumvar; ++ new_mapping = __mmap(address, length, protect, flags, filedes, offset); ++ ++ // now that we've done the call, handle the result and return ++ if(new_mapping == MAP_FAILED) { ++ // it failed, and if we were to retry, it would diverge anyway - just return the failure ++ // alternatively, we had to do just a normal mmap, so just return the pointer ++ return new_mapping; ++ } ++ ++ // in the event that we got the memory and such, mprotect the other portions so that they won't be able to be accesssed improperly ++ mprotect(new_mapping, alignedlength * nthisvar, PROT_NONE); ++ mprotect(new_mapping + alignedlength * (nthisvar+1), alignedlength * (nnumvar - (nthisvar + 1)), PROT_NONE); ++#ifdef DEBUG ++ printf("returning new mapping at %p\n", new_mapping + alignedlength * nthisvar); ++#endif ++ return new_mapping + alignedlength * nthisvar; ++ } else { ++ // for file mappings, handle them by repeated allocation of the required size and selection of the currect index ++ int i=0; ++ for(i=0; i < nnumvar; i++) { ++ if(i == nthisvar) { ++ new_mapping = __mmap(address, length, protect, flags, filedes, offset); ++ } else { ++ // ignored region ++ __mmap(address, length, PROT_NONE, flags | MAP_ANONYMOUS, -1, 0); ++ } ++ } ++ return new_mapping; ++ } ++ } ++} ++ + /* Allocate an aligned memory block. */ + void * weak_function + __libc_memalign (size_t align, size_t n) +@@ -74,7 +272,7 @@ + return NULL; + nup = GLRO(dl_pagesize); + } +- page = __mmap (0, nup, PROT_READ|PROT_WRITE, ++ page = __cfar_nol_mmap (0, nup, PROT_READ|PROT_WRITE, + MAP_ANON|MAP_PRIVATE, -1, 0); + if (page == MAP_FAILED) + return NULL;