Skip to content

Commit

Permalink
Add small ELF parser
Browse files Browse the repository at this point in the history
This small ELF parser contains just the bare minimum functions so we
can load ELF sections without the use of any external library.  This
module also doesn't allocate dynamic memory.

Signed-off-by: Giuliano Belinassi <[email protected]>
  • Loading branch information
giulianobelinassi committed Nov 11, 2024
1 parent 529b432 commit 2d81243
Show file tree
Hide file tree
Showing 4 changed files with 321 additions and 2 deletions.
3 changes: 2 additions & 1 deletion include/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ noinst_HEADERS = \
terminal_colors.h \
ld_rtld.h \
insn_queue.h \
insn_queue_lib.h
insn_queue_lib.h \
minielf.h

# Workaround a bug in Autoconf 2.69
if CPU_X86_64
Expand Down
69 changes: 69 additions & 0 deletions include/minielf.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* libpulp - User-space Livepatching Library
*
* Copyright (C) 2024 SUSE Software Solutions GmbH
*
* This file is part of libpulp.
*
* libpulp 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.
*
* libpulp 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 General Public License
* along with libpulp. If not, see <http://www.gnu.org/licenses/>.
*/

/* Small parser for elf, not depending on larger libraries such as libelf
* and with very small memory footprint. No allocation on the heap is done
* in this library.
*/

#include <elf.h>

/** Maximum size of the Section String Table. */
#define STRTBL_SIZE_MAX 0x500

/** Typedefs so we can adjust according to architecture. */
typedef Elf64_Ehdr Elf_Ehdr;
typedef Elf64_Shdr Elf_Shdr;
typedef Elf64_Half Elf_Half;
typedef Elf64_Off Elf_Off;

/** Read the Elf File Header. */
Elf_Ehdr *Elf_Parse_Ehdr(Elf_Ehdr *ehdr, int fd);

/** Get an ELF section by its name.
*
* OBS: use `readelf -S <file>` to show section names.
*/
Elf_Shdr *Elf_Get_Shdr_By_Name(Elf_Shdr *shdr, const char *name, int fd,
const Elf_Ehdr *ehdr, const char strtbl[]);


/** Get an ELF section by its index.
*
* OBS: use `readelf -S <file>` to show section index [<idx>].
*/
Elf_Shdr *Elf_Get_Shdr(Elf_Shdr *shdr, Elf_Half index,
int fd, const Elf_Ehdr *ehdr);

/** Get the section string table. */
long Elf_Load_Strtbl(char strtbl[STRTBL_SIZE_MAX], const Elf_Ehdr *ehdr, int fd);

/** Load Section into `dest` buffer. */
long Elf_Load_Section(unsigned dest_size, unsigned char *dest,
const Elf_Shdr *shdr, int fd);

/** ----- ELF functions related to ULP ----- . */

/** Get the .ulp section from the given ELF file. */
int Get_ULP_Section(unsigned dest_size, unsigned char *dest, const char *file);

/** Get the .ulp.rev section from the given ELF file. */
int Get_ULP_REV_Section(unsigned dest_size, unsigned char *dest, const char *file);
3 changes: 2 additions & 1 deletion lib/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ libpulp_la_SOURCES = \
interpose.c \
msg_queue.c \
insn_queue.c \
error.c
error.c \
minielf.c

libpulp_la_LDFLAGS = \
-ldl \
Expand Down
248 changes: 248 additions & 0 deletions lib/minielf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
/*
* libpulp - User-space Livepatching Library
*
* Copyright (C) 2024 SUSE Software Solutions GmbH
*
* This file is part of libpulp.
*
* libpulp 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.
*
* libpulp 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 General Public License
* along with libpulp. If not, see <http://www.gnu.org/licenses/>.
*/

/* Small parser for elf, not depending on larger libraries such as libelf
* and with very small memory footprint. No allocation on the heap is done
* in this library.
*/

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#include "error.h"
#include "minielf.h"
#include "ulp_common.h"

#define debug(...) DEBUG(__VA_ARGS__)
#define warn(...) WARN(__VA_ARGS__)

/** Ban memory allocation functions. This module should never allocate memory
in the heap. */
#pragma GCC poison malloc calloc free

/** Read the Elf File Header. */
Elf_Ehdr *Elf_Parse_Ehdr(Elf_Ehdr *ehdr, int fd)
{
/* Go to begining of file. */
lseek(fd, 0L, SEEK_SET);

ssize_t n = read(fd, ehdr, sizeof(Elf_Ehdr));

if (n != sizeof(Elf_Ehdr)) {
warn("Invalid ELF file: invalid size");
return NULL;
}

/* Check if the header makes sense. */
if (memcmp(&ehdr->e_ident, ELFMAG, SELFMAG) != 0) {
warn("Invalid ELF file: invalid magic number");
return NULL;
}

return ehdr;
}

