/* * * Copyright (c) 2008-2011 Erich Hoover * Copyright (c) 2015 Timothy Pearson * * libr libbfd Backend - Add resources into ELF binaries using libbfd * * *** PLEASE READ THE README FILE FOR LICENSE DETAILS SPECIFIC TO THIS FILE *** * * This program 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 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * To provide feedback, report bugs, or otherwise contact me: * ehoover at mines dot edu * */ /* Include compile-time parameters */ #include "config.h" #include "libr.h" #include "libr-internal.h" /* File access */ #include /* Safe rename requires some errno() knowledge */ #include #include #include #include #include #include #include /* * Build the libr_file handle for processing with libbfd */ libr_intstatus open_handles(libr_file *file_handle, char *filename, libr_access_t access) { bfd *handle = NULL; handle = bfd_openr(filename, "default"); if(!handle) RETURN(LIBR_ERROR_OPENFAILED, "Failed to open input file"); if(!bfd_check_format(handle, bfd_object)) RETURN(LIBR_ERROR_WRONGFORMAT, "Invalid input file format: not a libbfd object"); if(bfd_get_flavour(handle) != bfd_target_elf_flavour) RETURN(LIBR_ERROR_WRONGFORMAT, "Invalid input file format: not an ELF file"); bfd_set_error(bfd_error_no_error); file_handle->filename = filename; file_handle->bfd_read = handle; file_handle->access = access; if(access == LIBR_READ_WRITE) { struct stat file_stat; int fd; /* Check for write permission on the file */ fd = open(filename, O_WRONLY); if(fd == ERROR) RETURN(LIBR_ERROR_WRITEPERM, "No write permission for file"); close(fd); /* Obtain the access mode of the input file */ if(stat(filename, &file_stat) == ERROR) RETURN(LIBR_ERROR_NOSIZE, "Failed to obtain file size"); file_handle->filemode = file_stat.st_mode; file_handle->fileowner = file_stat.st_uid; file_handle->filegroup = file_stat.st_gid; /* Open a temporary file with the same settings as the input file */ strcpy(file_handle->tempfile, LIBR_TEMPFILE); file_handle->fd_handle = mkstemp(file_handle->tempfile); handle = bfd_openw(file_handle->tempfile, bfd_get_target(file_handle->bfd_read)); if(!bfd_set_format(handle, bfd_get_format(file_handle->bfd_read))) RETURN(LIBR_ERROR_SETFORMAT, "Failed to set output file format to input file format"); if(!bfd_set_arch_mach(handle, bfd_get_arch(file_handle->bfd_read), bfd_get_mach(file_handle->bfd_read))) RETURN(LIBR_ERROR_SETARCH, "Failed to set output file architecture to input file architecture"); /* twice needed ? */ if(!bfd_set_format(handle, bfd_get_format(file_handle->bfd_read))) RETURN(LIBR_ERROR_SETFORMAT, "Failed to set output file format to input file format"); file_handle->bfd_write = handle; } else { file_handle->fd_handle = 0; file_handle->bfd_write = NULL; } RETURN_OK; } /* * Check to see if a symbol should be kept */ int keep_symbol(libr_section *sections, libr_section *chkscn) { libr_section *scn; /* Check that the section is publicly exposed */ for(scn = sections; scn != NULL; scn = scn->next) { if(scn == chkscn) { /* if it is, and has size zero, then it was marked for deletion */ if( #ifdef HAVE_BFD_2_34 bfd_section_size(chkscn) == 0 #else bfd_get_section_size(chkscn) == 0 #endif ) { return false; } return true; } } return true; } /* * Remove the symbol corresponding to a deleted section */ void remove_sections(libr_section *sections, void *symtab_buffer, long *symtab_count) { asymbol **symtab = (asymbol **) symtab_buffer; long i, cnt = *symtab_count; for(i=0;isections; iscn != NULL; iscn = iscn->next) { if( #ifdef HAVE_BFD_2_34 bfd_section_size(iscn) == 0 #else bfd_get_section_size(iscn) == 0 #endif ) { continue; /* Section has been marked for deletion */ } /* Use SEC_LINKER_CREATED to ask the libbfd backend to take care of configuring the section */ // Keep the ARM_ATTRIBUTES section type intact on armhf systems // If this is not done, readelf -A will not print any architecture information for the modified library, // and ldd will report that the library cannot be found if (strcmp(iscn->name, ".ARM.attributes") == 0) { oscn = bfd_make_section_anyway_with_flags(ohandle, iscn->name, iscn->flags); } else { oscn = bfd_make_section_anyway_with_flags(ohandle, iscn->name, iscn->flags | SEC_LINKER_CREATED); } if(oscn == NULL) { printf("failed to create out section: %s\n", bfd_errmsg(bfd_get_error())); return false; } if( #ifdef HAVE_BFD_2_34 !bfd_set_section_size(oscn, iscn->size) #else !bfd_set_section_size(ohandle, oscn, iscn->size) #endif ) { printf("failed to set data size: %s\n", bfd_errmsg(bfd_get_error())); return false; } #ifdef HAVE_BFD_2_34 vma = bfd_section_vma(iscn); #else vma = bfd_section_vma(ihandle, iscn); #endif if( #ifdef HAVE_BFD_2_34 !bfd_set_section_vma(oscn, vma) #else !bfd_set_section_vma(ohandle, oscn, vma) #endif ) { printf("failed to set virtual memory address: %s\n", bfd_errmsg(bfd_get_error())); return false; } oscn->lma = iscn->lma; if( #ifdef HAVE_BFD_2_34 !bfd_set_section_alignment(oscn, bfd_section_alignment(iscn)) #else !bfd_set_section_alignment(ohandle, oscn, bfd_section_alignment(ihandle, iscn)) #endif ) { printf("failed to compute section alignment: %s\n", bfd_errmsg(bfd_get_error())); return false; } oscn->entsize = iscn->entsize; iscn->output_section = oscn; iscn->output_offset = vma; if(!bfd_copy_private_section_data(ihandle, iscn, ohandle, oscn)) { printf("failed to compute section alignment: %s\n", bfd_errmsg(bfd_get_error())); return false; } } return true; } /* * Go through the rather complicated process of using libbfd to build the output file */ int build_output(libr_file *file_handle) { void *symtab_buffer = NULL, *reloc_buffer = NULL, *buffer = NULL; bfd_size_type symtab_size, reloc_size, size; bfd *ohandle = file_handle->bfd_write; bfd *ihandle = file_handle->bfd_read; long symtab_count, reloc_count; libr_section *iscn, *oscn; if(!bfd_set_start_address(ohandle, bfd_get_start_address(ihandle))) { printf("failed to set start address: %s\n", bfd_errmsg(bfd_get_error())); return false; } if(!bfd_set_file_flags(ohandle, bfd_get_file_flags(ihandle))) { printf("failed to set file flags: %s\n", bfd_errmsg(bfd_get_error())); return false; } /* Setup the sections in the output file */ if(!setup_sections(ihandle, ohandle)) return false; /* error already printed */ if(!bfd_copy_private_header_data(ihandle, ohandle)) { printf("failed to copy header: %s\n", bfd_errmsg(bfd_get_error())); return false; /* failed to create section */ } /* Get the old symbol table */ if((bfd_get_file_flags(ihandle) & HAS_SYMS) == 0) { printf("file has no symbol table: %s\n", bfd_errmsg(bfd_get_error())); return false; } symtab_size = bfd_get_symtab_upper_bound(ihandle); if((signed)symtab_size < 0) { printf("failed to get symbol table size: %s\n", bfd_errmsg(bfd_get_error())); return false; } symtab_buffer = malloc(symtab_size); symtab_count = bfd_canonicalize_symtab(ihandle, symtab_buffer); if(symtab_count < 0) { printf("failed to get symbol table number of entries: %s\n", bfd_errmsg(bfd_get_error())); return false; } /* Tweak the symbol table to remove sections that no-longer exist */ remove_sections(ihandle->sections, symtab_buffer, &symtab_count); bfd_set_symtab(ohandle, symtab_buffer, symtab_count); /* Actually copy section data */ for(iscn = ihandle->sections; iscn != NULL; iscn = iscn->next) { #ifdef HAVE_BFD_2_34 size = bfd_section_size(iscn); #else size = bfd_get_section_size(iscn); #endif if(size == 0) continue; /* Section has been marked for deletion */ oscn = iscn->output_section; reloc_size = bfd_get_reloc_upper_bound(ihandle, iscn); if(reloc_size == 0) bfd_set_reloc(ohandle, oscn, NULL, 0); else { reloc_buffer = malloc(reloc_size); reloc_count = bfd_canonicalize_reloc(ihandle, iscn, reloc_buffer, symtab_buffer); bfd_set_reloc(ohandle, oscn, reloc_buffer, reloc_count); } if( #ifdef HAVE_BFD_2_34 bfd_section_flags(iscn) & SEC_HAS_CONTENTS #else bfd_get_section_flags(ihandle, iscn) & SEC_HAS_CONTENTS #endif ) { /* NOTE: if the section is just being copied then do that, otherwise grab * the user data for the section (stored previously by set_data) */ if(iscn->userdata == NULL) { buffer = malloc(size); if(!bfd_get_section_contents(ihandle, iscn, buffer, 0, size)) { printf("failed to get section contents: %s\n", bfd_errmsg(bfd_get_error())); free(buffer); return false; } } else buffer = iscn->userdata; if(!bfd_set_section_contents(ohandle, oscn, buffer, 0, size)) { printf("failed to set section contents: %s\n", bfd_errmsg(bfd_get_error())); free(buffer); return false; } free(buffer); } } if(!bfd_copy_private_bfd_data(ihandle, ohandle)) { printf("failed to copy private data: %s\n", bfd_errmsg(bfd_get_error())); return false; } return true; } /* * Perform a cross-device safe rename */ int safe_rename(const char *old, const char *new) { char buffer[1024]; FILE *in, *out; size_t read; int status_in; int status_out; char linkdest[PATH_MAX]; char linkdest_test[PATH_MAX]; int ret; int is_symlink = 0; ret = readlink(new, linkdest_test, PATH_MAX); while (ret >= 0) { // Symlink encountered is_symlink = 1; linkdest_test[ret] = 0; char* cwd = getcwd(NULL, 0); snprintf(linkdest, PATH_MAX, "%s/%s", cwd, linkdest_test); free(cwd); ret = readlink(linkdest, linkdest_test, PATH_MAX); } in = fopen(old, "r"); if(!in) { return -1; } // Avoid bus error if modifying a library we are currently using unlink((is_symlink)?linkdest:new); out = fopen((is_symlink)?linkdest:new, "w"); if(!out) { fclose(in); return -1; } while(!feof(in)) { read = fread(buffer, 1, sizeof(buffer), in); if (read > 0) { fwrite(buffer, 1, read, out); } if (ferror(in) || ferror(out)) { fclose(in); fclose(out); remove((is_symlink)?linkdest:new); return -1; } } status_in = ferror(in); status_out = ferror(out); fclose(in); fclose(out); if(status_in || status_out) { remove((is_symlink)?linkdest:new); return -1; } return remove(old); } /* * Write the output file using the libbfd method */ void write_output(libr_file *file_handle) { int write_ok = false; if(file_handle->bfd_write != NULL) { write_ok = true; if(!build_output(file_handle)) { printf("failed to build output file.\n"); write_ok = false; } if(!bfd_close(file_handle->bfd_write)) { printf("failed to close write handle.\n"); write_ok = false; } if(file_handle->fd_handle != 0 && close(file_handle->fd_handle)) { write_ok = false; printf("failed to close write file descriptor.\n"); } } /* The read handle must be closed last since it is used in the write process */ if(!bfd_close(file_handle->bfd_read)) printf("failed to close read handle.\n"); /* Copy the temporary output over the input */ if(write_ok) { if(rename(file_handle->tempfile, file_handle->filename) < 0) { if(errno != EXDEV || safe_rename(file_handle->tempfile, file_handle->filename) < 0) printf("failed to rename output file: %m\n"); } if(chmod(file_handle->filename, file_handle->filemode) < 0) printf("failed to set file mode.\n"); if(chown(file_handle->filename, file_handle->fileowner, file_handle->filegroup) < 0) printf("failed to set file ownership.\n"); } } /* * Find a named section from the ELF file using libbfd */ libr_intstatus find_section(libr_file *file_handle, char *section_name, libr_section **retscn) { libr_section *scn; for(scn = file_handle->bfd_read->sections; scn != NULL; scn = scn->next) { if(strcmp(scn->name, section_name) == 0) { *retscn = scn; RETURN_OK; } } RETURN(LIBR_ERROR_NOSECTION, "ELF resource section not found"); } /* * Obtain the data from a section using libbfd */ libr_data *get_data(libr_file *file_handle, libr_section *scn) { libr_data *data = malloc(scn->size); if(!bfd_get_section_contents(file_handle->bfd_read, scn, data, 0, scn->size)) { free(data); data = NULL; } scn->userdata = data; return data; } /* * Create new data for a section using libbfd */ libr_data *new_data(libr_file *file_handle, libr_section *scn) { /* NOTE: expanding data is handled by set_data for libbfd */ if(scn->userdata != NULL) return scn->userdata; scn->size = 0; scn->userdata = malloc(0); return scn->userdata; } /* * Create new data for a section using libbfd (at least, do so memory-wise) */ libr_intstatus set_data(libr_file *file_handle, libr_section *scn, libr_data *data, off_t offset, char *buffer, size_t size) { char *intbuffer = NULL; /* special case: clear buffer */ if(buffer == NULL) { scn->size = 0; if(scn->userdata != NULL) free(scn->userdata); RETURN_OK; } /* normal case: add new data to the buffer */ scn->size = offset + size; scn->userdata = realloc(data, scn->size); if(scn->userdata == NULL) RETURN(LIBR_ERROR_MEMALLOC, "Failed to allocate memory for data"); intbuffer = scn->userdata; memcpy(&intbuffer[offset], buffer, size); RETURN_OK; } /* * Create a new section using libbfd */ libr_intstatus add_section(libr_file *file_handle, char *resource_name, libr_section **retscn) { libr_section *scn = NULL; scn = bfd_make_section(file_handle->bfd_read, resource_name); if(scn == NULL) RETURN(LIBR_ERROR_NEWSECTION, "Failed to create new section"); if( #ifdef HAVE_BFD_2_34 !bfd_set_section_flags(scn, SEC_HAS_CONTENTS | SEC_DATA | SEC_IN_MEMORY) #else !bfd_set_section_flags(file_handle->bfd_read, scn, SEC_HAS_CONTENTS | SEC_DATA | SEC_IN_MEMORY) #endif ) { RETURN(LIBR_ERROR_SETFLAGS, "Failed to set flags for section"); } *retscn = scn; RETURN_OK; } /* * Remove a section and eliminate it from the ELF string table using libbfd */ libr_intstatus remove_section(libr_file *file_handle, libr_section *scn) { scn->size = 0; RETURN_OK; } /* * Return the pointer to the actual data in the section */ void *data_pointer(libr_section *scn, libr_data *data) { return data; } /* * Return the size of the data in the section */ size_t data_size(libr_section *scn, libr_data *data) { return scn->size; } /* * Return the next section in the ELF file */ libr_section *next_section(libr_file *file_handle, libr_section *scn) { /* get the first section */ if(scn == NULL) { if(file_handle->bfd_read == NULL) return NULL; return file_handle->bfd_read->sections; } return scn->next; } /* * Return the name of a section */ char *section_name(libr_file *file_handle, libr_section *scn) { return (char *) scn->name; } /* * Initialize libbfd */ void initialize_backend(void) { bfd_init(); }