rpm/libelf/update.c

415 lines
10 KiB
C
Executable File

/*
update.c - implementation of the elf_update(3) function.
Copyright (C) 1995 Michael Riepe <riepe@ifwsn4.ifw.uni-hannover.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <private.h>
static const unsigned short __encoding = ELFDATA2LSB + (ELFDATA2MSB << 8);
#define native_encoding (*(unsigned char*)&__encoding)
#define rewrite(var,val,f) \
do{if((var)!=(val)){(var)=(val);(f)|=ELF_F_DIRTY;}}while(0)
#define align(var,val) \
do{if((val)>1){(var)+=(val)-1;(var)-=(var)%(val);}}while(0)
#define max(a,b) ((a)>(b)?(a):(b))
static off_t
_elf32_layout(Elf *elf, unsigned *flag) {
int layout = (elf->e_elf_flags & ELF_F_LAYOUT) == 0;
Elf32_Ehdr *ehdr = (Elf32_Ehdr*)elf->e_ehdr;
size_t off = 0;
unsigned version;
unsigned encoding;
size_t entsize;
unsigned shnum;
Elf_Scn *scn;
*flag = elf->e_elf_flags | elf->e_phdr_flags;
if ((version = ehdr->e_version) == EV_NONE) {
version = EV_CURRENT;
}
if (!valid_version(version)) {
seterr(ERROR_UNKNOWN_VERSION);
return -1;
}
if ((encoding = ehdr->e_ident[EI_DATA]) == ELFDATANONE) {
encoding = native_encoding;
}
if (!valid_encoding(encoding)) {
seterr(ERROR_UNKNOWN_ENCODING);
return -1;
}
entsize = _fsize32(version, ELF_T_EHDR); elf_assert(entsize);
rewrite(ehdr->e_ehsize, entsize, elf->e_ehdr_flags);
off = entsize;
rewrite(ehdr->e_phnum, elf->e_phnum, elf->e_ehdr_flags);
if (elf->e_phnum) {
entsize = _fsize32(version, ELF_T_PHDR); elf_assert(entsize);
rewrite(ehdr->e_phentsize, entsize, elf->e_ehdr_flags);
if (layout) {
align(off, 4);
rewrite(ehdr->e_phoff, off, elf->e_ehdr_flags);
off += elf->e_phnum * entsize;
}
else {
off = max(off, ehdr->e_phoff + elf->e_phnum * entsize);
}
}
else {
rewrite(ehdr->e_phentsize, 0, elf->e_ehdr_flags);
if (layout) {
rewrite(ehdr->e_phoff, 0, elf->e_ehdr_flags);
}
}
for (scn = elf->e_scn_1, shnum = 0; scn; scn = scn->s_link, ++shnum) {
Elf32_Shdr *shdr = (Elf32_Shdr*)scn->s_shdr;
size_t scn_align = 1;
size_t len = 0;
Scn_Data *sd;
elf_assert(shdr);
*flag |= scn->s_scn_flags | scn->s_shdr_flags;
if (scn->s_index != shnum) {
seterr(ERROR_SCNLIST_BROKEN);
return -1;
}
if (scn->s_index == SHN_UNDEF) {
rewrite(shdr->sh_entsize, 0, scn->s_shdr_flags);
if (layout) {
rewrite(shdr->sh_offset, 0, scn->s_shdr_flags);
rewrite(shdr->sh_size, 0, scn->s_shdr_flags);
rewrite(shdr->sh_addralign, 0, scn->s_shdr_flags);
}
continue;
}
#if 0
if (shdr->sh_type == SHT_NULL) {
continue;
}
#endif
for (sd = scn->s_data_1; sd; sd = sd->sd_link) {
size_t fsize, msize;
if (shdr->sh_type == SHT_NOBITS) {
fsize = sd->sd_data.d_size;
}
else if (!valid_type(sd->sd_data.d_type)) {
/* can't translate */
fsize = sd->sd_data.d_size;
}
else {
msize = _msize32(sd->sd_data.d_version, sd->sd_data.d_type);
elf_assert(msize);
fsize = _fsize32(version, sd->sd_data.d_type);
elf_assert(fsize);
fsize = (sd->sd_data.d_size / msize) * fsize;
}
if (layout) {
align(len, sd->sd_data.d_align);
scn_align = max(scn_align, sd->sd_data.d_align);
rewrite(sd->sd_data.d_off, (off_t)len, sd->sd_data_flags);
len += fsize;
}
else {
len = max(len, sd->sd_data.d_off + fsize);
}
*flag |= sd->sd_data_flags;
}
if (valid_scntype(shdr->sh_type)) {
Elf_Type type = _elf_scn_types[shdr->sh_type];
size_t fsize;
elf_assert(valid_type(type));
if (type != ELF_T_BYTE) {
fsize = _fsize32(version, type);
elf_assert(fsize);
rewrite(shdr->sh_entsize, fsize, scn->s_shdr_flags);
}
}
if (layout) {
align(off, scn_align);
rewrite(shdr->sh_offset, off, scn->s_shdr_flags);
rewrite(shdr->sh_size, len, scn->s_shdr_flags);
rewrite(shdr->sh_addralign, scn_align, scn->s_shdr_flags);
if (shdr->sh_type != SHT_NOBITS) {
off += len;
}
}
else if (len > shdr->sh_size) {
seterr(ERROR_SCN2SMALL);
return -1;
}
else if (shdr->sh_type != SHT_NOBITS) {
off = max(off, shdr->sh_offset + shdr->sh_size);
}
else {
off = max(off, shdr->sh_offset);
}
}
rewrite(ehdr->e_shnum, shnum, elf->e_ehdr_flags);
if (shnum) {
entsize = _fsize32(version, ELF_T_SHDR); elf_assert(entsize);
rewrite(ehdr->e_shentsize, entsize, elf->e_ehdr_flags);
if (layout) {
align(off, 4);
rewrite(ehdr->e_shoff, off, elf->e_ehdr_flags);
off += shnum * entsize;
}
else {
off = max(off, ehdr->e_shoff + shnum * entsize);
}
}
else {
rewrite(ehdr->e_shentsize, 0, elf->e_ehdr_flags);
if (layout) {
rewrite(ehdr->e_shoff, 0, elf->e_ehdr_flags);
}
}
rewrite(ehdr->e_ident[EI_MAG0], ELFMAG0, elf->e_ehdr_flags);
rewrite(ehdr->e_ident[EI_MAG1], ELFMAG1, elf->e_ehdr_flags);
rewrite(ehdr->e_ident[EI_MAG2], ELFMAG2, elf->e_ehdr_flags);
rewrite(ehdr->e_ident[EI_MAG3], ELFMAG3, elf->e_ehdr_flags);
rewrite(ehdr->e_ident[EI_CLASS], ELFCLASS32, elf->e_ehdr_flags);
rewrite(ehdr->e_ident[EI_DATA], encoding, elf->e_ehdr_flags);
rewrite(ehdr->e_ident[EI_VERSION], version, elf->e_ehdr_flags);
rewrite(ehdr->e_version, version, elf->e_ehdr_flags);
*flag |= elf->e_ehdr_flags;
return off;
}
static off_t
_elf32_write(Elf *elf, size_t len) {
Elf32_Ehdr *ehdr;
Elf32_Shdr *shdr;
Elf_Scn *scn;
Scn_Data *sd;
Elf_Data src;
Elf_Data dst;
unsigned encode;
size_t fsize;
size_t msize;
char *outbuf;
if (!len) {
return len;
}
if (!(outbuf = (char*)malloc(len))) {
seterr(ERROR_MEM_OUTBUF);
return -1;
}
memset(outbuf, _elf_fill, len);
ehdr = (Elf32_Ehdr*)elf->e_ehdr; elf_assert(ehdr);
encode = ehdr->e_ident[EI_DATA];
src.d_buf = ehdr;
src.d_type = ELF_T_EHDR;
src.d_size = _msize32(_elf_version, ELF_T_EHDR);
src.d_version = _elf_version;
dst.d_buf = outbuf;
dst.d_size = ehdr->e_ehsize;
dst.d_version = ehdr->e_version;
if (!elf32_xlatetof(&dst, &src, encode)) {
free(outbuf);
return -1;
}
if (ehdr->e_phnum) {
src.d_buf = elf->e_phdr;
src.d_type = ELF_T_PHDR;
src.d_size = ehdr->e_phnum * _msize32(_elf_version, ELF_T_PHDR);
src.d_version = _elf_version;
dst.d_buf = outbuf + ehdr->e_phoff;
dst.d_size = ehdr->e_phnum * ehdr->e_phentsize;
dst.d_version = ehdr->e_version;
if (!elf32_xlatetof(&dst, &src, encode)) {
free(outbuf);
return -1;
}
}
for (scn = elf->e_scn_1; scn; scn = scn->s_link) {
shdr = (Elf32_Shdr*)scn->s_shdr; elf_assert(shdr);
src.d_buf = shdr;
src.d_type = ELF_T_SHDR;
src.d_size = sizeof(*shdr);
src.d_version = EV_CURRENT;
dst.d_buf = outbuf + ehdr->e_shoff + scn->s_index * ehdr->e_shentsize;
dst.d_size = ehdr->e_shentsize;
dst.d_version = ehdr->e_version;
if (!elf32_xlatetof(&dst, &src, encode)) {
free(outbuf);
return -1;
}
if (scn->s_index == SHN_UNDEF || shdr->sh_type == SHT_NOBITS) {
continue;
}
sd = NULL;
while ((sd = (Scn_Data*)elf_getdata(scn, (Elf_Data*)sd))) {
src = sd->sd_data;
if (!src.d_size) {
continue;
}
elf_assert(src.d_buf);
dst.d_buf = outbuf + shdr->sh_offset + src.d_off;
dst.d_size = src.d_size;
dst.d_version = ehdr->e_version;
if (valid_type(src.d_type)) {
msize = _msize32(src.d_version, src.d_type);
elf_assert(msize);
fsize = _fsize32(dst.d_version, src.d_type);
elf_assert(fsize);
dst.d_size = (dst.d_size / msize) * fsize;
}
else {
src.d_type = ELF_T_BYTE;
}
if (!elf32_xlatetof(&dst, &src, encode)) {
free(outbuf);
return -1;
}
}
}
if (lseek(elf->e_fd, 0L, 0) == -1L) {
seterr(ERROR_IO_SEEK);
}
else if (write(elf->e_fd, outbuf, len) != (int)len) {
seterr(ERROR_IO_WRITE);
}
#if HAVE_FTRUNCATE
else if (ftruncate(elf->e_fd, len) == -1) {
seterr(ERROR_IO_TRUNC);
}
#endif
else {
elf->e_elf_flags &= ~ELF_F_DIRTY;
elf->e_ehdr_flags &= ~ELF_F_DIRTY;
elf->e_phdr_flags &= ~ELF_F_DIRTY;
for (scn = elf->e_scn_1; scn; scn = scn->s_link) {
scn->s_scn_flags &= ~ELF_F_DIRTY;
scn->s_shdr_flags &= ~ELF_F_DIRTY;
for (sd = scn->s_data_1; sd; sd = sd->sd_link) {
sd->sd_data_flags &= ~ELF_F_DIRTY;
}
if (elf->e_readable) {
shdr = (Elf32_Shdr*)scn->s_shdr;
elf_assert(shdr);
scn->s_type = shdr->sh_type;
scn->s_size = shdr->sh_size;
scn->s_offset = shdr->sh_offset;
}
}
if (elf->e_readable) {
elf_assert(elf->e_data);
elf_assert(!elf->e_parent);
elf_assert(len >= EI_NIDENT);
if (elf->e_rawdata) {
if (elf->e_data == elf->e_rawdata) {
elf->e_data = outbuf;
}
free(elf->e_rawdata);
elf->e_rawdata = outbuf;
}
else if (!elf->e_cooked) {
free(elf->e_data);
elf->e_data = outbuf;
}
else {
free(outbuf);
}
elf->e_size = len;
elf->e_class = ehdr->e_ident[EI_CLASS];
elf->e_encoding = ehdr->e_ident[EI_DATA];
elf->e_version = ehdr->e_ident[EI_VERSION];
}
else {
free(outbuf);
}
return len;
}
free(outbuf);
return -1;
}
off_t
elf_update(Elf *elf, Elf_Cmd cmd) {
unsigned flag;
off_t len;
if (!elf) {
return -1;
}
elf_assert(elf->e_magic == ELF_MAGIC);
if (cmd == ELF_C_WRITE) {
if (!elf->e_writable) {
seterr(ERROR_RDONLY);
return -1;
}
if (elf->e_disabled) {
seterr(ERROR_FDDISABLED);
return -1;
}
}
else if (cmd != ELF_C_NULL) {
seterr(ERROR_INVALID_CMD);
return -1;
}
if (!elf->e_ehdr) {
seterr(ERROR_NOEHDR);
}
else if (elf->e_kind != ELF_K_ELF) {
seterr(ERROR_NOTELF);
}
else if (elf->e_class == ELFCLASS32) {
len = _elf32_layout(elf, &flag);
if (len != -1 && cmd == ELF_C_WRITE && (flag & ELF_F_DIRTY)) {
len = _elf32_write(elf, len);
}
return len;
}
else if (valid_class(elf->e_class)) {
seterr(ERROR_UNIMPLEMENTED);
}
else {
seterr(ERROR_UNKNOWN_CLASS);
}
return -1;
}