/** Get an ELF section by its name.
*
* OBS: use `readelf -S <file>` to show section names.
*/
Elf_Shdr *Elf_Get_Shdr_By_Name(Elf_Shdr *shdr, const char *name, int fd,
const Elf_Ehdr *ehdr, const char strtbl[])
{
/* Make sure it will fit the buffer. */
libpulp_assert(ehdr->e_shentsize == sizeof(Elf_Shdr));

/* Go to offset. */
lseek(fd, ehdr->e_shoff, SEEK_SET);

for (Elf_Half i = 0; i < ehdr->e_shnum; i++) {
/* Load the header. */
ssize_t n = read(fd, shdr, ehdr->e_shentsize);

if (n != ehdr->e_shentsize) {
warn("Invalid ELF file: invalid size\n");
return NULL;
}

/* Check if name matches. */
if (strcmp(name, &strtbl[shdr->sh_name]) == 0) {
return shdr;
}
}

return NULL;
}

/** Get an ELF section by its index.
*
* OBS: use `readelf -S <file>` to show section index [<idx>].
*/
Elf_Shdr *Elf_Get_Shdr(Elf_Shdr *shdr, Elf_Half index,
int fd, const Elf_Ehdr *ehdr)
{
/* Make sure it will fit the buffer. */
libpulp_assert(ehdr->e_shentsize == sizeof(Elf_Shdr));

/* Go to offset. */
lseek(fd, ehdr->e_shoff, SEEK_SET);

for (Elf_Half i = 0; i < ehdr->e_shnum; i++) {
/* Load the header. */
ssize_t n = read(fd, shdr, ehdr->e_shentsize);

if (n != ehdr->e_shentsize) {
warn("Invalid ELF file: invalid size");
return NULL;
}

/* Check if index matches. */
if (index == i) {
return shdr;
}
}

return NULL;
}

/** Get the section string table. */
long Elf_Load_Strtbl(char strtbl[STRTBL_SIZE_MAX], const Elf_Ehdr *ehdr, int fd)
{
/* Make sure it will fit the buffer. */
libpulp_assert(ehdr->e_shentsize == sizeof(Elf_Shdr));

/* Declare a section for us to store and iterate. */
Elf_Shdr shdr;
Elf_Shdr *p_shdr = Elf_Get_Shdr(&shdr, ehdr->e_shstrndx, fd, ehdr);

/* Make sure we are in the correct section. */
if (p_shdr == NULL) {
warn("Invalid ELF file: no section string table");
return 0L;
}

if (shdr.sh_size > STRTBL_SIZE_MAX) {
warn("Unable to load section string table: size larger than buffer");
return 0L;
}

/* Go to offset. */
lseek(fd, shdr.sh_offset, SEEK_SET);

/* Load the strtbl. */
ssize_t n = read(fd, strtbl, shdr.sh_size);

if ((size_t)n != shdr.sh_size) {
warn("Unable to load section string table: file size mismatch.\n");
return 0L;
}

return shdr.sh_size;
}

/** Load Section into `dest` buffer. */
long Elf_Load_Section(unsigned dest_size, unsigned char *dest,
const Elf_Shdr *shdr, int fd)
{
/* Check if dest can hold the section. */
if (shdr->sh_size > dest_size) {
warn("Unable to load section: buffer too small");
return 0L;
}

/* Go to offset. */
lseek(fd, shdr->sh_offset, SEEK_SET);

/* Load the section. */
ssize_t n = read(fd, dest, shdr->sh_size);

if ((size_t)n != shdr->sh_size) {
warn("Unable to load section string table: read size mismatch");
return 0L;
}

return shdr->sh_size;
}

/** Load the .ulp section of livepatch of `file` into the buffer. */
static int
Get_Elf_Section(unsigned dest_size, unsigned char *dest,
const char *section_name, const char *file)
{
/* Open ELF file. */
int elf_fd = open(file, O_RDONLY);
if (elf_fd < 0) {
warn("Unable to open file %s: %s", file, strerror(errno));
return ENOENT;
}

/* Load ELF file header. */
Elf_Ehdr ehdr;
Elf_Ehdr *p_ehdr = Elf_Parse_Ehdr(&ehdr, elf_fd);
if (p_ehdr == NULL) {
warn("File is not an ELF object.");

close(elf_fd);
return EINVAL;
}

/* Load ELF section string table. */
char strtbl[STRTBL_SIZE_MAX];
Elf_Load_Strtbl(strtbl, &ehdr, elf_fd);

/* Load section header from the ELF file. */
Elf_Shdr ulp_shdr;
Elf_Shdr *p_ulp_shdr = Elf_Get_Shdr_By_Name(&ulp_shdr, section_name,
elf_fd, &ehdr, strtbl);

if (p_ulp_shdr == NULL) {
warn("Section %s not found.", section_name);

close(elf_fd);
return EINVAL;
}

/* Load ELF section into dest. */
long x = Elf_Load_Section(dest_size, dest, &ulp_shdr, elf_fd);
if (x == 0) {
close(elf_fd);
return EINVAL;
}

close(elf_fd);
return 0;
}

int
Get_ULP_Section(unsigned dest_size, unsigned char *dest, const char *file)
{
return Get_Elf_Section(dest_size, dest, ".ulp", file);
}

int
Get_ULP_REV_Section(unsigned dest_size, unsigned char *dest, const char *file)
{
return Get_Elf_Section(dest_size, dest, ".ulp.rev", file);
}

0 comments on commit 2d81243

Please sign in to comment.