Skip to content

Commit ba41f97

Browse files
committed
zap.Open: Invalidate relative path roots
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
1 parent d27427d commit ba41f97

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

Diff for: sink.go

+5
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ func (sr *sinkRegistry) newSink(rawURL string) (Sink, error) {
103103
if err != nil {
104104
return nil, fmt.Errorf("can't parse %q as a URL: %v", rawURL, err)
105105
}
106+
107+
if u.Scheme == schemeFile && !filepath.IsAbs(u.Path) {
108+
return nil, fmt.Errorf("file URI %q attempts a relative path", rawURL)
109+
}
110+
106111
if u.Scheme == "" {
107112
u.Scheme = schemeFile
108113
}

Diff for: writer_test.go

+86
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,92 @@ func TestOpenOtherErrors(t *testing.T) {
224224
}
225225
}
226226

227+
func TestOpenRelativeValidated(t *testing.T) {
228+
tests := []struct {
229+
msg string
230+
paths []string
231+
wantErr string
232+
}{
233+
{
234+
msg: "invalid relative path root",
235+
paths: []string{
236+
"file:../some/path",
237+
},
238+
// url.Parse's Path for this path value is "" which would result
239+
// in a file not found error if not validated.
240+
wantErr: `open sink "file:../some/path": file URI "file:../some/path" attempts a relative path`,
241+
},
242+
{
243+
msg: "invalid double dot as the host element",
244+
paths: []string{
245+
"file://../some/path",
246+
},
247+
wantErr: `open sink "file://../some/path": file URLs must leave host empty or use localhost: got file://../some/path`,
248+
},
249+
}
250+
251+
for _, tt := range tests {
252+
t.Run(tt.msg, func(t *testing.T) {
253+
_, _, err := Open(tt.paths...)
254+
assert.EqualError(t, err, tt.wantErr)
255+
})
256+
}
257+
}
258+
259+
func TestOpenDotSegmentsSanitized(t *testing.T) {
260+
tempName := filepath.Join(t.TempDir(), "test.log")
261+
assert.False(t, fileExists(tempName))
262+
require.True(t, filepath.IsAbs(tempName), "Expected absolute temp file path.")
263+
264+
tests := []struct {
265+
msg string
266+
paths []string
267+
toWrite []byte
268+
wantFileContents string
269+
}{
270+
{
271+
msg: "no hostname one double dot segment",
272+
paths: []string{"file:/.." + tempName},
273+
toWrite: []byte("a"),
274+
wantFileContents: "a",
275+
},
276+
{
277+
msg: "no hostname two double dot segments",
278+
paths: []string{"file:/../.." + tempName},
279+
toWrite: []byte("b"),
280+
wantFileContents: "ab",
281+
},
282+
{
283+
msg: "empty host name one double dot segment",
284+
paths: []string{"file:///.." + tempName},
285+
toWrite: []byte("c"),
286+
wantFileContents: "abc",
287+
},
288+
{
289+
msg: "empty hostname two double dot segments",
290+
paths: []string{"file:///../.." + tempName},
291+
toWrite: []byte("d"),
292+
wantFileContents: "abcd",
293+
},
294+
}
295+
296+
for _, tt := range tests {
297+
t.Run(tt.msg, func(t *testing.T) {
298+
ws, cleanup, err := Open(tt.paths...)
299+
require.NoError(t, err)
300+
defer cleanup()
301+
302+
_, err = ws.Write(tt.toWrite)
303+
require.NoError(t, err)
304+
305+
b, err := os.ReadFile(tempName)
306+
require.NoError(t, err)
307+
308+
assert.Equal(t, string(b), tt.wantFileContents)
309+
})
310+
}
311+
}
312+
227313
type testWriter struct {
228314
expected string
229315
t testing.TB

0 commit comments

Comments
 (0)