// Copyright © 2017 Collabora Ltd // This file is part of libcapsule. // libcapsule is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation; either version 2.1 of the // License, or (at your option) any later version. // libcapsule is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // You should have received a copy of the GNU Lesser General Public // License along with libcapsule. If not, see <http://www.gnu.org/licenses/>. #include <assert.h> #include <sys/param.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include "debug.h" #include "utils.h" unsigned long debug_flags; // ========================================================================== // these are for finding entries in the dynamic section // note that tha d_un.d_ptr member may be pre-offset by the // linker, or we may beed to adjust it by the value of base ourselves: // this is effectively private linker information and there's no // hard and fast rule: const void * fix_addr (const void *base, ElfW(Addr) offset_or_addr) { if (offset_or_addr < (ElfW(Addr)) base) { // Assume it's an offset, so an address relative to addr return base + offset_or_addr; } else { // Assume it's an absolute address return (const void *) offset_or_addr; } } const ElfW(Dyn) * find_dyn (ElfW(Addr) base, void *start, int what) { ElfW(Dyn) *entry = start + base; for( ; entry->d_tag != DT_NULL; entry++ ) if( entry->d_tag == what ) return entry; return NULL; } size_t find_value (ElfW(Addr) base, void *start, int what) { const ElfW(Dyn) *entry = find_dyn( base, start, what ); return entry ? entry->d_un.d_val : (size_t) -1; } ElfW(Addr) find_ptr (ElfW(Addr) base, void *start, int what) { const ElfW(Dyn) *entry = find_dyn( base, start, what ); if( entry ) { if( entry->d_un.d_ptr < base ) return base + entry->d_un.d_ptr; else return entry->d_un.d_ptr; } return (ElfW(Addr)) NULL; } /* * dynamic_section_find_strtab: * @start: Array of dynamic section entries * @base: Starting address of the shared object in memory * @siz: (out) (optional): Used to return the length of the string * table * * Find the string table for the given dynamic section. * * Returns: (nullable): The string table, a series of concatenated * 0-terminated strings whose total size is written through @siz if * non-%NULL, or %NULL if not found */ const char * dynamic_section_find_strtab (const ElfW(Dyn) *entries, const void *base, size_t *siz) { const ElfW(Dyn) *entry; ElfW(Addr) stab = 0; for( entry = entries; entry->d_tag != DT_NULL; entry++ ) { if( entry->d_tag == DT_STRTAB ) { stab = entry->d_un.d_ptr; } else if( entry->d_tag == DT_STRSZ ) { if( siz ) *siz = entry->d_un.d_val; } } if (stab == 0) { return NULL; } else if (stab < (ElfW(Addr)) base) { return base + stab; } else { return (const char *) stab; } } /* * find_symbol: * @idx: The symbol index * @stab: (array element-type=ElfW(Sym)): The symbol table * @symsz: The length of the symbol table in bytes * @str: The string table * @strsz: The length of the string table in bytes * @name: (out) (optional): Used to return the name, a pointer into @str * * If @idx is in-bounds for @stab, return a pointer to the @idx'th * entry in @stab. Otherwise return %NULL. */ const ElfW(Sym) * find_symbol (int idx, const ElfW(Sym) *stab, size_t symsz, const char *str, size_t strsz, const char **name) { const ElfW(Sym) *target = stab + idx; if( idx < 0 ) return NULL; // we could just accept the index as legitimate but then we'd // run the risk of popping off into an unknown hyperspace coordinate // this way we stop if the target is past the known end of the table: if( (idx + 1) * sizeof(*target) > symsz ) return NULL; if( name ) *name = str + target->st_name; return target; } // ========================================================================== // like strncpy except it guarantees the final byte is NUL in // case we truncated the string. does not yet warn you in any // way about truncation though, should probably fix that: char *safe_strncpy (char *dest, const char *src, size_t n) { char *rv = strncpy( dest, src, n - 1 ); dest[ n - 1 ] = '\0'; return rv; } // prefix is the root of the external tree we're patching in // with libcapsule, path is what we're trying to resolve if // it is a symlink. path must have at least PATH_MAX chars allocated. // // Designed to be called repeatedly, starting with an ABSOLUTE // path the first time. Will write the resolved link back into // path each time and return true, until fed a path which is // not a symlink, at which point it will leave path alone and // return false: int resolve_link(const char *prefix, char path[PATH_MAX]) { int dfd; char rl[PATH_MAX]; char dir[PATH_MAX]; char *end = NULL; int rv = 0; safe_strncpy( dir, path, PATH_MAX ); end = strrchr( dir, '/' ); if( end ) *end = '\0'; else strcpy( dir, "." ); // not sure this is right, FIXME? // but as long as the first call to us // in any sequence was an absolute path // this will never come up dfd = open( dir, O_RDONLY ); if( dfd < 0 ) return 0; rv = readlinkat( dfd, path, rl, sizeof(rl) ); if( rv == sizeof(rl) ) { // Either rl was truncated, or there is no longer space for '\0' rv = -1; goto out; } if( rv >= 0 ) { rl[ rv ] = '\0'; if( rl[0] == '/' ) { if( build_filename( path, PATH_MAX, prefix, rl, NULL ) >= PATH_MAX ) { goto out; } } else { if( build_filename( path, PATH_MAX, dir, rl, NULL ) >= PATH_MAX ) { goto out; } } } out: close( dfd ); return rv != -1; } // todo - check this properly for actual word boundaries and // make it warn about unknown debug keywords: void set_debug_flags (const char *control) { debug_flags = DEBUG_NONE; if ( !control ) return; if( strstr( control, "path" ) ) debug_flags |= DEBUG_PATH; if( strstr( control, "search" ) ) debug_flags |= DEBUG_SEARCH; if( strstr( control, "ldcache" ) ) debug_flags |= DEBUG_LDCACHE; if( strstr( control, "capsule" ) ) debug_flags |= DEBUG_CAPSULE; if( strstr( control, "mprotect" ) ) debug_flags |= DEBUG_MPROTECT; if( strstr( control, "wrappers" ) ) debug_flags |= DEBUG_WRAPPERS; if( strstr( control, "reloc" ) ) debug_flags |= DEBUG_RELOCS; if( strstr( control, "elf" ) ) debug_flags |= DEBUG_ELF; if( strstr( control, "dlfunc" ) ) debug_flags |= DEBUG_DLFUNC; if( strstr( control, "tool" ) ) debug_flags |= DEBUG_TOOL; if( strstr( control, "all" ) ) debug_flags |= DEBUG_ALL; if( !debug_flags ) return; fprintf(stderr, "capsule debug flags: \n" " path : %c # path manipulation and translation" "\n" " search : %c # searching for DSOs" "\n" " ldcache : %c # loading/processing the ld cache" "\n" " capsule : %c # setting up the proxy capsule" "\n" " mprotect: %c # handling mprotect (for RELRO)" "\n" " wrappers: %c # function wrappers installed in the capsule" "\n" " reloc : %c # patching capsule symbols into external DSOs" "\n" " dlfunc : %c # special handling of dlopen/dlsym calls" "\n" " elf : %c # detailed ELF introspection logging" "\n" " tool : %c # command-line tools" "\n", (debug_flags & DEBUG_PATH ) ? 'Y' : 'n' , (debug_flags & DEBUG_SEARCH ) ? 'Y' : 'n' , (debug_flags & DEBUG_LDCACHE ) ? 'Y' : 'n' , (debug_flags & DEBUG_CAPSULE ) ? 'Y' : 'n' , (debug_flags & DEBUG_MPROTECT) ? 'Y' : 'n' , (debug_flags & DEBUG_WRAPPERS) ? 'Y' : 'n' , (debug_flags & DEBUG_RELOCS ) ? 'Y' : 'n' , (debug_flags & DEBUG_DLFUNC ) ? 'Y' : 'n' , (debug_flags & DEBUG_ELF ) ? 'Y' : 'n' , (debug_flags & DEBUG_TOOL ) ? 'Y' : 'n' ); } // soname: bare libfoo.so.X style name // path: [possibly absolute] path to DSO // return true if soname: libFOO.so.X matches // path: /path/to/libFOO.so.X.Y or /path/to/libFOO.so.X int soname_matches_path (const char *soname, const char *path) { const char *path_soname = strrchr( path, '/' ); const char *pattern = path_soname ? path_soname + 1: path; const size_t slen = strlen( soname ); if( strncmp( soname, pattern, slen ) != 0 ) return 0; const char *end = pattern + slen; return ( *end == '\0' || *end == '.' ); } /* * build_filename_va: * @buf: a buffer * @len: length of buffer * @first_path: an absolute or relative path * @ap: further path segments * * Fill @buf with a copy of @first_path, with subsequent path segments * appended to it. * * Returns: The number of bytes that would have been used in buf, not * including the '\0', if there was enough space. If this is >= len, * then truncation has occurred. */ size_t build_filename_va (char *buf, size_t len, const char *first_path, va_list ap) { const char *path; size_t used = 0; int first = 1; int need_separator = 0; if( len > 0 ) *buf = '\0'; for( path = first_path, first = 1; path != NULL; path = va_arg( ap, const char * ), first = 0) { size_t path_len; // Collapse any leading '//' to '/' while( path[0] == '/' && path[1] == '/' ) path++; // If this is not the first path segment, strip any leading '/' if( path[0] == '/' && !first ) path++; path_len = strlen( path ); // Collapse any trailing '/' to nothing, unless this is the // first path segment, in which case collapse them to just '/' while( path_len > (first ? 1 : 0) && path[path_len - 1] == '/' ) { path_len--; } // Ensure there is a '/' before we append path if necessary if( need_separator ) { used++; if( used < len ) { buf[used - 1] = '/'; buf[used] = '\0'; } } // If there's still any space left, try to append the path if( used < len ) { strncpy( buf + used, path, MIN( len - used, path_len + 1 ) ); buf[len - 1] = '\0'; } used += path_len; // Next time, we need to append a separator, unless this was // the first path segment and it was '/' need_separator = (path_len == 0 || path[path_len - 1] != '/'); } va_end( ap ); return used; } /* * build_filename: * @buf: a buffer * @len: length of buffer * @first_path: an absolute or relative path * @...: further path segments * * Fill @buf with a copy of @first_path, with subsequent path segments * appended to it. * * Returns: The number of bytes that would have been used in buf, not * including the '\0', if there was enough space. If this is >= len, * then truncation has occurred. */ size_t build_filename (char *buf, size_t len, const char *first_path, ...) { size_t used; va_list ap; va_start( ap, first_path ); used = build_filename_va( buf, len, first_path, ap ); va_end( ap ); return used; } /* * build_filename_alloc: * @first_path: an absolute or relative path * @...: further path segments * * Allocate and return a string built from @first_path and subsequent * segments. Abort if there is not enough memory. * * Returns: A string that can be freed with free() */ char * build_filename_alloc (const char *first_path, ...) { char *buf; size_t allocate; size_t len; va_list ap; // Do a first pass to count how much space we're going to need va_start( ap, first_path ); // We need an extra byte for the "\0" which isn't included in the // result, consistent with strlcpy() allocate = build_filename_va( NULL, 0, first_path, ap ) + 1; va_end( ap ); buf = xrealloc( NULL, allocate ); // Iterate over the arguments again to fill buf va_start( ap, first_path ); len = build_filename_va( buf, allocate, first_path, ap ); va_end( ap ); // build_filename_va() returns the same thing every time. In // particular this means truncation did not occur, because that // would be indicated by len >= allocate. assert( len + 1 == allocate ); return buf; } ptr_list * ptr_list_alloc(size_t size) { ptr_list *list = xcalloc( 1, sizeof(ptr_list) ); list->loc = xcalloc( size, sizeof(ptr_item) ); list->allocated = size; list->next = 0; return list; } /* * ptr_list_free_to_array: * @list: (transfer full): list to free * @n: (out) (optional): if not %NULL, used to return the number of * items that were in @list * * Add an extra %NULL entry to @list, then return an array with the * same contents as @list, freeing @list. If @n is not %NULL, set it * to the number of elements in the returned array before the %NULL * added by this function. */ void ** ptr_list_free_to_array (ptr_list *list, size_t *n) { void *ret; // Without these assertions, it's unsafe to assign a ptr_item * // to a void ** static_assert( alignof( void * ) == alignof( ptr_item ), "union {void *, ElfW(Addr)} assumed to be same size " "as void *"); static_assert( sizeof( void * ) == sizeof( ptr_item ), "union {void *, ElfW(Addr)} assumed to be same " "alignment as void *"); if( n != NULL ) *n = list->next; ptr_list_push_ptr( list, NULL ); ret = list->loc; list->loc = NULL; ptr_list_free (list); return ret; } void ptr_list_free (ptr_list *list) { free( list->loc ); list->loc = NULL; list->allocated = 0; list->next = 0; free( list ); } void ptr_list_push_addr (ptr_list *list, ElfW(Addr) addr) { if( list->next >= list->allocated ) { list->loc = xrealloc( list->loc, (list->allocated + 16) * sizeof(ptr_item) ); list->allocated += 16; } list->loc[ list->next++ ].addr = addr; } void ptr_list_push_ptr (ptr_list *list, void *ptr) { if( list->next >= list->allocated ) { list->loc = realloc( list->loc, (list->allocated + 16) * sizeof(ptr_item) ); if( !list->loc ) { fprintf( stderr, "failed to realloc ptr_list\n" ); abort(); } list->allocated += 16; } list->loc[ list->next++ ].ptr = ptr; } int ptr_list_add_ptr (ptr_list *list, void *ptr, ptrcmp equals) { for( size_t n = 0; n < list->next; n++ ) if( equals( list->loc[ n ].ptr, ptr ) ) return 0; ptr_list_push_ptr( list, ptr ); return 1; } int ptr_list_contains (ptr_list *list, ElfW(Addr) addr) { if( list->next == 0 ) return 0; for( size_t n = 0; n < list->next; n++ ) if( list->loc[ n ].addr == addr ) return 1; return 0; } void * ptr_list_nth_ptr (ptr_list *list, size_t nth) { if( nth < list->next ) return list->loc[ nth ].ptr; return NULL; } void oom( void ) { fprintf( stderr, "libcapsule: out of memory\n" ); abort(); } char * xstrdup( const char *s ) { char *ret = strdup( s ); if (s != NULL && ret == NULL) oom(); return ret; } void * xrealloc( void *ptr, size_t size ) { void *ret = realloc( ptr, size ); if( ptr != NULL && size != 0 && ret == NULL ) oom(); return ret; } void * xcalloc( size_t n, size_t size ) { void *ret = calloc( n, size ); if( n != 0 && size != 0 && ret == NULL ) oom(); return ret; } int xasprintf( char **dest, const char *format, ...) { int ret; va_list ap; va_start( ap, format ); ret = vasprintf( dest, format, ap ); va_end( ap ); if( ret < 0 ) oom(); return ret; } /* * _capsule_set_error_literal: * @code_dest: (out) (optional): used to return an errno value * @message_dest: (out) (optional) (transfer full): used to return an * error message * @code: an errno value * @message: an error message * * Set an error code, like g_set_error_literal(). */ void _capsule_set_error_literal( int *code_dest, char **message_dest, int code, const char *message ) { if( code_dest != NULL ) *code_dest = code; if( message_dest != NULL ) *message_dest = xstrdup( message ); } /* * _capsule_set_error: * @code_dest: (out) (optional): used to return an errno value * @message_dest: (out) (optional) (transfer full): used to return an * error message * @code: an errno value * @message: an error message * * Set an error code, like g_set_error(). */ void _capsule_set_error( int *code_dest, char **message_dest, int code, const char *format, ... ) { va_list ap; if( code_dest != NULL ) *code_dest = code; if( message_dest != NULL ) { va_start( ap, format ); if( vasprintf( message_dest, format, ap ) < 0 ) oom(); va_end( ap ); } }