libbpf: Fix double-free when linker processes empty sections
authorEduard Zingerman <eddyz87@gmail.com>
Tue, 28 Mar 2023 00:47:38 +0000 (03:47 +0300)
committerAndrii Nakryiko <andrii@kernel.org>
Tue, 28 Mar 2023 03:02:15 +0000 (20:02 -0700)
Double-free error in bpf_linker__free() was reported by James Hilliard.
The error is caused by miss-use of realloc() in extend_sec().
The error occurs when two files with empty sections of the same name
are linked:
- when first file is processed:
  - extend_sec() calls realloc(dst->raw_data, dst_align_sz)
    with dst->raw_data == NULL and dst_align_sz == 0;
  - dst->raw_data is set to a special pointer to a memory block of
    size zero;
- when second file is processed:
  - extend_sec() calls realloc(dst->raw_data, dst_align_sz)
    with dst->raw_data == <special pointer> and dst_align_sz == 0;
  - realloc() "frees" dst->raw_data special pointer and returns NULL;
  - extend_sec() exits with -ENOMEM, and the old dst->raw_data value
    is preserved (it is now invalid);
  - eventually, bpf_linker__free() attempts to free dst->raw_data again.

This patch fixes the bug by avoiding -ENOMEM exit for dst_align_sz == 0.
The fix was suggested by Andrii Nakryiko <andrii.nakryiko@gmail.com>.

Reported-by: James Hilliard <james.hilliard1@gmail.com>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Tested-by: James Hilliard <james.hilliard1@gmail.com>
Link: https://lore.kernel.org/bpf/CADvTj4o7ZWUikKwNTwFq0O_AaX+46t_+Ca9gvWMYdWdRtTGeHQ@mail.gmail.com/
Link: https://lore.kernel.org/bpf/20230328004738.381898-3-eddyz87@gmail.com
tools/lib/bpf/linker.c

index d706978..5ced96d 100644 (file)
@@ -1115,7 +1115,19 @@ static int extend_sec(struct bpf_linker *linker, struct dst_sec *dst, struct src
 
        if (src->shdr->sh_type != SHT_NOBITS) {
                tmp = realloc(dst->raw_data, dst_final_sz);
-               if (!tmp)
+               /* If dst_align_sz == 0, realloc() behaves in a special way:
+                * 1. When dst->raw_data is NULL it returns:
+                *    "either NULL or a pointer suitable to be passed to free()" [1].
+                * 2. When dst->raw_data is not-NULL it frees dst->raw_data and returns NULL,
+                *    thus invalidating any "pointer suitable to be passed to free()" obtained
+                *    at step (1).
+                *
+                * The dst_align_sz > 0 check avoids error exit after (2), otherwise
+                * dst->raw_data would be freed again in bpf_linker__free().
+                *
+                * [1] man 3 realloc
+                */
+               if (!tmp && dst_align_sz > 0)
                        return -ENOMEM;
                dst->raw_data = tmp;