Reading: Traversal-resistant file APIs

Go 1.24 provides a new file API os.Root which defense some kinds of path traversal attacks.

Path traversal attacks

The blog post introduced 3 situations of path traversal attacks

user control over the input file path

If the attacker controls part of the local filesystem, they may be able to use symbolic links to cause a program to access the wrong file:

// Attacker links /home/user/.config to /home/otheruser/.config:
err := os.WriteFile("/home/user/.config/foo", config, 0o666)

This attack reminds me of SQL injection attacks, in the similarity that the resource destination is a variable that can be manipulated by the attacker.

The XXS attack is another common one related to user input. I handled business requirements before, that allow users to execute their own JavaScript scripts on the platform itself, to achieve sort of “customization”. I think this is quite common in areas like low-code software. These kinds of use cases really need to be handled very carefully.

Programmers actually need to be aware of ANY kind of user input, as it is the entry point that welcomes attackers to interact with the system. See input validation attack

Given the ability to control part of the local filesystem, attack may create symlink points to a specific file.

Related reading:

time-of-check/time-of-use (TOCTOU) races

This is very interesting that I often think of the race condition on async calls, concurrent programming or parallel programming, but less under this kind of sequential situation. Often when importing and calling functions in sequence, I unconsciously think that it is “fast” enough and not possible to happen race in between the “checking” call and “using” call of the checking result.

Related readings:

Path sanitization

  • Go 1.20: filepath.IsLocal
  • Go 1.23: filepath.Localize

Beyond sanitization

  • Before Go 1.24: use github.com/google/safeopen to safely open untrusted file

os.Root

  1. Using os.Root struct to constrant the scope, isolation, for the specific directory.
  2. Life of Root can be controlled by os.OpenRoot and Root.Close method.
  3. os.OpenInRoot is convenient for opening file in specific directory, especially when the file path is likely untrusted.
  4. Opeartion of os.Root could be expensive, especially when there is ’..’ in the filename or the file is deeply nested.