Skip to content

Reject world-writable components in exec-suid script paths - #18

Merged
ConnorNelson merged 1 commit into
mainfrom
reject-world-writable-paths
Aug 29, 2026
Merged

Reject world-writable components in exec-suid script paths#18
ConnorNelson merged 1 commit into
mainfrom
reject-world-writable-paths

Conversation

@ConnorNelson

Copy link
Copy Markdown
Member

Summary

  • Reject any script-path component with the other-write permission bit.
  • Reject sticky world-writable directories such as /tmp.
  • Reject world-writable target scripts.
  • Move ordinary integration-test scripts into root-owned /tests/tmp.
  • Add regression coverage for non-sticky directories, sticky directories, and world-writable scripts.

Rationale

Root ownership alone is insufficient when a path component is writable by other users.

For example, when fs.protected_hardlinks=0, an unprivileged user may be able to create a hardlink in /tmp to a root-owned setuid script and give it an option-like name such as -c<payload>.

If the privileged script uses an interpreter header such as:

#!/usr/bin/exec-suid -- /usr/bin/python3 -I

and the hardlink is invoked using that relative spelling, exec-suid appends the path verbatim to the interpreter arguments:

/usr/bin/python3 -I "-c<payload>"

Python can interpret this as its -c option and execute the filename payload with the privileges selected from the setuid script.

The hardlink still refers to a root-owned inode, so ownership validation does not reject it. The sticky bit on /tmp restricts removing or renaming entries owned by other users, but it does not prevent users from creating new entries.

Rejecting every other-writable path component prevents setuid scripts from being reached through these attacker-controlled locations. It also avoids depending on the current hardlink-protection sysctl, which cannot account for links created while protection was disabled.

The same rule applies to the target script itself: a root-owned but world-writable privileged script should not be trusted.

This intentionally means that setuid scripts beneath /tmp or another world-writable directory are no longer accepted, even when the directory has the sticky bit.

Validation

  • Release container builds successfully.
  • All 23 integration tests pass.
  • git diff --check passes.

@ConnorNelson
ConnorNelson merged commit d541cb2 into main Aug 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant