Skip to content

A $ORIGIN in a user link flag reaches the program as /../lib: link flags are escaped for ninja and not quoted for the shell #703

Description

@speak-agent

Summary

A run path written with $ORIGIN in [build] ldflags, or passed by a build program through mcpp::link_flag, does not reach the linker. build.ninja carries the ninja-escaped $$ORIGIN, ninja hands the command to sh, and sh expands $ORIGIN to nothing. The program's run path then holds /../lib, which is the host's /lib. The build succeeds and says nothing.

Reproduction

mcpp 2026.9.26.1, gcc@16.1.0, Linux x86_64:

[package]
name = "tomlp"
version = "0.1.0"

[toolchain]
linux = "gcc@16.1.0"

[build]
ldflags = ["-Wl,-rpath,$ORIGIN/../lib"]
$ mcpp build
$ grep -o -- '-Wl,-rpath,[^ ]*ORIGIN[^ ]*' target/*/*/build.ninja
-Wl,-rpath,$$ORIGIN/../lib
$ readelf -d target/*/*/bin/tomlp | grep -i rpath
 0x000000000000000f (RPATH)  Library rpath: [<glibc lib64>:<gcc lib64>:/../lib:<subos lib>]

A build program that calls mcpp::link_flag("-Wl,-rpath,$ORIGIN/../lib") produces the same build.ninja line and the same run path.

Cause

SPEC-004 §8 states how an element of cflags, cxxflags and asmflags becomes words, forbids an implementation to interpret $ (rule 7), and requires every word to reach the compiler verbatim whatever the host's command-line reader. ldflags and the link directives have no such statement, and normalize_ldflag (src/build/flags.cppm:367-385) escapes an element for ninja only. The engine's own run path is written -Wl,-rpath,'$$ORIGIN' (src/build/plan.cppm:745, :2125), quoted for ninja and for the shell, which is why mcpp's own $ORIGIN entries are correct.

Consequence

The entry is not merely lost. /../lib names the host's /lib, so a program linked against a payload or graph closure searches a host directory at run time: the class of defect #696 closed for link-time search.

Found through the mcpp-plugins deps-* work, whose design record gave up $ORIGIN for this reason and links by full path instead.

Proposed repair

SPEC-004 §8's element reading applies to ldflags and to mcpp::link_flag: an element is read into words, and each word reaches the linker verbatim, escaped for ninja and then quoted for the host. The same reading splits an element that packs several tokens, which the rendering of link-unit flags guards against case by case today (src/build/ninja_backend.cppm:342-370). Windows is unaffected, because its commands run without a shell.

Compatibility: an element already written for the shell or for ninja by hand (\$ORIGIN, '$$ORIGIN') changes meaning. The index is searched for such spellings before the release.

Criterion: an e2e in which $ORIGIN from the manifest and from link_flag reaches the program's run path verbatim. It fails on 2026.9.26.1.

This is item F1 of the design record for the next release (.agents/docs/2026-09-26-compile-database-and-issue-699-design.md), which lands with the fix.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions