-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
zap.Open: Invalidate relative paths and paths with ".." segments #1397
Conversation
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## master #1397 +/- ##
==========================================
+ Coverage 98.28% 98.34% +0.06%
==========================================
Files 53 53
Lines 3495 3511 +16
==========================================
+ Hits 3435 3453 +18
+ Misses 50 48 -2
Partials 10 10 ☔ View full report in Codecov by Sentry. |
Currently, zap.Open doesn't prevent someone from explicitly doing something like zap.Open("file://../../../secret"). zap.Open already documents that paths passed to it must be absolute. Add validation to error if zap.Open is called with a relative paths that could write files outside of intended file directory hierarchy. This change addresses https://cwe.mitre.org/data/definitions/23.html ref #1390
def8fda
to
332853b
Compare
func checkOpenPaths(paths []string) error { | ||
var openErr error | ||
for _, path := range paths { | ||
if !strings.HasPrefix(path, "file:") { | ||
continue | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just change the file://
sink instead?
This is how the sink is created based on scheme:
Lines 64 to 72 in 332853b
func newSinkRegistry() *sinkRegistry { | |
sr := &sinkRegistry{ | |
factories: make(map[string]func(*url.URL) (Sink, error)), | |
openFile: os.OpenFile, | |
} | |
// Infallible operation: the registry is empty, so we can't have a conflict. | |
_ = sr.RegisterSink(schemeFile, sr.newFileSinkFromURL) | |
return sr | |
} |
It registers the file://
sink by default.
So when a file://
URL is given, that calls newFileSinkFromURL,
which then calls newFileSinkFromPath:
Lines 150 to 159 in 332853b
func (sr *sinkRegistry) newFileSinkFromPath(path string) (Sink, error) { | |
switch path { | |
case "stdout": | |
return nopCloserSink{os.Stdout}, nil | |
case "stderr": | |
return nopCloserSink{os.Stderr}, nil | |
} | |
return sr.openFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o666) | |
} |
That's the function that finally calls os.OpenFile (through a function reference).
That's where we should have the validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just experimented and yes this location allows for the reuse of a lot more code.
if err != nil { | ||
return err | ||
} | ||
if !(filepath.IsAbs(u.Path) && !strings.Contains(u.Path, "..")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if filepath.IsAbs returns true, we're good. It's okay if an absolute path has /../
because that'll just modify the absolute path. /foo/bar/../baz
is just /foo/baz
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@abhinav, I think that means we don't need this feature then?
sr.openFile
, given a rawUrl with a file scheme, always consumes an absolute path because url.Parse
return object's Path field only contains an absolute path under this constraints. filepath.IsAbs
will always return true here. The case where rawUrl isn't passed through url.Parse is also gated by a filepath.IsAbs.
Experimentation:
https://go.dev/play/p/w2VtVnkbIb8
Arbitrary prefix sequences of double dot segments like will be treated as if these sequences of double dot segments were not provided.
sink, _, err := zap.Open("file:///../../Users/rhang/gocode/src/x/zapopen/file") // acts like file:///Users/rhang/...
If that make sense I can close the issue after adding test to confirm this behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@r-hang can we confirm with a test that it's not possible to provide a relative path or one with '..' to that API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#1398 as a successor since I'm not able to reopen this one.
Add validation to ensure file schema paths passed to zap.Open are absolute since this is already documented. Curently, file schema URIs with relative roots e.g. "file://../" are parsed to "" by url.Parse and lead to errors when opening a "" path. Additional validation makes this problem easier to correct. Tests are also added to demonstrate that double dot segements within file schema URIs passed to zap.Open remaining within the specified file hierarchy. This change addresses https://cwe.mitre.org/data/definitions/23.html ref #1390 This PR succeeds #1397
Currently, zap.Open doesn't prevent someone from explicitly doing something like zap.Open("file://../../../secret").
zap.Open already documents that paths passed to it must be absolute. Add validation to error if zap.Open is called with a relative paths that could write files outside of intended file directory hierarchy.
This change addresses https://cwe.mitre.org/data/definitions/23.html
ref #1390