Skip to content

Terminate Ninja options before build target operands #645

Description

@leynos

Summary

Netsuke appends build targets directly after its trusted Ninja options without inserting Ninja's -- option terminator.

On main at ac10b783f33661a0af27bd8f5d19065f095f98db, configure_ninja_build_command() constructs the base command with -j and -f <generated-build-file>, then calls:

cmd.args(targets.as_slice());

Ninja continues parsing options in that suffix. A target string beginning with - can therefore alter Ninja's control plane instead of naming a target. This affects both explicit CLI targets and project-configured default_targets; the two Codex findings are the same argv-boundary defect.

Reproduction

A bounded reproduction against Ninja 1.12.1 used a trusted build file and an attacker-controlled sibling directory containing another build.ninja.

Without a terminator:

ninja safe -C ../evil default

Ninja entered ../evil, loaded its build file, executed its recipe, and returned success.

With a terminator:

ninja -- safe -C ../evil default

Ninja treated -C and its following strings as target operands; the attacker-controlled recipe did not run.

The same primitive permits later -f, -t, -j, and other recognized Ninja options. For example, project configuration can supply:

default_targets = ["-f", "evil.ninja"]

When the user invokes netsuke or netsuke build without explicit targets, the resulting argv selects the attacker's Ninja file after Netsuke has already supplied its generated one.

std::process::Command correctly prevents shell metacharacter injection, but it does not distinguish child-program options from operands.

Impact

An attacker who can influence a target name forwarded by a developer/CI wrapper, or who controls automatically discovered project configuration, can redirect Ninja to an attacker-selected directory, build file, tool mode, or option set. That can execute attacker-controlled build recipes with the invoking developer or CI account's ambient filesystem and environment privileges.

Required change

Insert a literal -- between all Netsuke-owned Ninja options and every build-target operand:

ninja ... -f <generated-build-file> -- <targets...>

Use the same command-shaping path for explicit and configured default targets. If supported Ninja versions behave differently for an empty operand list, insert the terminator whenever the selected target list is non-empty; otherwise prefer one uniform argv shape.

Do not solve this solely by rejecting leading hyphens. Ninja can represent unusual literal target names, and the child program already provides the correct operand boundary.

Acceptance criteria

  • Every Ninja build invocation places -- immediately before target operands.
  • Explicit CLI targets and default_targets from configuration use the same protected path.
  • A configured target list ['-f', 'evil.ninja'] cannot replace the generated build file.
  • An explicit target suffix equivalent to -- -C evil default cannot change Ninja's working directory.
  • Unit tests inspect the exact Command argv ordering, including no-target and multi-target cases.
  • An integration test with a real Ninja executable proves the attacker-controlled build file or directory is not selected.
  • Ordinary target selection, default-target behaviour, clean/graph tool modes, and supported platforms remain unchanged.
  • make check-fmt, make lint, and make test pass.

References

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 workinghighUnblocks delivery within the current sprint or release window. Reaction time is days, not hours.testingTest coverage, test infrastructure, and verification tooling work.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions