mirror of
https://github.com/dynup/kpatch
synced 2024-12-22 13:12:06 +00:00
9bb75659e2
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>
266 lines
7.5 KiB
C
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;
|
|
}
|