libbpf: Fix segfault due to libelf functions not setting errno
authorQuentin Monnet <qmo@kernel.org>
Thu, 5 Dec 2024 13:59:42 +0000 (13:59 +0000)
committerAndrii Nakryiko <andrii@kernel.org>
Thu, 5 Dec 2024 23:19:00 +0000 (15:19 -0800)
Libelf functions do not set errno on failure. Instead, it relies on its
internal _elf_errno value, that can be retrieved via elf_errno (or the
corresponding message via elf_errmsg()). From "man libelf":

    If a libelf function encounters an error it will set an internal
    error code that can be retrieved with elf_errno. Each thread
    maintains its own separate error code. The meaning of each error
    code can be determined with elf_errmsg, which returns a string
    describing the error.

As a consequence, libbpf should not return -errno when a function from
libelf fails, because an empty value will not be interpreted as an error
and won't prevent the program to stop. This is visible in
bpf_linker__add_file(), for example, where we call a succession of
functions that rely on libelf:

    err = err ?: linker_load_obj_file(linker, filename, opts, &obj);
    err = err ?: linker_append_sec_data(linker, &obj);
    err = err ?: linker_append_elf_syms(linker, &obj);
    err = err ?: linker_append_elf_relos(linker, &obj);
    err = err ?: linker_append_btf(linker, &obj);
    err = err ?: linker_append_btf_ext(linker, &obj);

If the object file that we try to process is not, in fact, a correct
object file, linker_load_obj_file() may fail with errno not being set,
and return 0. In this case we attempt to run linker_append_elf_sysms()
and may segfault.

This can happen (and was discovered) with bpftool:

    $ bpftool gen object output.o sample_ret0.bpf.c
    libbpf: failed to get ELF header for sample_ret0.bpf.c: invalid `Elf' handle
    zsh: segmentation fault (core dumped)  bpftool gen object output.o sample_ret0.bpf.c

Fix the issue by returning a non-null error code (-EINVAL) when libelf
functions fail.

Fixes: faf6ed321cf6 ("libbpf: Add BPF static linker APIs")
Signed-off-by: Quentin Monnet <qmo@kernel.org>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20241205135942.65262-1-qmo@kernel.org
tools/lib/bpf/linker.c

index cf71d149fe26a216ab96a9f8a97e5bb6ec8cd2ea..e56ba6e67451d6c2c506356cd320587ed65b6373 100644 (file)
@@ -566,17 +566,15 @@ static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
        }
        obj->elf = elf_begin(obj->fd, ELF_C_READ_MMAP, NULL);
        if (!obj->elf) {
-               err = -errno;
                pr_warn_elf("failed to parse ELF file '%s'", filename);
-               return err;
+               return -EINVAL;
        }
 
        /* Sanity check ELF file high-level properties */
        ehdr = elf64_getehdr(obj->elf);
        if (!ehdr) {
-               err = -errno;
                pr_warn_elf("failed to get ELF header for %s", filename);
-               return err;
+               return -EINVAL;
        }
 
        /* Linker output endianness set by first input object */
@@ -606,9 +604,8 @@ static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
        }
 
        if (elf_getshdrstrndx(obj->elf, &obj->shstrs_sec_idx)) {
-               err = -errno;
                pr_warn_elf("failed to get SHSTRTAB section index for %s", filename);
-               return err;
+               return -EINVAL;
        }
 
        scn = NULL;
@@ -618,26 +615,23 @@ static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
 
                shdr = elf64_getshdr(scn);
                if (!shdr) {
-                       err = -errno;
                        pr_warn_elf("failed to get section #%zu header for %s",
                                    sec_idx, filename);
-                       return err;
+                       return -EINVAL;
                }
 
                sec_name = elf_strptr(obj->elf, obj->shstrs_sec_idx, shdr->sh_name);
                if (!sec_name) {
-                       err = -errno;
                        pr_warn_elf("failed to get section #%zu name for %s",
                                    sec_idx, filename);
-                       return err;
+                       return -EINVAL;
                }
 
                data = elf_getdata(scn, 0);
                if (!data) {
-                       err = -errno;
                        pr_warn_elf("failed to get section #%zu (%s) data from %s",
                                    sec_idx, sec_name, filename);
-                       return err;
+                       return -EINVAL;
                }
 
                sec = add_src_sec(obj, sec_name);
@@ -2680,14 +2674,14 @@ int bpf_linker__finalize(struct bpf_linker *linker)
 
        /* Finalize ELF layout */
        if (elf_update(linker->elf, ELF_C_NULL) < 0) {
-               err = -errno;
+               err = -EINVAL;
                pr_warn_elf("failed to finalize ELF layout");
                return libbpf_err(err);
        }
 
        /* Write out final ELF contents */
        if (elf_update(linker->elf, ELF_C_WRITE) < 0) {
-               err = -errno;
+               err = -EINVAL;
                pr_warn_elf("failed to write ELF contents");
                return libbpf_err(err);
        }