Skip to content

Commit 1a81a8a

Browse files
committed
proc: add pure-resolver sanity tests
These are somewhat adapted from the sanity tests for the libpathrs resolver, but somewhat less extensive (the libpathrs resolver needs to handle one-shot O_* flags, which we don't support in filepath-securejoin). Signed-off-by: Aleksa Sarai <[email protected]>
1 parent 894399a commit 1a81a8a

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

procfs_lookup_linux_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//go:build linux
2+
3+
// Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package securejoin
8+
9+
import (
10+
"fmt"
11+
"os"
12+
"testing"
13+
14+
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
func TestProcfsLookupInRoot(t *testing.T) {
19+
withWithoutOpenat2(t, true, func(t *testing.T) {
20+
// NOTE: We don't actually need root for unsafeHostProcRoot, but we
21+
// can't test for that because Go doesn't let you compare function
22+
// pointers...
23+
requireRoot(t)
24+
25+
// The openat2 and non-openat2 backends return different error
26+
// messages for the breakout case (".." and suspected magic-links).
27+
// The main issue is that openat2 just returns -EXDEV and returning
28+
// errUnsafeProcfs in all cases of the fallback resolver (for
29+
// consistency) doesn't make much sense.
30+
breakoutErr := errPossibleBreakout
31+
if hasOpenat2() {
32+
breakoutErr = errUnsafeProcfs
33+
}
34+
35+
for _, test := range []struct {
36+
name string
37+
root, subpath string
38+
expectedPath string
39+
expectedErr error
40+
}{
41+
{"nonproc-xdev", "/", "proc", "", errUnsafeProcfs},
42+
{"proc-nonroot", "/proc/tty", ".", "", errUnsafeProcfs},
43+
{"proc-emptypath", "/proc", "", "/proc", nil},
44+
{"proc-root-dotdot", "/proc", "1/../..", "", breakoutErr},
45+
{"proc-root-dotdot-top", "/proc", "..", "", breakoutErr},
46+
{"proc-abs-slash", "/proc", "/", "", breakoutErr},
47+
{"proc-abs-path", "/proc", "/etc/passwd", "", breakoutErr},
48+
// {"dotdot", "1/..", breakoutErr}, // only errors out for fallback resolver
49+
{"proc-uptime", "/proc", "uptime", "/proc/uptime", nil},
50+
{"proc-sys-kernel-arch", "/proc", "sys/kernel/arch", "/proc/sys/kernel/arch", nil},
51+
{"proc-symlink-nofollow", "/proc", "self", "/proc/self", nil},
52+
{"proc-symlink-follow", "/proc", "self/.", fmt.Sprintf("/proc/%d", os.Getpid()), nil},
53+
{"proc-self-attr", "/proc", "self/attr/apparmor/exec", fmt.Sprintf("/proc/%d/attr/apparmor/exec", os.Getpid()), nil},
54+
{"proc-magiclink-nofollow", "/proc", "self/exe", fmt.Sprintf("/proc/%d/exe", os.Getpid()), nil},
55+
{"proc-magiclink-follow", "/proc", "self/cwd/.", "", breakoutErr},
56+
} {
57+
test := test // copy iterator
58+
t.Run(test.name, func(t *testing.T) {
59+
root, err := os.Open(test.root)
60+
require.NoError(t, err, "open procfs resolver root")
61+
62+
handle, err := procfsLookupInRoot(root, test.subpath)
63+
assert.ErrorIsf(t, err, test.expectedErr, "procfsLookupInRoot(%q)", test.subpath) //nolint:testifylint // this is an isolated operation so we can continue despite an error
64+
if handle != nil {
65+
handlePath, err := ProcSelfFdReadlink(handle)
66+
require.NoError(t, err, "ProcSelfFdReadlink handle")
67+
assert.Equal(t, test.expectedPath, handlePath, "ProcSelfFdReadlink of handle")
68+
_ = handle.Close()
69+
}
70+
})
71+
}
72+
})
73+
}

0 commit comments

Comments
 (0)