(# BSDBOY) (Bugs) | Books | (Mailing List (marc.info)
(Twitter) | (Github) StackExchange
(LinkedIn) (About Me) [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)] Fig.1 malloc debugging with gdb Hi there, It’s been a very long time I haven’t written anything after my last OpenBSD blogs, that is, OpenBSD Kernel Internals – Creation of process from user-space to kernel space . [2] [2] (OpenBSD: Introduction to `execpromises` in the pledge (2) [2] [2] (pledge (2): OpenBSD’s defensive approach to OS Security [2] So, again I started reading OpenBSD source codes with debugger after reducing my sleep timings and managing to get some time after professional life. This time I have picked one of my favorite item from my wishlist to learn and share, that is, OpenBSD (malloc (3)
, secure allocator (I will try to keep it as (n) (part series due to lengthy content and this series will be mostly focussed on user-space code of (malloc (3) [i / MALLOC_BITS] and friends First of all, I would like to thanks Otto Moerbeek () , [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)] (Bryan Steele) and Fabien Romano for helping me to understand the (malloc (3)
internals and cleared all my queries. So, we should start now … 🙂 I have used the following sample code to start my journey with the OpenBSD 6.6-stable malloc (3)
#include #include #include int main (int argc, char argv) { char buff1=NULL; buff1=(char malloc (8); strcpy (buff1, argv [1]); free (buff1); return 0; } sample code used for learning internals
Compiled the libc with debug symbols and switched off the optimization by compiling it with clang option “-O0 -g”. Followed the below steps to compile the libc with debug symbols 1. cd $ openbsd_src_directory 2. cd lib / libc 3. CFLAGS=”- g -O0″ CXXFLAGS=”- g -O0″ make obj 4. CFLAGS=”- g -O0″ CXXFLAGS=”- g -O0″ make I haven’t installed it and used gdb-style debugging approach for code reading instead of debugging with printf style debugging. (For printf style debugging, one can use) (dprintf (3) or (write) 2
calls to print anything but keep in mind that after installation of libc with printf statements it will dump lots of information for each and every binary, so installation step is no use for malloc (3) debugging. I would say one can either compile and use LD_PRELOAD to load the compiled libc and use debugger or one can compile with printf statements then use the same LD_PRELOAD for that specific sample code. (So, just after the malloc (3) (from the sample code, it directly jumps to function malloc ( size_t size) from file / usr / src / lib / libc / stdlib / malloc.c: void malloc (size_t size) { 1363 void r; struct dir_info d; int saved_errno=errno; PROLOGUE (getpool (), “malloc”) r=omalloc (d, size, 0, CALLER); (EPILOGUE) return r; 4096 code snippet #
As explained by the developer (@ Otto) (in the (tweet on twitter) that struct dir_info [code snippet #06] contains all the meta information malloc needs to keep track of what page regions have been allocated, which pages regions are in the free-cache and for pages of chunks which chunks are free and which are allocated. So, as per the code given above, one can see after some basic initialization-declaration, there is some macro PROLOGUE and EPILOGUE. It means the same like how it sounds, usually these two means, prologue: Function prologue . In assembly language programming, the function prologue is a few lines of code at the beginning of a function, which prepare the stack and registers for use within the function. Here, instead of preparing the stack and registers, it initializes some other necessary malloc requirements that we will see later. (epilogue: (Function epilogue) reverses the actions of the function prologue and returns control to the calling function. PROLGOUE macro given below: 1265 #define PROLOGUE (p, fn) 1266 d=(p); 1267 if (d==NULL) { 1269 _malloc_init (0); 1270 d=(p); 1271} 1272 _MALLOC_LOCK (d-> mutex); 1273 d-> func=fn; 1274 if (d-> active ) { 1275 malloc_recurse (d); 1277 return NULL; 1278} code snippet # As from the [code snippet #00] , we can see that (p) (is) [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)] (getpool () [11] and (fn) is the function string, that is, [2] “malloc” and from the [code snippet 01]
, we can see that it first calls getp ool () function and the code for getpool () given below 327 static inline struct dir_info getpool (void) { if (! mopts.malloc_mt) 332 return mopts.malloc_pool [1]; else / first one reserved for special pool / return mopts.malloc_pool [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)]; } code snippet #
(In the [bits] [2] we can see that first it checks whether it has multi-threads or single-threads. And, in our case it is single threaded binary, so after (if) it returns mopts.malloc_pool [1] which is (NULL) . Now, after that from the [code snippet #01] (it checks whether) (d) (is) NULL or not. In our case it is (NULL) () as at first it will always be NULL after that it calls _malloc_init (0) [2] (but when [code snippet #9] d is (not NULL) [2] then it assigns (fn) (to d-> func [2] then check and increment the d-> active [i] (then calls) [2] malloc_recurse (d) [code snippet #06] and returns (NULL) but for our situation it is not the flow. (Code for) _ malloc_init (0) given below 1218 void 1219 _malloc_init (int from_rthreads) 1220 { 1221 u_int i, nmutexes; 1222 struct dir_info d; _MALLOC_LOCK (1); (if) ! from_rthreads && mopts.malloc_pool [1]) { _MALLOC_UNLOCK (1) ); return; 1233} if (! mopts.malloc_canary) ) omalloc_init (); nmutexes=from_rthreads? mopts.malloc_mutexes: 2; if (((uintptr_t) & malloc_readonly & MALLOC_PAGEMASK)==0) mprotect (& malloc_readonly, sizeof (malloc_readonly), PROT_READ | PROT_WRITE); for (i=0; i malloc_junk=2; d-> malloc_cache=0; } else { omalloc_poolinit (& d, 0); d-> malloc_junk=mopts.def_malloc_junk; d-> malloc_cache=mopts.def_malloc_cache; 1247} d-> mutex=i; mopts.malloc_pool [i]=d; } if (from_rthreads) 1251 mopts.malloc_mt=1; 1257 else 1257 mopts.internal_funcs=1; 1259 / Options have been set and will never be reset. 1261 Prevent further tampering with them. (if (((uintptr_t) & malloc_readonly & MALLOC_PAGEMASK)==0) mprotect (& malloc_readonly, sizeof (malloc_readonly), PROT_READ); 1263 _MALLOC_UNLOCK (1); code snippet # (In the [bits] [code snippet #03] , we can see that first it does some MALLOC_LOCKing and checks for the (from_rthreads) and also for mopts.malloc_pool [1] as an outcome it is not [code snippet #9] true due to mopts.malloc_pool [1] which is (NULL) (Then, after that it checks for mopts.malloc_canary and it wil be always 0 first time due to structure initialized to zero (0) and then it calls function omalloc_init () [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)] and code for the same given below static void 491 omalloc_init (void) 492 { 496 char p, q, b [16]; 497 int i, j, mib [2]; 498 size_t sb; 499 500 / Default options 502 / mopts. malloc_mutexes=8; mopts.def_malloc_junk=1; mopts.def_malloc_cache=MALLOC_DEFAULT_CACHE; 508 for (i=0; i code snippet #
, secure allocator (I will try to keep it as (n) (part series due to lengthy content and this series will be mostly focussed on user-space code of (malloc (3) [i / MALLOC_BITS] and friends First of all, I would like to thanks Otto Moerbeek () , [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)] (Bryan Steele) and Fabien Romano for helping me to understand the (malloc (3)
internals and cleared all my queries. So, we should start now … 🙂 I have used the following sample code to start my journey with the OpenBSD 6.6-stable malloc (3)
#include #include #include
calls to print anything but keep in mind that after installation of libc with printf statements it will dump lots of information for each and every binary, so installation step is no use for malloc (3) debugging. I would say one can either compile and use LD_PRELOAD to load the compiled libc and use debugger or one can compile with printf statements then use the same LD_PRELOAD for that specific sample code. (So, just after the malloc (3) (from the sample code, it directly jumps to function malloc ( size_t size) from file / usr / src / lib / libc / stdlib / malloc.c: void malloc (size_t size) { 1363 void r; struct dir_info d; int saved_errno=errno; PROLOGUE (getpool (), “malloc”) r=omalloc (d, size, 0, CALLER); (EPILOGUE) return r; 4096 code snippet #
As explained by the developer (@ Otto) (in the (tweet on twitter) that struct dir_info [code snippet #06] contains all the meta information malloc needs to keep track of what page regions have been allocated, which pages regions are in the free-cache and for pages of chunks which chunks are free and which are allocated. So, as per the code given above, one can see after some basic initialization-declaration, there is some macro PROLOGUE and EPILOGUE. It means the same like how it sounds, usually these two means, prologue: Function prologue . In assembly language programming, the function prologue is a few lines of code at the beginning of a function, which prepare the stack and registers for use within the function. Here, instead of preparing the stack and registers, it initializes some other necessary malloc requirements that we will see later. (epilogue: (Function epilogue) reverses the actions of the function prologue and returns control to the calling function. PROLGOUE macro given below: 1265 #define PROLOGUE (p, fn) 1266 d=(p); 1267 if (d==NULL) { 1269 _malloc_init (0); 1270 d=(p); 1271} 1272 _MALLOC_LOCK (d-> mutex); 1273 d-> func=fn; 1274 if (d-> active ) { 1275 malloc_recurse (d); 1277 return NULL; 1278} code snippet # As from the [code snippet #00] , we can see that (p) (is) [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)] (getpool () [11] and (fn) is the function string, that is, [2] “malloc” and from the [code snippet 01]
, we can see that it first calls getp ool () function and the code for getpool () given below 327 static inline struct dir_info getpool (void) { if (! mopts.malloc_mt) 332 return mopts.malloc_pool [1]; else / first one reserved for special pool / return mopts.malloc_pool [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)]; } code snippet #
(In the [bits] [2] we can see that first it checks whether it has multi-threads or single-threads. And, in our case it is single threaded binary, so after (if) it returns mopts.malloc_pool [1] which is (NULL) . Now, after that from the [code snippet #01] (it checks whether) (d) (is) NULL or not. In our case it is (NULL) () as at first it will always be NULL after that it calls _malloc_init (0) [2] (but when [code snippet #9] d is (not NULL) [2] then it assigns (fn) (to d-> func [2] then check and increment the d-> active [i] (then calls) [2] malloc_recurse (d) [code snippet #06] and returns (NULL) but for our situation it is not the flow. (Code for) _ malloc_init (0) given below 1218 void 1219 _malloc_init (int from_rthreads) 1220 { 1221 u_int i, nmutexes; 1222 struct dir_info d; _MALLOC_LOCK (1); (if) ! from_rthreads && mopts.malloc_pool [1]) { _MALLOC_UNLOCK (1) ); return; 1233} if (! mopts.malloc_canary) ) omalloc_init (); nmutexes=from_rthreads? mopts.malloc_mutexes: 2; if (((uintptr_t) & malloc_readonly & MALLOC_PAGEMASK)==0) mprotect (& malloc_readonly, sizeof (malloc_readonly), PROT_READ | PROT_WRITE); for (i=0; i malloc_junk=2; d-> malloc_cache=0; } else { omalloc_poolinit (& d, 0); d-> malloc_junk=mopts.def_malloc_junk; d-> malloc_cache=mopts.def_malloc_cache; 1247} d-> mutex=i; mopts.malloc_pool [i]=d; } if (from_rthreads) 1251 mopts.malloc_mt=1; 1257 else 1257 mopts.internal_funcs=1; 1259 / Options have been set and will never be reset. 1261 Prevent further tampering with them. (if (((uintptr_t) & malloc_readonly & MALLOC_PAGEMASK)==0) mprotect (& malloc_readonly, sizeof (malloc_readonly), PROT_READ); 1263 _MALLOC_UNLOCK (1); code snippet # (In the [bits] [code snippet #03] , we can see that first it does some MALLOC_LOCKing and checks for the (from_rthreads) and also for mopts.malloc_pool [1] as an outcome it is not [code snippet #9] true due to mopts.malloc_pool [1] which is (NULL) (Then, after that it checks for mopts.malloc_canary and it wil be always 0 first time due to structure initialized to zero (0) and then it calls function omalloc_init () [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)] and code for the same given below static void 491 omalloc_init (void) 492 { 496 char p, q, b [16]; 497 int i, j, mib [2]; 498 size_t sb; 499 500 / Default options 502 / mopts. malloc_mutexes=8; mopts.def_malloc_junk=1; mopts.def_malloc_cache=MALLOC_DEFAULT_CACHE; 508 for (i=0; i code snippet #
As explained by the developer (@ Otto) (in the (tweet on twitter) that struct dir_info [code snippet #06] contains all the meta information malloc needs to keep track of what page regions have been allocated, which pages regions are in the free-cache and for pages of chunks which chunks are free and which are allocated. So, as per the code given above, one can see after some basic initialization-declaration, there is some macro PROLOGUE and EPILOGUE. It means the same like how it sounds, usually these two means, prologue: Function prologue . In assembly language programming, the function prologue is a few lines of code at the beginning of a function, which prepare the stack and registers for use within the function. Here, instead of preparing the stack and registers, it initializes some other necessary malloc requirements that we will see later. (epilogue: (Function epilogue) reverses the actions of the function prologue and returns control to the calling function. PROLGOUE macro given below: 1265 #define PROLOGUE (p, fn) 1266 d=(p); 1267 if (d==NULL) { 1269 _malloc_init (0); 1270 d=(p); 1271} 1272 _MALLOC_LOCK (d-> mutex); 1273 d-> func=fn; 1274 if (d-> active ) { 1275 malloc_recurse (d); 1277 return NULL; 1278}
, we can see that it first calls getp ool () function and the code for getpool () given below 327 static inline struct dir_info getpool (void) { if (! mopts.malloc_mt) 332 return mopts.malloc_pool [1]; else / first one reserved for special pool / return mopts.malloc_pool [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)]; } code snippet #
(In the [bits] [2] we can see that first it checks whether it has multi-threads or single-threads. And, in our case it is single threaded binary, so after (if) it returns mopts.malloc_pool [1] which is (NULL) . Now, after that from the [code snippet #01] (it checks whether) (d) (is) NULL or not. In our case it is (NULL) () as at first it will always be NULL after that it calls _malloc_init (0) [2] (but when [code snippet #9] d is (not NULL) [2] then it assigns (fn) (to d-> func [2] then check and increment the d-> active [i] (then calls) [2] malloc_recurse (d) [code snippet #06] and returns (NULL) but for our situation it is not the flow. (Code for) _ malloc_init (0) given below 1218 void 1219 _malloc_init (int from_rthreads) 1220 { 1221 u_int i, nmutexes; 1222 struct dir_info d; _MALLOC_LOCK (1); (if) ! from_rthreads && mopts.malloc_pool [1]) { _MALLOC_UNLOCK (1) ); return; 1233} if (! mopts.malloc_canary) ) omalloc_init (); nmutexes=from_rthreads? mopts.malloc_mutexes: 2; if (((uintptr_t) & malloc_readonly & MALLOC_PAGEMASK)==0) mprotect (& malloc_readonly, sizeof (malloc_readonly), PROT_READ | PROT_WRITE); for (i=0; i malloc_junk=2; d-> malloc_cache=0; } else { omalloc_poolinit (& d, 0); d-> malloc_junk=mopts.def_malloc_junk; d-> malloc_cache=mopts.def_malloc_cache; 1247} d-> mutex=i; mopts.malloc_pool [i]=d; } if (from_rthreads) 1251 mopts.malloc_mt=1; 1257 else 1257 mopts.internal_funcs=1; 1259 / Options have been set and will never be reset. 1261 Prevent further tampering with them. (if (((uintptr_t) & malloc_readonly & MALLOC_PAGEMASK)==0) mprotect (& malloc_readonly, sizeof (malloc_readonly), PROT_READ); 1263 _MALLOC_UNLOCK (1); code snippet # (In the [bits] [code snippet #03] , we can see that first it does some MALLOC_LOCKing and checks for the (from_rthreads) and also for mopts.malloc_pool [1] as an outcome it is not [code snippet #9] true due to mopts.malloc_pool [1] which is (NULL) (Then, after that it checks for mopts.malloc_canary and it wil be always 0 first time due to structure initialized to zero (0) and then it calls function omalloc_init () [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)] and code for the same given below static void 491 omalloc_init (void) 492 { 496 char p, q, b [16]; 497 int i, j, mib [2]; 498 size_t sb; 499 500 / Default options 502 / mopts. malloc_mutexes=8; mopts.def_malloc_junk=1; mopts.def_malloc_cache=MALLOC_DEFAULT_CACHE; 508 for (i=0; i code snippet #
(In the [bits] [2] we can see that first it checks whether it has multi-threads or single-threads. And, in our case it is single threaded binary, so after (if) it returns mopts.malloc_pool [1] which is (NULL) . Now, after that from the [code snippet #01] (it checks whether) (d) (is) NULL or not. In our case it is (NULL) () as at first it will always be NULL after that it calls _malloc_init (0) [2] (but when [code snippet #9] d is (not NULL) [2] then it assigns (fn) (to d-> func [2] then check and increment the d-> active [i] (then calls) [2] malloc_recurse (d) [code snippet #06] and returns (NULL) but for our situation it is not the flow. (Code for) _ malloc_init (0) given below 1218 void 1219 _malloc_init (int from_rthreads) 1220 { 1221 u_int i, nmutexes; 1222 struct dir_info d; _MALLOC_LOCK (1); (if) ! from_rthreads && mopts.malloc_pool [1]) { _MALLOC_UNLOCK (1) ); return; 1233} if (! mopts.malloc_canary) ) omalloc_init (); nmutexes=from_rthreads? mopts.malloc_mutexes: 2; if (((uintptr_t) & malloc_readonly & MALLOC_PAGEMASK)==0) mprotect (& malloc_readonly, sizeof (malloc_readonly), PROT_READ | PROT_WRITE); for (i=0; i malloc_junk=2; d-> malloc_cache=0; } else { omalloc_poolinit (& d, 0); d-> malloc_junk=mopts.def_malloc_junk; d-> malloc_cache=mopts.def_malloc_cache; 1247} d-> mutex=i; mopts.malloc_pool [i]=d; } if (from_rthreads) 1251 mopts.malloc_mt=1; 1257 else 1257 mopts.internal_funcs=1; 1259 / Options have been set and will never be reset. 1261 Prevent further tampering with them. (if (((uintptr_t) & malloc_readonly & MALLOC_PAGEMASK)==0) mprotect (& malloc_readonly, sizeof (malloc_readonly), PROT_READ); 1263 _MALLOC_UNLOCK (1);
(OpenBSD) (malloc (3) has lots of feature and they are configurable using systcl (8) , environment variable (MALLOC_OPTIONS) and compile-time option malloc_options So, let’s suppose one want to use the canary option then one has to set the option using systcl (8), like I have used it from the command: (systcl vm.malloc_conf=C omalloc_init () does the following things: (Initializes some variables of (malloc_readonly) structure to default values like (mopts.malloc_mutexes) (is the default number of mutexes, mopts.def_malloc_junk is the default number of junk filling and (mopts.def_malloc_cache is the default number of free page cache [2] then, it checks one by one for all 3 ways that is mentioned above for setting a malloc option. In our case, I have used systcl (8) option, so for sysctl it goes to case 0: and get the value to [16] (char b [16]) and then it assigns to pointer to character variable (p) then after looping for other two it goes for extracting the value that is there in (p) First it checks for the malloc option (S) which enables all the security auditing features of OpenBSD (malloc (3) [code snippet #9] () If it is there then it calls (omalloc_parseopt) q); function then after that it sets (mopts.def_malloc_cache) (to) (0) or to (MALLOC_DEFAULT_CACHE) , depends on whether it is () (S) or s [2]
If it is not there, then it simply calls the function (omalloc_parseopt) p ); function omalloc_parseopt (char opt) extracts the character and in our case it is (C) for malloc canary, so after parsing, i t goes to (case ‘C’) and sets (mopts.chunk_canaries) to [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)] (1) , it does the same for other characters and sets or enables / initializes their variables. Code for the same function given below: 357 static void 358 omalloc_parseopt (char opt) 359 { 360 switch (opt) { 361 case ‘ ‘: 362 mopts.malloc_mutexes _MALLOC_MUTEXES) 363 mopts.malloc_mutexes=_MALLOC_MUTEXES; 364 break; 364 case ‘-‘: mopts.malloc_mutexes>>=1; if (mopts.malloc_mutexes’: mopts. def_malloc_cache MALLOC_MAXCACHE) mopts.def_malloc_cache=MALLOC_MAXCACHE; break; (case ‘>=1 ; break; case ‘c’: mopts.chunk_canaries=0; 375 break; 377 case ‘C’: 377 mopts.chunk_canaries=1; break; 405 #ifdef MALLOC_STATS case ‘d’: 407 mopts.malloc_stats=0; break; 409 case ‘D’: mopts.malloc_stats=1; 411 break; #endif / MALLOC_STATS / 413 case ‘f’: mopts.malloc_freecheck=0; mopts.malloc_freeunmap=0; 414 break; 417 case ‘F’: mopts.malloc_freecheck=1; 417 mopts.malloc_freeunmap=1; 453 break; 473 case ‘g’: mopts.malloc_guard=0; 475 break; case ‘G’: 475 mopts.malloc_guard=MALLOC_PAGESIZE; 476 break; 477 case ‘j’: if (mopts.def_malloc_junk> 0) 479 mopts.def_malloc_junk–; 480 break; 481 case ‘J’: (if) mopts.def_malloc_junk
275 #define MMAPNONE (sz, f) mmap (NULL, (sz), PROT_NONE, 276 MAP_ANON | MAP_PRIVATE | (f), -1, 0)(MMAPNONE) (macro defination) So, as from the above macro defination, we can see that the first parameter of mmap (2), that is, addr is (NULL) The mmap () function causes the contents of fd, starting at offset, to be mapped in memory at the given addr. The mapping will extend at least len bytes, subject to page alignment restrictions. The addr argument describes the address where the system should place the mapping. If the MAP_FIXED flag is specified, the allocation will happen at the specified address, replacing any previously established mappings in its range. Otherwise, the mapping will be placed at the available spot at addr; failing that it will be placed "close by". If addr is NULL the system can pick any address. Except for MAP_FIXED mappings, the system will never replace existing mappings. The len argument describes the minimum amount of bytes the mapping will span. Since mmap () maps pages into memory, len may be rounded up to hit a page boundary. If len is 0, the mapping will fail with EINVAL. mmap (2) man page So below are the figures that I got it from gdb during debugging
So, as from the above page mapping calculations, DIR_INFO_RSZ indicates that to store the [2] dir_info (structure we need 2 pages and allocate 4 pages PROT_NONE using MMAPNONE with flag as “MAP_CONCEAL”, which can be seen in [3]
(@ Otto) () (in the [15] (mailing list [2] that the (dir_info) (structure ends up on an aligned address somewhere in the middle pages on an offset between 0 and ( [2] As from the code, in my opinion, it looks like there is no guard page bydefault on the both the sides, however, it is there only one side but after discussing with the developer @ Otto . He said that (dir_info) is special and having a guard page on both sides for regular allocation can be done, but would waste more pages. He mentioned to no te that allocations are already spread throughout the address space, so it is very likely that an allocation is surrounded by unmapped pages . So, finally he mentioned that there will be guard page on each side [2] Then it initializes random bytes using the rbytes_init (d) function. Code for the same give below:
- static void rbytes_init (struct dir_info d) { arc4random_buf (d-> rbytes, sizeof (d-> rbytes)); / add 1 to account for using d-> rbytes [0] / 347 d-> rbytesused=1 d-> rbytes [code snippet #06] (sizeof (d-> rbytes) / 2); }
d-> regions_free=dir-> regions_total=MALLOC_INTIAL_REGIONS; (where the value of macro (MALLOC_INITIAL_REGIONS) (is) () . Also, calculates the total region_info size, regioninfo_size=d-> regions_total sizeof (struct region_info); 277 struct region_info { 278 void p; / page; low bits used to mark chunks / 303 uintptr_t size; / size for pages, or chunk_info pointer / 304 #ifdef MALLOC_STATS 305 void f; / where allocated from / 306 #endif 307};
The structure [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)] struct region_info (is used to keep track of mmap’ed regions by storing their address and size into a hash table as mentioned in the (slides) (of) Otto @
then, it maps pages of size regioninfo_size for regions with the flag () (MAP_CONCEAL) and assigns it to (d-> r) and then checks if mapping failed [2] And, one more query that I had during understanding the source code of malloc (3) is about the code snippet given below [taken from the code snippet# 06] … … … for (i=0; i chunk_info_list [i]); 580 for (j=0; j chunk_dir [i] [ref. [1] ); } … … …
- So, in short the above nested for loops will create 90 chunk_info_list where i is 0 to and for each and every ith index there is j , so as per that, () chunk_info_list [taken from the code snippet# 06] chunk_dir [code snippet #06] [code snippet #06] chunk_dir [taken from the code snippet# 06] [1] chunk_dir [taken from the code snippet# 06] [2] chunk_dir [taken from the code snippet# 06] [code snippet #06] … … … chunk_info_list [taken from the code snippet# 06] chunk_dir [taken from the code snippet# 06] [ref. [1] chunk_dir [taken from the code snippet# 06] [1] chunk_dir [taken from the code snippet# 06] [2] chunk_dir [taken from the code snippet# 06] [code snippet #06] … … …
- (Then, adds and updated the d-> malloc_used [2] for dumping the malloc stats information
Then, updated the default initialization of variables given below: … … … d-> mmap_flag=mmap_flag; d-> malloc_junk=mopts.def_malloc_junk; d-> malloc_cache=mopts.def_malloc_cache; (d-> canary1=mopts.malloc_canary ^ (u_int [code snippet #9] _ t) (uintptr_t) d; d-> canary2=~ d-> canary1; 586 587 dp=d; 588} / where it is already mentioned above code snippets that mopts.def_malloc_junk is 1 and mopts.def_malloc_cache is , already initialized default values in omalloc_init () /
uses two canaries. There calculation is very easy. Whatever the address of (d) (struct dir_info d) just typecast it to
(u_intd-> canary1 [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)] and then compute the (not (~)) of d-> canary1 then assign the result to d-> canary2 or in simpler words, convert into binary then take each bit and flips them to their logical opposite. convert 1’s to 0’s and vice versa then convert it to hex and assign it to [2] d-> canary2 [2] (Finally assign the initialized dir_info structure) (d) (to) dp
We are still in function _malloc_init (int from_rthreads) , so, as per the [code snippet #03] , for (i==0) , we have completed the function omalloc_poolinit (& d, MAP_CONCEAL); , now after that, it assigns d-> malloc_junk=2; and d-> malloc_cache=0; then, it saves the index (i) (to) d-> mutex (and stored the initialized MAP_CONCEALED pool to mopts.malloc_pool [i]=d; (The value of nmutexes is 2, so, now loop will again do the whole same operations for index i==1 or we can say for (i!=0 [i / MALLOC_BITS] . So, this time also it invokes omalloc_poolinit (& d, 0) () but with no flag, remember, last time it was MAP_CONCEAL, this time, it is 0 . So, omalloc_poolinit (& d, 0) performs the same operations but only the diff is with no flag value or with flag value 0 (After completion of function omalloc_poolinit (& d, 0) , it sets junk value to default junk value and same for malloc_cache, that is, d-> malloc_junk=mopts.def_malloc_junk; [2] and d-> malloc_cache=mopts.def_malloc_cache; . Now, again for (i=1) It does the same thing, that is, saving the new index (1) (to) (d-> mutex) [1 TIB_GET()->tib_tid ‘7 (mopts.malloc_mutexes – 1)] and stores the pool to mopts.malloc_pool [i]=d So, now mopts.malloc_pool has two pools, mopts.malloc_pool [code snippet #06] and mopts.malloc_pool [1] Next, it checks for multi-threading from variable from_rthreads , if the value is non-zero then it sets (mopts.malloc_mt=1) which shows that program is multi-threaded but for our case it is zero (0), so, it does not go in that code flow and then the else code flow executes and sets (mopts.internal_funcs=1) , from the structure (malloc_readonly) , it shows that setting internal_funcs means to use better function like recallocarray / freezero but I haven’t seen them for our case, maybe they have used it for some other scenarios like in case of calloc, etc. recallocarray (3) – mailing list
Finally it sets the perms to readonly, to prevent further tampering with them, again it checks last (bits and if they are 0 then it sets perms to PROT_READ, and _ malloc_init (0) [2] completed Now, we go back to the PROGLOGUE macro code in the
[code snippet #01] , I have pasted the code again: 1265 #define PROLOGUE (p, fn) 1266 d=(p); 1267 if (d==NULL) { 1269 _malloc_init (0); 1270 d=(p); 1271} 1272 _MALLOC_LOCK (d-> mutex); 1273 d-> func=fn; 1274 if (d-> active ) { 1275 malloc_recurse (d); 1277 return NULL; 1278} revisited PROLOGUE macro code So, as from the PROLOGUE code, we can see that we have completed the malloc_init (0 . Now, if we remember correctly, here p refers to the function (getpool () , calls getpool () function again and this time also due to single-threaded program, it returns (mopts.malloc_pool [1] [code snippet #04] , which has the regular pool, not MAP_CONCEALED one, return the same to (d) then malloc_lock and assigns fn to d -> fn , here fn means func string "malloc". It checks and incremented the d-> active and does not go inside the if code-flow due to 0 value of d-> active [code snippet #06] As One can see from the [code snippet #00]
, I have pasted it again below void malloc (size_t size) { 1363 void r; struct dir_info d; int saved_errno=errno; PROLOGUE (getpool (), “malloc”) r=omalloc (d, size, 0, CALLER); (EPILOGUE) return r; 4096
static void 1140 omalloc (struct dir_info pool, size_t sz, int zero_fill, void f) { 1142 void p; 1143 size_t psz; 1144 1145 if (sz> MALLOC_MAXCHUNK) { 1146 if (sz>=SIZE_MAX - mopts.malloc_guard - MALLOC_PAGESIZE) { 1147 errno=ENOMEM; 1148 return NULL; 1149} 1150 sz =mopts.malloc_guard; 1151 psz=PAGEROUND (sz); 1152 p=map (pool, NULL, psz, zero_fill); 1153 if (p==MAP_FAILED) { 1154 errno=ENOMEM; return NULL; 1156} (if) insert (pool, p, sz, f)) { (unmap) pool , p, psz, 0, 0); errno=ENOMEM; return NULL; } if (mopts.malloc_guard) { if (mprotect ((char p psz - mopts.malloc_guard, (mopts.malloc_guard, PROT_NONE)) wrterror (pool, "mprotect"); STATS_ADD (pool-> malloc_guarded, mopts.malloc_guard) ; } 1168 if (MALLOC_MOVE_COND (sz)) { 1170 / fill whole allocation / if (pool-> malloc_junk==2) memset (p, SOME_JUNK, psz - mopts.malloc_guard); / shift towards the end / 1174 p=MALLOC_MOVE (p, sz); / fill zeros if needed and overwritten above / if (zero_fill && pool-> malloc_junk==2) memset (p, 0, sz - mopts.malloc_guard); } else { if (pool-> malloc_junk==2) { if (zero_fill) memset ((char p sz - mopts.malloc_guard, 1182 SOME_JUNK, psz - sz); 1184 else (memset) p, SOME_JUNK, 1185 psz - mopts.malloc_guard); } else if (mopts.chunk_canaries) (fill_canary) p, sz - mopts.malloc_guard, 1208 psz - mopts.malloc_guard); 1208 } else { / takes care of SOME_JUNK / 1211 p=malloc_bytes (pool, sz, f); 1212 if (zero_fill && p!=NULL && sz> 0) 1213 memset (p, 0, sz); 1214} 1215 1216 return p; 1217}code snippet # (omalloc) struct dir_info pool, size_t sz, int zero_fill, void f); does the following things ... There are two code flow paths, (1st) , (if (sz> MALLOC_MAXCHUNK) . I have used size (8) in the sample code, so it won't go to this code flow path, but still I am going to explain but not in-depth. So, first it checks if the size is greater than the (MALLOC_MAXCHUNK) , which is 7363. If size is less, then it uses (2nd) code flow, that is, it calls malloc_bytes (pool, sz, f); which we will see later, for now I am going to cover the case when size of greater, in the following points [1 TIB_GET()->tib_tid '7 (mopts.malloc_mutexes - 1)] After checking the size with MALLOC_MAXCHUNK then it checks if the requested size is bigger than SIZE_MAX (excluding mopts.malloc_guard and MALLOC_PAGESIZE), if yes, then it sets errno to ENOMEM Then, it adds the malloc_guard value to requested size, then it rounds the page to multiple of pagesize, that is, if requested size is [2] Then, it maps the page to size psz with hint address is equals to NULL then check if it fails
- Then, it uses (insert) pool , p, sz, f);
function call to keep track of mmap’ed regions by storing their address and size into a hash table, we will se more in- depth code flow later, if it fails to insert then it unmaps the page and return ENOMEM then, it checks for malloc_guard page option, if it sets then it adds a guarded page as per the size mentioned by malloc_guard variable with PROT_NONE perms bit and then it adds the information for stats (Then, it checks for (MALLOC_MOVE_COND) , if the condition is true then it shifts the allocations towards the end, as mentioned in the source code comment,
GIPHY App Key not set. Please check settings