Skip to content

Commit

Permalink
kldxref: Apply caprelocs in addition to normal ELF relocations
Browse files Browse the repository at this point in the history
Similar to ELF objects handled by libelf, define a "generic" structure
(Gcapreloc) and provide a routine in elf.c (elf_read_capreloc) which
reads the caprelocs section from an ELF file into an array of
Gcapreloc structures.

Recognize DT_RISCV_CHERI___CAPRELOCS* when parsing .dynamic of an ELF
DSO and use it to identify the caprelocs section.  This is the only
layering violation as there is not a portable DT_CAPRELOCS.

Add a new linker set of capreloc handlers and use the handler on any
relocated data after normal ELF relocations.

Note that caprelocs are only supported for RISC-V currently.
  • Loading branch information
bsdjhb committed Mar 12, 2024
1 parent 888e665 commit 4b51049
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 0 deletions.
50 changes: 50 additions & 0 deletions usr.sbin/kldxref/ef.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ struct ef_file {
long ef_relsz; /* number of entries */
GElf_Rela *ef_rela; /* relocation table */
long ef_relasz; /* number of entries */
Gcapreloc *ef_capreloc; /* capreloc table */
long ef_caprelocsz; /* number of entries */
};

static void ef_print_phdr(GElf_Phdr *);
Expand Down Expand Up @@ -227,8 +229,10 @@ ef_parse_dynamic(elf_file_t ef, const GElf_Phdr *phdyn)
GElf_Off hash_off, sym_off, str_off;
GElf_Off rel_off;
GElf_Off rela_off;
GElf_Off caprelocs_off;
int rel_sz;
int rela_sz;
int caprelocs_sz;
int dynamic_idx;

/*
Expand Down Expand Up @@ -269,6 +273,8 @@ ef_parse_dynamic(elf_file_t ef, const GElf_Phdr *phdyn)

hash_off = rel_off = rela_off = sym_off = str_off = 0;
rel_sz = rela_sz = 0;
caprelocs_off = 0;
caprelocs_sz = 0;
for (i = 0; i < ndyn; i++) {
dp = &dyn[i];
if (dp->d_tag == DT_NULL)
Expand Down Expand Up @@ -338,6 +344,23 @@ ef_parse_dynamic(elf_file_t ef, const GElf_Phdr *phdyn)
goto out;
}
break;
case DT_RISCV_CHERI___CAPRELOCS:
if (elf_machine(ef->ef_efile) != EM_RISCV)
break;
if (caprelocs_off != 0)
warnx("second DT_RISCV_CHERI___CAPRELOCS entry ignored");
else
caprelocs_off = ef_get_offset(ef,
dp->d_un.d_ptr);
break;
case DT_RISCV_CHERI___CAPRELOCSSZ:
if (elf_machine(ef->ef_efile) != EM_RISCV)
break;
if (caprelocs_sz != 0)
warnx("second DT_RISCV_CHERI___CAPRELOCSSZ entry ignored");
else
caprelocs_sz = dp->d_un.d_val;
break;
}
}
if (hash_off == 0) {
Expand Down Expand Up @@ -494,6 +517,25 @@ ef_parse_dynamic(elf_file_t ef, const GElf_Phdr *phdyn)
goto out;
}

if (caprelocs_off != 0) {
if (caprelocs_sz == 0) {
warnx("%s: no DT_CAPRELOCSSZ for DT_CAPRELOCS",
ef->ef_name);
error = EFTYPE;
goto out;
}
error = elf_read_capreloc(ef->ef_efile, caprelocs_off,
caprelocs_sz, &ef->ef_caprelocsz, &ef->ef_capreloc);
if (error != 0) {
warnx("%s: cannot load DT_CAPRELOCS section",
ef->ef_name);
goto out;
}
if (ef->ef_verbose)
warnx("%s: %ld CAPRELOC entries", ef->ef_name,
ef->ef_caprelocsz);
}

error = 0;
out:
free(dyn);
Expand All @@ -507,6 +549,7 @@ ef_seg_read_rel(elf_file_t ef, GElf_Addr address, size_t len, void *dest)
GElf_Off ofs;
const GElf_Rela *a;
const GElf_Rel *r;
const Gcapreloc *cr;
int error;

ofs = ef_get_offset(ef, address);
Expand All @@ -532,6 +575,12 @@ ef_seg_read_rel(elf_file_t ef, GElf_Addr address, size_t len, void *dest)
if (error != 0)
return (error);
}
for (cr = ef->ef_capreloc; cr < &ef->ef_capreloc[ef->ef_caprelocsz];
cr++) {
error = elf_capreloc(ef->ef_efile, cr, 0, address, len, dest);
if (error != 0)
return (error);
}
return (0);
}

Expand Down Expand Up @@ -636,6 +685,7 @@ ef_open(struct elf_file *efile, int verbose)
static void
ef_close(elf_file_t ef)
{
free(ef->ef_capreloc);
free(ef->ef_rela);
free(ef->ef_rel);
free(ef->ef_strtab);
Expand Down
40 changes: 40 additions & 0 deletions usr.sbin/kldxref/ef.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ typedef struct ef_file *elf_file_t;
typedef Elf64_Size GElf_Size;
typedef Elf64_Hashelt GElf_Hashelt;

/* libelf doesn't know about struct capreloc */
typedef struct {
GElf_Addr capability_location;
GElf_Size object;
GElf_Size offset;
GElf_Size size;
GElf_Size permissions;
} Gcapreloc;

