diff --git a/private/util/validate_usr_symlinks.awk b/private/util/validate_usr_symlinks.awk index 23053a707..a96381570 100644 --- a/private/util/validate_usr_symlinks.awk +++ b/private/util/validate_usr_symlinks.awk @@ -18,9 +18,15 @@ BEGIN { sub(/^(\.\/|\/)+/, "", path) if (path in expected) { - if ($0 !~ /type=link/) { + # Match `type=link` only as a whole mtree field — bounded by whitespace + # on the left and whitespace or end-of-line on the right. A naive + # substring match was tricked by other mtree field values that happened + # to contain the literal string "type=link" (for example a uname, + # gname, or flags value), silently skipping the symlink check for a + # non-symlink entry at /bin, /sbin, /lib, etc. + if ($0 !~ /[[:space:]]type=link([[:space:]]|$)/) { VIOLATIONS[original_path] = original_path " is not a symlink (must link to " expected[path] ")" - } else if (match($0, / link=([^ \t]+)/, dest) && dest[1] != expected[path]) { + } else if (match($0, /[[:space:]]link=([^[:space:]]+)/, dest) && dest[1] != expected[path]) { VIOLATIONS[original_path] = original_path " symlinks to '" dest[1] "' instead of '" expected[path] "'" } } else if (path ~ ("^(" prefixes ")/")) { diff --git a/private/util/validate_usr_symlinks_test.sh b/private/util/validate_usr_symlinks_test.sh index 371f839f1..fe1d71b9e 100755 --- a/private/util/validate_usr_symlinks_test.sh +++ b/private/util/validate_usr_symlinks_test.sh @@ -87,4 +87,27 @@ run "/lib/libfoo.so.1 type=file mode=0644 nlink=1 uid=0 gid=0 size=4096" \ run "./bin/ls type=file mode=0755 nlink=1 uid=0 gid=0 size=12345" \ && fail "content under ./bin/ should fail" || true +# --- substring-match regression cases --- +# An earlier version of this check used `$0 !~ /type=link/`, which matched the +# string "type=link" anywhere on the line, including inside other mtree field +# values. A non-symlink entry whose uname/gname/flags happened to contain that +# substring would silently pass. The check is now field-bounded. + +run "./bin type=dir uname=type=link gname=root mode=0755 uid=0 gid=0" \ + && fail "./bin as a dir with uname=type=link should fail (substring bypass)" || true + +run "./bin type=dir uname=root gname=type=link mode=0755 uid=0 gid=0" \ + && fail "./bin as a dir with gname=type=link should fail (substring bypass)" || true + +run "./bin type=dir flags=type=link mode=0755 uid=0 gid=0" \ + && fail "./bin as a dir with flags=type=link should fail (substring bypass)" || true + +run "./lib type=dir uname=type=link mode=0755 uid=0 gid=0" \ + && fail "./lib as a dir with uname=type=link should fail (substring bypass)" || true + +# A legitimate symlink that happens to carry extra trailing fields must keep +# working under the field-bounded check. +run "./bin type=link mode=0777 nlink=1 uid=0 gid=0 link=usr/bin extra=ignored" \ + || fail "./bin -> usr/bin with extra trailing field should pass" + echo "All tests passed."