kpatch/kpatch-build/create-kpatch-module.c
Evgenii Shatokhin 9bb75659e2 kpatch-build: Detect R_X86_64_64 dynrelas with large addends
Or, to be exact, with addend values which cannot be represented by
a signed int variable.

This only applies to the old KPatch core.

Commit 15067fcd "kmod/core: apply dynrela addend for R_X86_64_64" fixed
calculation of the values for R_X86_64_64 dynrelas. This revealed
another issue, similar to https://github.com/dynup/kpatch/issues/1064.

Dynrelas are stored as 'struct kpatch_patch_dynrela' instances in the
patch module but both the patch module and kpatch.ko use
'struct kpatch_dynrela' to work with the dynrelas. 'addend' has type
'long' in kpatch_patch_dynrela but 'int' in kpatch_dynrela, so this
value can be truncated when read.

R_X86_64_64 dynrela can be created, for example, if a patch for vmlinux
refers to something like '(unsigned long)&idt_table+0x80000000' (a global
variable which is not exported, with some addend).
The addend == +0x80000000, however, effectively becomes 0xffffffff80000000
(== -0x80000000) due to this bug.

Unfortunately, 'struct kpatch_dynrela' is a part of the ABI between
kpatch.ko and patch modules. Plain changing 'int addend' into 'long addend'
there could be problematic. The patch module built using the new
'struct kpatch_dynrela' will either fail to load if kpatch.ko is using the old
'struct kpatch_dynrela' or cause crashes or data corruptions. Unloading
and reloading patch modules and kpatch.ko is not always an option
either.

Luckily, R_X86_64_64 dynrelas seem to be quite rare in the production
patch modules and R_X86_64_64 dynrelas with large addends are expected
to be even more rare.

So, instead of fixing the truncation of addends right away, I propose to
detect it, for now, when building a patch. If one never hits such conditions,
it is not worth it to fix the issue. If R_X86_64_64 dynrelas with large
addends do happen and cannot be avoided, we can try to figure out how to
fix this properly, without breaking too much.

Signed-off-by: Evgenii Shatokhin <eshatokhin@virtuozzo.com>
2020-05-05 23:21:16 +03:00

266 lines
7.5 KiB
C

