How Go 1.24’s New os.Root API Stops Directory Traversal Attacks
This article explains the newly added os.Root type and os.OpenRoot function in Go 1.24, demonstrates how they restrict file operations to a specific directory, and shows how they prevent classic directory‑traversal exploits such as CVE‑2024‑3400.
Go 1.24 has entered freeze, and the new os.Root type in the standard library provides the ability to perform file‑system operations confined to a specific directory.
Proposal
Directory‑traversal vulnerabilities are common; attackers supply relative paths like "../../../etc/passwd" to access unintended files. CVE‑2024‑3400 is a recent real‑world example that led to remote code execution.
Similar mechanisms exist in other languages and OSes, for example:
Python’s os.chroot() limits the root directory.
Linux file‑system namespaces via mount and chroot. echo 'password123' >> /tmp/password Original Go code that simply reads a file from the current directory:
func main() {
fileName := os.Args[1]
localFilePath := "."
filePath := fmt.Sprintf("%s/%s", localFilePath, fileName)
content, err := os.ReadFile(filePath)
if err != nil {
fmt.Printf("Error reading file %s: %s
", fileName, err)
return
}
fmt.Printf("File %s opened successfully. file content %s
", fileName, content)
}Because the argument is untrusted, the program can read files outside its intended scope.
➜ os_root git:(main) ✗ pwd
/Users/hxzhouh/workspace/github/me/blog-example/go/go1.24/os_root
➜ os_root git:(main) ✗ ./main ../../../../../../../../../tmp/password
./../../../../../../../../../tmp/password
File ../../../../../../../../../tmp/password opened successfully. file content password123In Go 1.24, the new os.Root type and the core function os.OpenRoot open a directory and return an os.Root value. Methods on os.Root are allowed to operate only within that directory and reject paths that escape it, including those that follow symlinks.
os.Root methods prohibit accessing locations outside the directory hierarchy.
Rewriting the previous example using os.OpenRoot:
func main() {
fileName := os.Args[1]
root, err := os.OpenRoot(".")
if err != nil {
panic(err)
}
file, err := root.Open(fileName)
if err != nil {
fmt.Println(fmt.Sprintf("Error opening file %s: %s
", fileName, err.Error()))
return
}
content := make([]byte, 1024)
c, err := file.Read(content)
if err != nil {
panic(err)
}
content = content[:c]
fmt.Printf("File %s opened successfully. file content %s
", fileName, content)
}Running the rebuilt program:
➜ os_root git:(main) ✗ gotip version
go version devel go1.24-fafd447 Wed Dec 11 15:57:34 2024 -0800 darwin/arm64
➜ os_root git:(main) ✗ gotip build -o go1.24 main.go
➜ os_root git:(main) ✗ ./go1.24 ../../../../../../../../../tmp/password
Error opening file ../../../../../../../../../tmp/password: openat ../../../../../../../../../tmp/password: path escapes from parentIf the target file is outside the parent directory hierarchy, os.Root returns an error, effectively mitigating the traversal attack.
For more APIs, refer to the official documentation. After the 1.24 release, many third‑party libraries are expected to adopt this functionality, as similar features already exist in other languages.
Reference Links
Go 1.24 Release Notes: https://tip.golang.org/doc/go1.24
os.Root documentation: https://tip.golang.org/pkg/os#Root
CVE‑2024‑3400 details: https://nvd.nist.gov/vuln/detail/CVE-2024-3400
Official API docs: https://pkg.go.dev/os@master#Root
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
