os: properly handle trailing slashes in paths in Root
This change fixes a significant mechanism by which
operations in a Root can escape the root.
The implementation of Root on platforms supporting the openat
family of functions assumed that openat(parent, "f/", O_NOFOLLOW)
would not resolve symlinks in "f". This is not correct; the
trailing slash causes f to be resolved.
This permits Root operations to escape when the target filename
ends in a slash and the target is a symlink to a directory outside the
root. This does not permit directly accessing non-directory files
outside a root, since the trailing slash adds a requirement that the
target be a directory. However, under some circumstances an attacker
might exploit this flaw to access non-directory files outside
a root, for example by first renaming a directory outside the root
to a location within it and then accessing files within that directory.
This change adjusts Root's handling of slash-terminated paths.
Trailing slashes are removed from the path at the start of an
operation, and the presence of slashes is tracked as a boolean.
Slashes are never reattached to a path component.
In addition, the doInRoot helper function now automatically
handles trailing slashes in a POSIX-compatible fashion.
When a path ends in one or more slashes:
- symlinks in the final component are resolved; and
- the final path component after symlink resolutions
must reference a directory.
This change also adds a new sets of tests to exercise Root's
behavior in a wider variety of circumstances. These tests
run through a matrix of file configurations, such as:
- path "target", a regular file
- path "dir/../target", a directory
- path "target/", a symlink to "dir/../target/", which does not exist
- etc.
These tests execute Root operations and the corresponding unrooted
operation, validate specific expected results for some configurations,
and verify that the rooted and unrooted versions of the operation
produce the same result.
Thanks to Mundur (https://github.com/M0nd0R) for reporting this issue.
Fixes #79005
Fixes CVE-2026-39822
Change-Id: I34072ab63f2367baf236592f11143f4e6a6a6964
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/4740
Reviewed-by: Neal Patel <nealpatel@google.com>
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/797880
Auto-Submit: David Chase <drchase@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.
Gopher image by Renee French, licensed under Creative Commons 4.0 Attribution license.
Our canonical Git repository is located at https://go.googlesource.com/go. There is a mirror of the repository at https://github.com/golang/go.
Unless otherwise noted, the Go source files are distributed under the BSD-style license found in the LICENSE file.
Official binary distributions are available at https://go.dev/dl/.
After downloading a binary release, visit https://go.dev/doc/install for installation instructions.
If a binary distribution is not available for your combination of operating system and architecture, visit https://go.dev/doc/install/source for source installation instructions.
Go is the work of thousands of contributors. We appreciate your help!
To contribute, please read the contribution guidelines at https://go.dev/doc/contribute.
Note that the Go project uses the issue tracker for bug reports and proposals only. See https://go.dev/wiki/Questions for a list of places to ask questions about the Go language.