/*
* create-kpatch-module.c
*
* 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.
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA,
* 02110-1301, USA.
*/
#include <string.h>
#include <stdlib.h>
#include <libgen.h>
#include <argp.h>
#include "log.h"
#include "kpatch-elf.h"
#include "kpatch-intermediate.h"
#include "kpatch-patch.h"
/* For log.h */
char *childobj;
enum loglevel loglevel = NORMAL;
/*
* Create .kpatch.dynrelas from .kpatch.relocations and .kpatch.symbols sections
*
* Iterate through .kpatch.relocations and fill in the corresponding dynrela
* entry using information from .kpatch.relocations and .kpatch.symbols
*/
static void create_dynamic_rela_sections(struct kpatch_elf *kelf, struct section *krelasec,
struct section *ksymsec, struct section *strsec)
{
struct kpatch_patch_dynrela *dynrelas;
struct kpatch_relocation *krelas;
struct kpatch_symbol *ksym, *ksyms;
struct section *dynsec;
struct symbol *sym;
struct rela *rela;
unsigned int index, nr, offset, dest_offset, objname_offset, name_offset;
unsigned int type;
long addend;
char *target_name;
ksyms = ksymsec->data->d_buf;
krelas = krelasec->data->d_buf;
nr = (unsigned int)(krelasec->data->d_size / sizeof(*krelas));
dynsec = create_section_pair(kelf, ".kpatch.dynrelas", sizeof(*dynrelas), nr);
dynrelas = dynsec->data->d_buf;
for (index = 0; index < nr; index++) {
offset = index * (unsigned int)sizeof(*krelas);
/*
* To fill in each dynrela entry, find dest location,
* objname offset, ksym, and symbol name offset
*/
/* Get dest location */
rela = find_rela_by_offset(krelasec->rela,
offset + offsetof(struct kpatch_relocation, dest));
if (!rela)
ERROR("find_rela_by_offset");
sym = rela->sym;
dest_offset = (unsigned int)rela->addend;
/* Get objname offset */
rela = find_rela_by_offset(krelasec->rela,
(unsigned int)(offset + offsetof(struct kpatch_relocation, objname)));
if (!rela)
ERROR("find_rela_by_offset");
objname_offset = (unsigned int)rela->addend;
/* Get ksym (.kpatch.symbols entry) and symbol name offset */
rela = find_rela_by_offset(krelasec->rela,
(unsigned int)(offset + offsetof(struct kpatch_relocation, ksym)));
if (!rela)
ERROR("find_rela_by_offset");
ksym = ksyms + (rela->addend / sizeof(*ksyms));
offset = (unsigned int )(index * sizeof(*ksyms));
rela = find_rela_by_offset(ksymsec->rela,
(unsigned int)(offset + offsetof(struct kpatch_symbol, name)));
if (!rela)
ERROR("find_rela_by_offset");
name_offset = (unsigned int)rela->addend;
/* Fill in dynrela entry */
type = krelas[index].type;
addend = krelas[index].addend;
if (type == R_X86_64_64 && (addend > INT_MAX || addend <= INT_MIN)) {
target_name = (char *)strsec->data->d_buf + name_offset;
ERROR("got R_X86_64_64 dynrela for '%s' with addend too large or too small for an int: %lx",
target_name, addend);
}
dynrelas[index].src = ksym->src;
dynrelas[index].addend = addend;
dynrelas[index].type = type;
dynrelas[index].external = krelas[index].external;
dynrelas[index].sympos = ksym->sympos;
/* dest */
ALLOC_LINK(rela, &dynsec->rela->relas);
rela->sym = sym;
rela->type = R_X86_64_64;
rela->addend = dest_offset;
rela->offset = (unsigned int)(index * sizeof(*dynrelas));
/* name */
ALLOC_LINK(rela, &dynsec->rela->relas);
rela->sym = strsec->secsym;
rela->type = R_X86_64_64;
rela->addend = name_offset;
rela->offset = (unsigned int)(index * sizeof(*dynrelas) + \
offsetof(struct kpatch_patch_dynrela, name));
/* objname */
ALLOC_LINK(rela, &dynsec->rela->relas);
rela->sym = strsec->secsym;
rela->type = R_X86_64_64;
rela->addend = objname_offset;
rela->offset = (unsigned int)(index * sizeof(*dynrelas) + \
offsetof(struct kpatch_patch_dynrela, objname));
}
}
static void remove_intermediate_sections(struct kpatch_elf *kelf)
{
size_t i;
char *intermediate_sections[] = {
".kpatch.symbols",
".rela.kpatch.symbols",
".kpatch.relocations",
".rela.kpatch.relocations",
".kpatch.arch",
".rela.kpatch.arch"
};
for (i = 0; i < sizeof(intermediate_sections)/sizeof(intermediate_sections[0]); i++)
kpatch_remove_and_free_section(kelf, intermediate_sections[i]);
}
struct arguments {
char *args[2];
int debug;
};
static char args_doc[] = "input.o output.o";
static struct argp_option options[] = {
{"debug", 'd', 0, 0, "Show debug output" },
{ 0 }
};
static error_t parse_opt (int key, char *arg, struct argp_state *state)
{
/* Get the input argument from argp_parse, which we
know is a pointer to our arguments structure. */
struct arguments *arguments = state->input;
switch (key)
{
case 'd':
arguments->debug = 1;
break;
case ARGP_KEY_ARG:
if (state->arg_num >= 2)
/* Too many arguments. */
argp_usage (state);
arguments->args[state->arg_num] = arg;
break;
case ARGP_KEY_END:
if (state->arg_num < 2)
/* Not enough arguments. */
argp_usage (state);
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp = { options, parse_opt, args_doc, 0 };
int main(int argc, char *argv[])
{
struct kpatch_elf *kelf;
struct section *symtab, *sec;
struct section *ksymsec, *krelasec, *strsec;
struct arguments arguments;
unsigned int ksyms_nr, krelas_nr;
arguments.debug = 0;
argp_parse (&argp, argc, argv, 0, 0, &arguments);
if (arguments.debug)
loglevel = DEBUG;
elf_version(EV_CURRENT);
childobj = basename(arguments.args[0]);
kelf = kpatch_elf_open(arguments.args[0]);
/*
* Sanity checks:
* - Make sure all the required sections exist
* - Make sure that the number of entries in
* .kpatch.{symbols,relocations} match
*/
strsec = find_section_by_name(&kelf->sections, ".kpatch.strings");
if (!strsec)
ERROR("missing .kpatch.strings");
ksymsec = find_section_by_name(&kelf->sections, ".kpatch.symbols");
if (!ksymsec)
ERROR("missing .kpatch.symbols section");
ksyms_nr = (unsigned int)(ksymsec->data->d_size / sizeof(struct kpatch_symbol));
krelasec = find_section_by_name(&kelf->sections, ".kpatch.relocations");
if (!krelasec)
ERROR("missing .kpatch.relocations section");
krelas_nr = (unsigned int)(krelasec->data->d_size / sizeof(struct kpatch_relocation));
if (krelas_nr != ksyms_nr)
ERROR("number of krelas and ksyms do not match");
/* Create dynrelas from .kpatch.{relocations,symbols} sections */
create_dynamic_rela_sections(kelf, krelasec, ksymsec, strsec);
remove_intermediate_sections(kelf);
kpatch_reindex_elements(kelf);
symtab = find_section_by_name(&kelf->sections, ".symtab");
if (!symtab)
ERROR("missing .symtab section");
list_for_each_entry(sec, &kelf->sections, list) {
if (!is_rela_section(sec))
continue;
sec->sh.sh_link = symtab->index;
sec->sh.sh_info = sec->base->index;
kpatch_rebuild_rela_section_data(sec);
}
kpatch_create_shstrtab(kelf);
kpatch_create_strtab(kelf);
kpatch_create_symtab(kelf);
kpatch_write_output_elf(kelf, kelf->elf, arguments.args[1]);
kpatch_elf_teardown(kelf);
kpatch_elf_free(kelf);
return 0;
}