Skip to content

process: compare argv[0] and AT_EXECFN basenames as bytes - #404

Open
pierre-warnier wants to merge 1 commit into
mainfrom
argv0-bytes
Open

pierre-warnier wants to merge 1 commit into
mainfrom
argv0-bytes

Conversation

@pierre-warnier

Copy link
Copy Markdown
Collaborator

@oech3 suggested in #402 (comment) that the argv[0] check avoid lossy conversion. This does that.

verify_argv0_matches_execfn ran both basenames through to_string_lossy before comparing them. Lossy conversion maps every invalid UTF-8 sequence to U+FFFD, so two different names compared equal: pw\xfe against an AT_EXECFN of /tmp/pw\xff, or a literal U+FFFD against \xff.

That was not exploitable. A lossy string equals an ASCII utility name only when the original bytes were exactly that name, so a collision could only reach the multicall path, and a caller can reach that path anyway by running the binary under its real name. It was still a check that said "equal" for different names.

Changes:

  • verify_argv0_matches_execfn takes &OsStr and compares the basenames byte for byte. A path with no basename, such as an empty argv[0], matches nothing. Before, two missing basenames compared equal as empty strings. The null check on getauxval stays, and the unsafe block is unchanged.
  • The multicall binary passes the raw argv[0] to the check. When it looks up utility names, in argv[0] or in the first argument, it takes the name only if it is valid UTF-8. Utility names are UTF-8, so anything else names no utility, as before. The "unknown utility" message still shows the argument lossily, since it is only displayed.

Tests: unit tests for the basename comparison (different invalid bytes, U+FFFD against \xff, identical invalid bytes, missing basenames). Two of them fail with the old lossy comparison. There is also a multicall test showing that a non-UTF-8 argv[0] or applet argument is refused.

Reviews welcome. Without objection, this will be merged after 7 days.

The setuid check converted both basenames with to_string_lossy before
comparing them. That maps every invalid UTF-8 sequence to U+FFFD, so two
different names such as "pw\xfe" and "pw\xff" compared equal. Compare
the bytes instead, and treat a path with no basename as matching
nothing.

The multicall dispatch matched utility names against lossily converted
strings too. Utility names are UTF-8, so take the name only when it is
valid UTF-8; anything else names no utility, as before.

Suggested-by: oech3
Comment thread src/bin/shadow-rs.rs
// Direct invocation via symlink (e.g., argv[0] = "passwd")
if let Some(run) = find_applet(&binary_name) {
return run_applet(&binary_name, run, &args);
if let Some((name, run)) = binary_name.and_then(|n| find_applet(n).map(|run| (n, run))) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a bit convoluted, could be if let Some(name) = binary_name && let Some(run) = find_applet(name), no?

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.

2 participants