struct elf_file;

struct elf_file_ops {
Expand All @@ -73,13 +82,23 @@ typedef int (elf_reloc_t)(struct elf_file *ef, const void *reldata,
Elf_Type reltype, GElf_Addr relbase, GElf_Addr dataoff, size_t len,
void *dest);

typedef int (elf_capreloc_t)(struct elf_file *ef, const Gcapreloc *cr,
GElf_Addr relbase, GElf_Addr dataoff, size_t len, void *dest);

struct elf_reloc_data {
unsigned char class;
unsigned char data;
GElf_Half machine;
elf_reloc_t *reloc;
};

struct elf_capreloc_data {
unsigned char class;
unsigned char data;
GElf_Half machine;
elf_capreloc_t *capreloc;
};

#define ELF_RELOC(_class, _data, _machine, _reloc) \
static struct elf_reloc_data __CONCAT(elf_reloc_data_, __LINE__) = { \
.class = (_class), \
Expand All @@ -89,12 +108,22 @@ struct elf_reloc_data {
}; \
DATA_SET(elf_reloc, __CONCAT(elf_reloc_data_, __LINE__))

#define ELF_CAPRELOC(_class, _data, _machine, _capreloc) \
static struct elf_capreloc_data __CONCAT(elf_capreloc_data_, __LINE__) = { \
.class = (_class), \
.data = (_data), \
.machine = (_machine), \
.capreloc = (_capreloc) \
}; \
DATA_SET(elf_capreloc, __CONCAT(elf_capreloc_data_, __LINE__))

struct elf_file {
elf_file_t ef_ef;
struct elf_file_ops *ef_ops;
const char *ef_filename;
Elf *ef_elf;
elf_reloc_t *ef_reloc;
elf_capreloc_t *ef_capreloc;
GElf_Ehdr ef_hdr;
size_t ef_pointer_size;
int ef_fd;
Expand All @@ -103,6 +132,7 @@ struct elf_file {

#define elf_class(ef) ((ef)->ef_hdr.e_ident[EI_CLASS])
#define elf_encoding(ef) ((ef)->ef_hdr.e_ident[EI_DATA])
#define elf_machine(ef) ((ef)->ef_hdr.e_machine)

/*
* "Generic" versions of module metadata structures.
Expand Down Expand Up @@ -250,6 +280,13 @@ int elf_read_rel(struct elf_file *efile, int section_index, long *nrelp,
int elf_read_rela(struct elf_file *efile, int section_index, long *nrelap,
GElf_Rela **relap);

/*
* Read a table of capreloc objects from an offset in an ELF file into
* a dynamically-allocated array of Gcapreloc objects.
*/
int elf_read_capreloc(struct elf_file *efile, off_t offset, size_t len,
long *ncaprelocp, Gcapreloc **caprelocp);

/*
* Read a string from an ELF file and return it in the provided
* buffer. If the string is longer than the buffer, this fails with
Expand Down Expand Up @@ -306,6 +343,9 @@ int elf_read_mod_pnp_match_info(struct elf_file *efile, GElf_Addr addr,
int elf_reloc(struct elf_file *ef, const void *reldata, Elf_Type reltype,
GElf_Addr relbase, GElf_Addr dataoff, size_t len, void *dest);

int elf_capreloc(struct elf_file *ef, const Gcapreloc *cr,
GElf_Addr relbase, GElf_Addr dataoff, size_t len, void *dest);

__END_DECLS

#endif /* _EF_H_*/
17 changes: 17 additions & 0 deletions usr.sbin/kldxref/ef_riscv.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,20 @@ ef_riscv_reloc(struct elf_file *ef, const void *reldata, Elf_Type reltype,
}

ELF_RELOC(ELFCLASS64, ELFDATA2LSB, EM_RISCV, ef_riscv_reloc);

int
ef_riscv_capreloc(struct elf_file *ef, const Gcapreloc *cr,
GElf_Addr relbase, GElf_Addr dataoff, size_t len, void *dest)
{
char *where;

where = (char *)dest + relbase + cr->capability_location - dataoff;

if (where < (char *)dest || where >= (char *)dest + len)
return (0);

le64enc(where, relbase + cr->object + cr->offset);
return (0);
}

ELF_CAPRELOC(ELFCLASS64, ELFDATA2LSB, EM_RISCV, ef_riscv_capreloc);
92 changes: 92 additions & 0 deletions usr.sbin/kldxref/elf.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include "ef.h"

SET_DECLARE(elf_reloc, struct elf_reloc_data);
SET_DECLARE(elf_capreloc, struct elf_capreloc_data);

static elf_reloc_t *
elf_find_reloc(const GElf_Ehdr *hdr)
Expand All @@ -63,6 +64,20 @@ elf_find_reloc(const GElf_Ehdr *hdr)
return (NULL);
}

static elf_capreloc_t *
elf_find_capreloc(const GElf_Ehdr *hdr)
{
struct elf_capreloc_data **ecd;

SET_FOREACH(ecd, elf_capreloc) {
if (hdr->e_ident[EI_CLASS] == (*ecd)->class &&
hdr->e_ident[EI_DATA] == (*ecd)->data &&
hdr->e_machine == (*ecd)->machine)
return ((*ecd)->capreloc);
}
return (NULL);
}

static bool
elf_is_cheriabi(const GElf_Ehdr *hdr)
{
Expand Down Expand Up @@ -118,6 +133,7 @@ elf_open_file(struct elf_file *efile, const char *filename, int verbose)
elf_close_file(efile);
return (EFTYPE);
}
efile->ef_capreloc = elf_find_capreloc(&efile->ef_hdr);

error = ef_open(efile, verbose);
if (error != 0) {
Expand Down Expand Up @@ -506,6 +522,73 @@ elf_read_rela(struct elf_file *efile, int section_index, long *nrelap,
return (0);
}

int
elf_read_capreloc(struct elf_file *efile, off_t offset, size_t len,
long *ncaprelocp, Gcapreloc **caprelocp)
{
void *data;
Gcapreloc *capreloc;
long i, ncapreloc;
size_t capsize;
int error;

/*
* NB: A capreloc is effectively a struct of 5 ELF_T_ADDR
* objects.
*/
capsize = 5 * elf_object_size(efile, ELF_T_ADDR);
if (len % capsize != 0)
return (EINVAL);

error = elf_read_data(efile, ELF_T_ADDR, offset, len, &data);
if (error != 0)
return (error);

ncapreloc = len / capsize;
capreloc = calloc(ncapreloc, sizeof(*capreloc));
if (capreloc == NULL) {
free(data);
return (ENOMEM);
}

switch (elf_class(efile)) {
case ELFCLASS32:
{
Elf32_Addr *buf = data;

for (i = 0; i < ncapreloc; i++) {
capreloc[i].capability_location = buf[0];
capreloc[i].object = buf[1];
capreloc[i].offset = buf[2];
capreloc[i].size = buf[3];
capreloc[i].permissions = buf[4];
buf += 5;
}
break;
}
case ELFCLASS64:
{
Elf64_Addr *buf = data;

for (i = 0; i < ncapreloc; i++) {
capreloc[i].capability_location = buf[0];
capreloc[i].object = buf[1];
capreloc[i].offset = buf[2];
capreloc[i].size = buf[3];
capreloc[i].permissions = buf[4];
buf += 5;
}
break;
}
default:
__builtin_unreachable();
}

*ncaprelocp = ncapreloc;
*caprelocp = capreloc;
return (0);
}

size_t
elf_pointer_size(struct elf_file *efile)
{
Expand Down Expand Up @@ -696,3 +779,12 @@ elf_reloc(struct elf_file *efile, const void *reldata, Elf_Type reltype,
return (efile->ef_reloc(efile, reldata, reltype, relbase, dataoff, len,
dest));
}

int
elf_capreloc(struct elf_file *efile, const Gcapreloc *capreloc,
GElf_Addr relbase, GElf_Addr dataoff, size_t len, void *dest)
{
assert (efile->ef_capreloc != NULL);
return (efile->ef_capreloc(efile, capreloc, relbase, dataoff, len,
dest));
}

0 comments on commit 4b51049

Please sign in to comment.