Re: [PATCH] tools/resolve_btfids: fix OUTPUT leakage from kselftest causing corrupted build paths
From: Ihor Solodrai
Date: Thu Jul 16 2026 - 19:46:25 EST
On 7/16/26 1:57 AM, Jiangshan Yi wrote:
> When building kselftest-all, the kselftest Makefile passes OUTPUT as a
> command-line variable to each test directory:
>
> tools/testing/selftests/Makefile:
> $(MAKE) OUTPUT=$$BUILD/$$TARGET -C $$TARGET
>
> GNU Make automatically exports command-line variables to all sub-makes.
> This OUTPUT propagates through the entire build chain:
>
> kselftest/mm (OUTPUT=.../selftests/mm)
> -> gen_mods_dir -> page_frag (kernel module build)
> -> tools/bpf/resolve_btfids (host tool dependency)
>
> The resolve_btfids Makefile uses a conditional assignment:
>
> OUTPUT ?= $(srctree)/tools/bpf/resolve_btfids/
>
> However, ?= cannot override a variable inherited from a parent build's
> command line or environment. The inherited OUTPUT points to the wrong
> directory (the mm selftest directory) and lacks a trailing slash.
>
> The tools/build Makefile.build system concatenates $(OUTPUT) with
> filenames without a separating slash in its pattern rules:
>
> $(OUTPUT)%.o: %.c FORCE
>
> When OUTPUT lacks a trailing '/', this produces corrupted paths such
> as ".../selftests/mmmain.o" instead of ".../selftests/mm/main.o".
> Meanwhile, the resolve_btfids Makefile itself uses $(OUTPUT)/filename
> (with an explicit slash), so the HOSTLD step produces the file at a
> different path than where the LINK step looks for it:
>
> HOSTLD produces: .../selftests/mmresolve_btfids-in.o (no slash)
> LINK looks for: .../selftests/mm/resolve_btfids-in.o (with slash)
>
> This results in a linker error:
>
> ld: cannot find .../resolve_btfids-in.o: No such file or directory
>
> Fix this by checking the origin of OUTPUT. When it is "command line"
> or "environment" (indicating leakage from a parent build such as
> kselftest), use 'override' to reset it to the correct directory. When
> built normally via the kernel build system, OUTPUT is set by
> scripts/Makefile.include with origin "file" from the O= parameter,
> which is correct and remains unaffected.
>
> The fix can be reproduced and verified with:
>
> make -C tools/bpf/resolve_btfids clean
> # Before fix: fails with corrupted paths
> make OUTPUT=/path/to/selftests/mm -C tools/bpf/resolve_btfids
> # After fix: succeeds, artifacts in resolve_btfids directory
>
> # Also test environment variable leakage:
> OUTPUT=/path/to/selftests/mm make -C tools/bpf/resolve_btfids
>
> Signed-off-by: Jiangshan Yi <yijiangshan@xxxxxxxxxx>
> ---
> tools/bpf/resolve_btfids/Makefile | 16 ++++++++++++++++
> 1 file changed, 16 insertions(+)
>
> diff --git a/tools/bpf/resolve_btfids/Makefile b/tools/bpf/resolve_btfids/Makefile
> index 7672208f65e4..eee88c73fd7a 100644
> --- a/tools/bpf/resolve_btfids/Makefile
> +++ b/tools/bpf/resolve_btfids/Makefile
> @@ -26,6 +26,22 @@ HOSTAR ?= ar
> HOSTPKG_CONFIG ?= pkg-config
> CROSS_COMPILE =
>
> +# If OUTPUT was inherited from a parent build's command line (e.g. from
> +# kselftest), it points to the wrong directory and may lack a trailing
> +# slash. The tools/build Makefile.build concatenates $(OUTPUT) with
> +# filenames without a separating slash ($(OUTPUT)%.o), so a missing
> +# trailing '/' produces corrupted paths such as "mmmain.o" instead of
> +# "mm/main.o", and the linker cannot find resolve_btfids-in.o.
> +#
> +# When built via the kernel build system, OUTPUT is set by
> +# scripts/Makefile.include (origin "file") from the O= parameter, which
> +# is correct. Only a command-line origin indicates leakage from a
> +# parent build and needs to be reset. Both "command line" and
> +# "environment" origins indicate leakage; "file" (set by Makefile.include
> +# from O=) and "undefined" are legitimate and must not be overridden.
> +ifneq ($(filter command line environment,$(origin OUTPUT)),)
> +override OUTPUT := $(srctree)/tools/bpf/resolve_btfids/
> +endif
> OUTPUT ?= $(srctree)/tools/bpf/resolve_btfids/
This seems to be a real bug, but the proposed diff is just wrong.
It doesn't even fix the case you target.
The `override` runs in the outer make, but the corruption happens in
the nested sub-make (resolve_btfids/Makefile:105):
$(BINARY_IN): fixdep FORCE prepare | $(OUTPUT)
$(Q)$(MAKE) $(build)=resolve_btfids
Try this:
$ cat parent.mk
ifneq ($(filter command line environment,$(origin OUTPUT)),)
override OUTPUT := /correct/
endif
export OUTPUT
all: ; @$(MAKE) --no-print-directory -f child.mk
$ cat child.mk
all: ; @echo "OUTPUT=[$(OUTPUT)] -> $(OUTPUT)main.o"
$ make -f parent.mk OUTPUT=/leaked/mm
OUTPUT=[/leaked/mm] -> /leaked/mmmain.o # override invisible; still broken
Your standalone repro probably only succeeds on a dirty tree.
Have you tried testing from clean?
Also bpf/Makefile:440 and hid/Makefile:159 deliberately pass
OUTPUT=$(HOST_BUILD_DIR)/resolve_btfids/ on the command line.
And so the patch breaks those too:
$ mkdir -p /tmp/o/resolve_btfids && make -C tools/bpf/resolve_btfids clean \
&& make -C tools/bpf/resolve_btfids OUTPUT=/tmp/o/resolve_btfids/
...
make[1]: *** No rule to make target '.../resolve_btfids/fixdep'. Stop.
make: *** [.../tools/build/Makefile.include:15: fixdep] Error 2
I think a proper fix is to figure out a way to avoid generic OUTPUT to
fall through to resolve_btids (somewhere in selftests/lib.mk
probably).
Please make sure to test your change properly before submitting.
pw-bot: cr
>
> LIBBPF_SRC := $(srctree)/tools/lib/bpf/