175. PathBuf::push — When an Absolute Argument Wipes Your Base Path
base.push(user_input) looks like string concatenation for paths. It isn’t — if user_input is absolute, the original base is gone.
The footgun
PathBuf::push reads almost like += for paths. Most of the time, it behaves that way:
| |
But the moment the pushed component is absolute, push throws the existing buffer away and starts over:
| |
That’s not a bug. The docs spell it out: “if path is absolute, it replaces the current path.” It mirrors how cd /etc/passwd works in a shell. The catch is that when one half of push is user input, the cd-like behavior turns into a path-traversal vector.
Why this bites
The most common shape of the bug:
| |
No panic, no error, no warning. The function just hands back a path that points somewhere else entirely.
The fix
Reject absolute components before joining. Path::is_absolute and Path::has_root are the two checks you need:
| |
has_root matters on Windows too — \windows\system32 has a root but no drive prefix, and push will replace the non-prefix portion of your buffer with it. is_absolute alone misses that case on Windows.
For full sandbox enforcement you also want to canonicalize and check the result is still under base — .. components can still escape — but stopping the absolute-path case is the cheap first line of defence.
Takeaway
PathBuf::push is not string concatenation. Treat any component you didn’t write yourself as suspect and gate it through is_absolute / has_root before letting it near your buffer.