root/tools/objtool/klp-checksum.c
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string.h>
#include <subcmd/parse-options.h>

#include <objtool/arch.h>
#include <objtool/builtin.h>
#include <objtool/check.h>
#include <objtool/elf.h>
#include <objtool/klp.h>
#include <objtool/objtool.h>
#include <objtool/warn.h>
#include <objtool/checksum.h>

static int checksum_debug_init(struct objtool_file *file)
{
        char *dup, *s;

        if (!opts.debug_checksum)
                return 0;

        dup = strdup(opts.debug_checksum);
        if (!dup) {
                ERROR_GLIBC("strdup");
                return -1;
        }

        s = dup;
        while (*s) {
                bool found = false;
                struct symbol *sym;
                char *comma;

                comma = strchr(s, ',');
                if (comma)
                        *comma = '\0';

                for_each_sym_by_name(file->elf, s, sym) {
                        if (!is_func_sym(sym) && !is_object_sym(sym))
                                continue;
                        sym->debug_checksum = 1;
                        found = true;
                }

                if (!found)
                        WARN("--debug-checksum: can't find '%s'", s);

                if (!comma)
                        break;

                s = comma + 1;
        }

        free(dup);
        return 0;
}

static void checksum_update_insn(struct objtool_file *file, struct symbol *func,
                                 struct instruction *insn)
{
        struct reloc *reloc = insn_reloc(file, insn);
        struct alternative *alt;
        unsigned long offset;
        struct symbol *sym;
        static bool in_alt;

        if (insn->fake)
                return;

        if (!reloc) {
                struct symbol *call_dest = insn_call_dest(insn);
                struct instruction *jump_dest = insn->jump_dest;

                /*
                 * For a jump/call non-relocated dest offset embedded in the
                 * instruction, the offset may vary due to changes in
                 * surrounding code.  Just hash the opcode and a
                 * position-independent representation of the destination.
                 */

                if (call_dest || jump_dest) {
                        unsigned char buf[16];
                        size_t len;

                        len = arch_jump_opcode_bytes(file, insn, buf);
                        __checksum_update_insn(func, insn, buf, len);

                        if (call_dest) {
                                __checksum_update_insn(func, insn, call_dest->demangled_name,
                                                       strlen(call_dest->demangled_name));

                        } else if (jump_dest) {
                                struct symbol *dest_sym;
                                unsigned long offset;

                                /*
                                 * use insn->_sym instead of insn_sym() here.
                                 * For alternative replacements, the latter
                                 * would give the function of the code being
                                 * replaced.
                                 */
                                dest_sym = jump_dest->_sym;
                                if (!dest_sym)
                                        goto alts;

                                __checksum_update_insn(func, insn, dest_sym->demangled_name,
                                                       strlen(dest_sym->demangled_name));

                                offset = jump_dest->offset - dest_sym->offset;
                                __checksum_update_insn(func, insn, &offset, sizeof(offset));
                        }

                        goto alts;
                }
        }

        __checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len);

        if (!reloc)
                goto alts;

        sym = reloc->sym;
        offset = arch_insn_adjusted_addend(insn, reloc);

        if (is_string_sec(sym->sec)) {
                char *str;

                str = sym->sec->data->d_buf + sym->offset + offset;
                __checksum_update_insn(func, insn, str, strlen(str));
                goto alts;
        }

        if (is_sec_sym(sym)) {
                sym = find_symbol_containing(reloc->sym->sec, offset);
                if (!sym)
                        goto alts;

                offset -= sym->offset;
        }

        __checksum_update_insn(func, insn, sym->demangled_name,
                               strlen(sym->demangled_name));
        __checksum_update_insn(func, insn, &offset, sizeof(offset));

alts:
        for (alt = insn->alts; alt; alt = alt->next) {
                struct alt_group *alt_group = alt->insn->alt_group;

                /* Prevent __ex_table recursion, e.g. LOAD_SEGMENT() */
                if (in_alt)
                        break;
                in_alt = true;

                __checksum_update_insn(func, insn, &alt->type,
                                       sizeof(alt->type));

                if (alt_group && alt_group->orig_group) {
                        struct instruction *alt_insn;

                        __checksum_update_insn(func, insn, &alt_group->feature,sizeof(alt_group->feature));

                        for (alt_insn = alt->insn; alt_insn; alt_insn = next_insn_same_sec(file, alt_insn)) {
                                checksum_update_insn(file, func, alt_insn);
                                if (!alt_group->last_insn || alt_insn == alt_group->last_insn)
                                        break;
                        }
                } else {
                        checksum_update_insn(file, func, alt->insn);
                }

                in_alt = false;
        }
}

