/*************************************************************************** * Copyright (C) 2012 by Terraneo Federico * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU 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. * * * * As a special exception, if other files instantiate templates or use * * macros or inline functions from this file, or you compile this file * * and link it with other works to produce a work based on this file, * * this file does not by itself cause the resulting work to be covered * * by the GNU General Public License. However the source code for this * * file must still be made available in accordance with the GNU General * * Public License. This exception does not invalidate any other reasons * * why a work based on this file might be covered by the GNU General * * Public License. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see * ***************************************************************************/ #include "elf_program.h" #include "process_pool.h" #include #include #include using namespace std; #ifdef WITH_PROCESSES namespace miosix { ///\internal Enable/disable debugging of program loading //#define DBG iprintf #define DBG(x,...) do {} while(0) ///By convention, in an elf file for Miosix, the data segment starts @ this addr static const unsigned int DATA_BASE=0x40000000; // // class ElfProgram // ElfProgram::ElfProgram(const unsigned int *elf, unsigned int size) : elf(elf), size(size) { //Trying to follow the "full recognition before processing" approach, //(http://www.cs.dartmouth.edu/~sergey/langsec/occupy/FullRecognition.jpg) //all of the elf fields that will later be used are checked in advance. //Unused fields are unchecked, so when using new fields, add new checks if(validateHeader()==false) throw runtime_error("Bad file"); } bool ElfProgram::validateHeader() { //Validate ELF header //Note: this code assumes a little endian elf and a little endian ARM CPU if(isUnaligned8(getElfBase())) throw runtime_error("Elf file load address alignment error"); if(sizee_ident,magic,EI_NIDENT)) throw runtime_error("Unrecognized format"); if(ehdr->e_type!=ET_EXEC) throw runtime_error("Not an executable"); if(ehdr->e_machine!=EM_ARM) throw runtime_error("Wrong CPU arch"); if(ehdr->e_version!=EV_CURRENT) return false; if(ehdr->e_entry>=size) return false; if(ehdr->e_phoff>=size-sizeof(Elf32_Phdr)) return false; if(isUnaligned4(ehdr->e_phoff)) return false; // Old GCC 4.7.3 used to set bit 0x2 (EF_ARM_HASENTRY) but there's no trace // of this requirement in the current ELF spec for ARM. if((ehdr->e_flags & EF_ARM_EABIMASK) != EF_ARM_EABI_VER5) return false; #if !defined(__FPU_USED) || __FPU_USED==0 if(ehdr->e_flags & EF_ARM_VFP_FLOAT) throw runtime_error("FPU required"); #endif if(ehdr->e_ehsize!=sizeof(Elf32_Ehdr)) return false; if(ehdr->e_phentsize!=sizeof(Elf32_Phdr)) return false; //This to avoid that the next condition could pass due to 32bit wraparound //20 is an arbitrary number, could be increased if required if(ehdr->e_phnum>20) throw runtime_error("Too many segments"); if(ehdr->e_phoff+(ehdr->e_phnum*sizeof(Elf32_Phdr))>size) return false; //Validate program header table bool codeSegmentPresent=false; bool dataSegmentPresent=false; bool dynamicSegmentPresent=false; int dataSegmentSize=0; const Elf32_Phdr *phdr=getProgramHeaderTable(); for(int i=0;ip_offset>=size) return false; if(phdr->p_filesz>=size) return false; if(phdr->p_offset+phdr->p_filesz>size) return false; switch(phdr->p_align) { case 1: break; case 4: if(isUnaligned4(phdr->p_offset)) return false; break; case 8: if(isUnaligned8(phdr->p_offset)) return false; break; default: throw runtime_error("Unsupported segment alignment"); } switch(phdr->p_type) { case PT_LOAD: if(phdr->p_flags & ~(PF_R | PF_W | PF_X)) return false; if(!(phdr->p_flags & PF_R)) return false; if((phdr->p_flags & PF_W) && (phdr->p_flags & PF_X)) throw runtime_error("File violates W^X"); if(phdr->p_flags & PF_X) { if(codeSegmentPresent) return false; //Can't apper twice codeSegmentPresent=true; if(ehdr->e_entryp_offset || ehdr->e_entry>phdr->p_offset+phdr->p_filesz || phdr->p_filesz!=phdr->p_memsz) return false; } if((phdr->p_flags & PF_W) && !(phdr->p_flags & PF_X)) { if(dataSegmentPresent) return false; //Two data segments? dataSegmentPresent=true; if(phdr->p_memszp_filesz) return false; unsigned int maxSize=MAX_PROCESS_IMAGE_SIZE- MIN_PROCESS_STACK_SIZE; if(phdr->p_memsz>=maxSize) throw runtime_error("Data segment too big"); dataSegmentSize=phdr->p_memsz; } break; case PT_DYNAMIC: if(dynamicSegmentPresent) return false; //Two dynamic segments? dynamicSegmentPresent=true; //DYNAMIC segment *must* come after data segment if(dataSegmentPresent==false) return false; if(phdr->p_align<4) return false; if(validateDynamicSegment(phdr,dataSegmentSize)==false) return false; break; default: //Ignoring other segments break; } } if(codeSegmentPresent==false) return false; //Can't not have code segment return true; } bool ElfProgram::validateDynamicSegment(const Elf32_Phdr *dynamic, unsigned int dataSegmentSize) { unsigned int base=getElfBase(); const Elf32_Dyn *dyn=reinterpret_cast(base+dynamic->p_offset); const int dynSize=dynamic->p_memsz/sizeof(Elf32_Dyn); Elf32_Addr dtRel=0; Elf32_Word dtRelsz=0; unsigned int hasRelocs=0; bool miosixTagFound=false; unsigned int ramSize=0; unsigned int stackSize=0; for(int i=0;id_tag) { case DT_REL: hasRelocs |= 0x1; dtRel=dyn->d_un.d_ptr; break; case DT_RELSZ: hasRelocs |= 0x2; dtRelsz=dyn->d_un.d_val; break; case DT_RELENT: hasRelocs |= 0x4; if(dyn->d_un.d_val!=sizeof(Elf32_Rel)) return false; break; case DT_MX_ABI: if(dyn->d_un.d_val==DV_MX_ABI_V1) miosixTagFound=true; else throw runtime_error("Unknown/unsupported DT_MX_ABI"); break; case DT_MX_RAMSIZE: ramSize=dyn->d_un.d_val; break; case DT_MX_STACKSIZE: stackSize=dyn->d_un.d_val; break; case DT_RELA: case DT_RELASZ: case DT_RELAENT: throw runtime_error("RELA relocations unsupported"); default: //Ignore other entries break; } } if(miosixTagFound==false) throw runtime_error("Not a Miosix executable"); if(stackSizeMAX_PROCESS_IMAGE_SIZE) throw runtime_error("Requested image size is too large"); if((stackSize & 0x3) || (ramSize & 0x3) || (ramSize < ProcessPool::blockSize) || (stackSize>MAX_PROCESS_IMAGE_SIZE) || (dataSegmentSize>MAX_PROCESS_IMAGE_SIZE) || (dataSegmentSize+stackSize>ramSize)) throw runtime_error("Invalid stack or RAM size"); if(hasRelocs!=0 && hasRelocs!=0x7) return false; if(hasRelocs) { //The third condition does not imply the other due to 32bit wraparound if(dtRel>=size) return false; if(dtRelsz>=size) return false; if(dtRel+dtRelsz>size) return false; if(isUnaligned4(dtRel)) return false; const Elf32_Rel *rel=reinterpret_cast(base+dtRel); const int relSize=dtRelsz/sizeof(Elf32_Rel); for(int i=0;ir_info)) { case R_ARM_NONE: break; case R_ARM_RELATIVE: if(rel->r_offsetr_offset>DATA_BASE+dataSegmentSize-4) return false; if(rel->r_offset & 0x3) return false; break; default: throw runtime_error("Unexpected relocation type"); } } } return true; } // // class ProcessImage // void ProcessImage::load(const ElfProgram& program) { if(image) ProcessPool::instance().deallocate(image); const unsigned int base=program.getElfBase(); const Elf32_Phdr *phdr=program.getProgramHeaderTable(); const Elf32_Phdr *dataSegment=0; Elf32_Addr dtRel=0; Elf32_Word dtRelsz=0; bool hasRelocs=false; for(int i=0;ip_type) { case PT_LOAD: if((phdr->p_flags & PF_W) && !(phdr->p_flags & PF_X)) dataSegment=phdr; break; case PT_DYNAMIC: { const Elf32_Dyn *dyn=reinterpret_cast (base+phdr->p_offset); const int dynSize=phdr->p_memsz/sizeof(Elf32_Dyn); for(int i=0;id_tag) { case DT_REL: hasRelocs=true; dtRel=dyn->d_un.d_ptr; break; case DT_RELSZ: hasRelocs=true; dtRelsz=dyn->d_un.d_val; break; case DT_MX_RAMSIZE: size=dyn->d_un.d_val; image=ProcessPool::instance() .allocate(dyn->d_un.d_val); default: break; } } break; } default: //Ignoring other segments break; } } const char *dataSegmentInFile= reinterpret_cast(base+dataSegment->p_offset); char *dataSegmentInMem=reinterpret_cast(image); memcpy(dataSegmentInMem,dataSegmentInFile,dataSegment->p_filesz); dataSegmentInMem+=dataSegment->p_filesz; memset(dataSegmentInMem,0,dataSegment->p_memsz-dataSegment->p_filesz); if(hasRelocs) { const Elf32_Rel *rel=reinterpret_cast(base+dtRel); const int relSize=dtRelsz/sizeof(Elf32_Rel); const unsigned int ramBase=reinterpret_cast(image); DBG("Relocations -- start (code base @0x%x, data base @ 0x%x)\n",base,ramBase); for(int i=0;ir_offset-DATA_BASE)/4; switch(ELF32_R_TYPE(rel->r_info)) { case R_ARM_RELATIVE: if(image[offset]>=DATA_BASE) { DBG("R_ARM_RELATIVE offset 0x%x from 0x%x to 0x%x\n", offset*4,image[offset],image[offset]+ramBase-DATA_BASE); image[offset]+=ramBase-DATA_BASE; } else { DBG("R_ARM_RELATIVE offset 0x%x from 0x%x to 0x%x\n", offset*4,image[offset],image[offset]+base); image[offset]+=base; } break; default: break; } } DBG("Relocations -- end\n"); } } ProcessImage::~ProcessImage() { if(image) ProcessPool::instance().deallocate(image); } } //namespace miosix #endif //WITH_PROCESSES