static void checksum_update_object(struct objtool_file *file, struct symbol *sym)
{
        struct reloc *reloc;

        __checksum_update_object(sym, 0, "len", &sym->len, sizeof(sym->len));

        if (sym->sec->data->d_buf)
                __checksum_update_object(sym, 0, "data",
                                         sym->sec->data->d_buf + sym->offset,
                                         sym->len);

        sym_for_each_reloc(file->elf, sym, reloc) {
                unsigned long sym_offset = reloc_offset(reloc) - sym->offset;
                struct symbol *target = reloc->sym;
                s64 offset;

                offset = reloc_addend(reloc);

                if (is_string_sec(target->sec)) {
                        char *str;

                        str = target->sec->data->d_buf + target->offset + offset;
                        __checksum_update_object(sym, sym_offset,
                                                 "reloc string", str, strlen(str));
                        continue;
                }

                if (is_sec_sym(target)) {
                        target = find_symbol_containing(reloc->sym->sec, offset);
                        if (!target)
                                continue;

                        offset -= target->offset;
                }

                __checksum_update_object(sym, sym_offset, "reloc name",
                                         target->demangled_name,
                                         strlen(target->demangled_name));
                __checksum_update_object(sym, sym_offset, "reloc addend",
                                         &offset, sizeof(offset));
        }
}

int calculate_checksums(struct objtool_file *file)
{
        struct instruction *insn;
        struct symbol *sym;

        if (checksum_debug_init(file))
                return -1;

        for_each_sym(file->elf, sym) {

                /*
                 * Skip cold subfunctions and aliases: they share the
                 * parent's checksum via func_for_each_insn() which
                 * follows func->cfunc into the cold subfunction.
                 */
                if (is_cold_func(sym) || is_alias_sym(sym) || !sym->len ||
                    !sym->sec || !sym->sec->data)
                        continue;

                if (is_func_sym(sym)) {
                        checksum_init(sym);
                        func_for_each_insn(file, sym, insn)
                                checksum_update_insn(file, sym, insn);
                        checksum_finish(sym);

                } else if (is_object_sym(sym)) {
                        checksum_init(sym);
                        checksum_update_object(file, sym);
                        checksum_finish(sym);
                }

        }

        return 0;
}

int create_sym_checksum_section(struct objtool_file *file)
{
        struct section *sec;
        struct symbol *sym;
        unsigned int idx = 0;
        struct sym_checksum *checksum;
        size_t entsize = sizeof(struct sym_checksum);

        sec = find_section_by_name(file->elf, ".discard.sym_checksum");
        if (sec) {
                if (!opts.dryrun)
                        WARN("file already has .discard.sym_checksum section, skipping");

                return 0;
        }

        for_each_sym(file->elf, sym)
                if (sym->csum.checksum)
                        idx++;

        sec = elf_create_section_pair(file->elf, ".discard.sym_checksum", entsize,
                                      idx, idx);
        if (!sec)
                return -1;

        idx = 0;
        for_each_sym(file->elf, sym) {
                if (!sym->csum.checksum)
                        continue;

                if (!elf_init_reloc(file->elf, sec->rsec, idx, idx * entsize,
                                    sym, 0, R_TEXT64))
                        return -1;

                checksum = (struct sym_checksum *)sec->data->d_buf + idx;
                checksum->addr = 0; /* reloc */
                checksum->checksum = sym->csum.checksum;

                mark_sec_changed(file->elf, sec, true);

                idx++;
        }

        return 0;
}

static const char * const klp_checksum_usage[] = {
        "objtool klp checksum [<options>] file.o",
        NULL,
};

int cmd_klp_checksum(int argc, const char **argv)
{
        struct objtool_file *file;
        int ret;

        const struct option options[] = {
                OPT_STRING(0,   "debug-checksum", &opts.debug_checksum, "syms", "enable checksum debug output"),
                OPT_BOOLEAN(0,  "dry-run", &opts.dryrun, "don't write modifications"),
                OPT_END(),
        };

        argc = parse_options(argc, argv, options, klp_checksum_usage, 0);
        if (argc != 1)
                usage_with_options(klp_checksum_usage, options);

        opts.checksum = true;

        objname = argv[0];

        file = objtool_open_read(objname);
        if (!file)
                return 1;

        ret = decode_file(file);
        if (ret)
                goto out;

        ret = calculate_checksums(file);
        if (ret)
                goto out;

        ret = create_sym_checksum_section(file);

out:
        free_insns(file);

        if (ret)
                return ret;

        if (!opts.dryrun && file->elf->changed && elf_write(file->elf))
                return 1;

        return elf_close(file->elf);
}