Skip to content

Commit cf5a79a

Browse files
committed
merge #57 into cyphar/filepath-securejoin:main
Aleksa Sarai (1): procfs: remove caching of procfs root LGTMs: cyphar
2 parents 1553864 + 7916146 commit cf5a79a

File tree

6 files changed

+29
-53
lines changed

6 files changed

+29
-53
lines changed

CHANGELOG.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
9292
implement this in `filepath-securejoin` ([libpathrs][] supports all of this,
9393
of course).
9494

95-
[libpathrs]: https://github.com/openSUSE/libpathrs
95+
### Changed ###
96+
- The procfs root file descriptor is no longer cached for the lifetime of the
97+
process. This kind of global file descriptor caching has caused security
98+
issues in container runtimes before (see [CVE-2024-21626][] for an example),
99+
and so it seems prudent to avoid it. This mirrors [a similar change made to
100+
libpathrs][libpathrs-pr204].
101+
102+
[libpathrs]: https://github.com/cyphar/libpathrs
103+
[libpathrs-pr204]: https://github.com/cyphar/libpathrs/pull/204
96104
[statx.2]: https://www.man7.org/linux/man-pages/man2/statx.2.html
97105

98106
## [0.4.1] - 2025-01-28 ##
@@ -262,7 +270,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
262270
safe to start migrating to as we have extensive tests ensuring they behave
263271
correctly and are safe against various races and other attacks.
264272

265-
[libpathrs]: https://github.com/openSUSE/libpathrs
273+
[libpathrs]: https://github.com/cyphar/libpathrs
266274
[open.2]: https://www.man7.org/linux/man-pages/man2/open.2.html
267275

268276
## [0.2.5] - 2024-05-03 ##

gocompat_generics_go121.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,3 @@ func slices_Clone[S ~[]E, E any](slice S) S { //nolint:revive // name is meant t
2626
func sync_OnceValue[T any](f func() T) func() T { //nolint:revive // name is meant to mirror stdlib
2727
return sync.OnceValue(f)
2828
}
29-
30-
func sync_OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { //nolint:revive // name is meant to mirror stdlib
31-
return sync.OnceValues(f)
32-
}

gocompat_generics_unsupported.go

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -93,32 +93,3 @@ func sync_OnceValue[T any](f func() T) func() T { //nolint:revive // name is mea
9393
return result
9494
}
9595
}
96-
97-
// Copied from the Go 1.24 stdlib implementation.
98-
func sync_OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { //nolint:revive // name is meant to mirror stdlib
99-
var (
100-
once sync.Once
101-
valid bool
102-
p any
103-
r1 T1
104-
r2 T2
105-
)
106-
g := func() {
107-
defer func() {
108-
p = recover()
109-
if !valid {
110-
panic(p)
111-
}
112-
}()
113-
r1, r2 = f()
114-
f = nil
115-
valid = true
116-
}
117-
return func() (T1, T2) {
118-
once.Do(g)
119-
if !valid {
120-
panic(p)
121-
}
122-
return r1, r2
123-
}
124-
}

open_linux.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func Reopen(handle *os.File, flags int) (*os.File, error) {
6767
if err != nil {
6868
return nil, err
6969
}
70+
defer procRoot.Close() //nolint:errcheck // close failures aren't critical here
7071

7172
// We can't operate on /proc/thread-self/fd/$n directly when doing a
7273
// re-open, so we need to open /proc/thread-self/fd and then open a single

procfs_linux.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ func unsafeHostProcRoot() (_ *os.File, Err error) {
189189
return procRoot, nil
190190
}
191191

192-
func doGetProcRoot() (*os.File, error) {
192+
func getProcRoot() (*os.File, error) {
193193
procRoot, err := privateProcRoot()
194194
if err != nil {
195195
// Fall back to using a /proc handle if making a private mount failed.
@@ -200,10 +200,6 @@ func doGetProcRoot() (*os.File, error) {
200200
return procRoot, err
201201
}
202202

203-
var getProcRoot = sync_OnceValues(func() (*os.File, error) {
204-
return doGetProcRoot()
205-
})
206-
207203
var hasProcThreadSelf = sync_OnceValue(func() bool {
208204
return unix.Access("/proc/thread-self/", unix.F_OK) == nil
209205
})
@@ -226,6 +222,7 @@ func procThreadSelf(procRoot *os.File, subpath string) (_ *os.File, _ ProcThread
226222
return nil, nil, err
227223
}
228224
procRoot = root
225+
defer procRoot.Close() //nolint:errcheck // close failures aren't critical here
229226
}
230227

231228
// We need to lock our thread until the caller is done with the handle
@@ -295,6 +292,7 @@ func ProcPid(pid int, subpath string) (*os.File, error) {
295292
if err != nil {
296293
return nil, err
297294
}
295+
defer procRoot.Close() //nolint:errcheck // close failures aren't critical here
298296

299297
handle, err := procfsLookupInRoot(procRoot, strconv.Itoa(pid)+"/"+subpath)
300298
if err != nil {
@@ -407,6 +405,7 @@ func rawProcSelfFdReadlink(fd int) (string, error) {
407405
if err != nil {
408406
return "", err
409407
}
408+
defer procRoot.Close() //nolint:errcheck // close failures aren't critical here
410409
return doRawProcSelfFdReadlink(procRoot, fd)
411410
}
412411

procfs_linux_test.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,14 @@ func testProcOvermountSubdir(t *testing.T, procRootFn procRootFunc, expectOvermo
164164
require.NoError(t, err)
165165
defer procExe.Close() //nolint:errcheck // test code
166166

167-
// Get the handle that doProcThreadSelf is using internally when we
168-
// test the external API.
169167
testProcRoot := procRoot
170168
if testProcRoot == nil {
171-
testProcRoot, err = getProcRoot()
169+
// If using the external API, we cannot get the procfs handled used
170+
// directly (because it gets created anew for each operation), so
171+
// instead just reuse procSelf for this.
172+
testProcRoot, err = openatFile(procSelf, "../../..", unix.O_PATH, 0)
172173
require.NoError(t, err)
173-
// do not call testProcRoot.Close() -- it's a global handle
174+
defer testProcRoot.Close() //nolint:errcheck // test code
174175
}
175176

176177
// no overmount
@@ -222,22 +223,22 @@ func TestProcOvermountSubdir_clonePrivateProcMount(t *testing.T) {
222223
})
223224
}
224225

225-
func TestProcOvermountSubdir_doGetProcRoot(t *testing.T) {
226+
func TestProcOvermountSubdir_getProcRoot(t *testing.T) {
226227
withWithoutOpenat2(t, true, func(t *testing.T) {
227228
// We expect to not get overmounts if we have the new mount API.
228229
// FIXME: It's possible to hit overmounts if there are locked mounts
229230
// and we hit the AT_RECURSIVE case...
230-
testProcOvermountSubdir(t, doGetProcRoot, !hasNewMountAPI())
231+
testProcOvermountSubdir(t, getProcRoot, !hasNewMountAPI())
231232
})
232233
}
233234

234-
func TestProcOvermountSubdir_doGetProcRoot_Mocked(t *testing.T) {
235+
func TestProcOvermountSubdir_getProcRoot_Mocked(t *testing.T) {
235236
if !hasNewMountAPI() {
236237
t.Skip("test requires fsopen/open_tree support")
237238
}
238239
withWithoutOpenat2(t, true, func(t *testing.T) {
239240
testForceGetProcRoot(t, func(t *testing.T, expectOvermounts bool) {
240-
testProcOvermountSubdir(t, doGetProcRoot, expectOvermounts)
241+
testProcOvermountSubdir(t, getProcRoot, expectOvermounts)
241242
})
242243
})
243244
}
@@ -246,7 +247,7 @@ func TestProcOvermountSubdir_ProcThreadSelf(t *testing.T) {
246247
withWithoutOpenat2(t, true, func(t *testing.T) {
247248
testForceProcThreadSelf(t, func(t *testing.T) {
248249
dummyGetRoot := func() (*os.File, error) { return nil, nil } //nolint:nilnil // intentional
249-
// Use the same overmounts policy as doGetProcRoot.
250+
// Use the same overmounts policy as getProcRoot.
250251
testProcOvermountSubdir(t, dummyGetRoot, !hasNewMountAPI())
251252
})
252253
})
@@ -454,18 +455,18 @@ func TestProcOvermount_newPrivateProcMount(t *testing.T) {
454455
testProcOvermount(t, newPrivateProcMount, true)
455456
}
456457

457-
func TestProcOvermount_doGetProcRoot(t *testing.T) {
458+
func TestProcOvermount_getProcRoot(t *testing.T) {
458459
privateProcMount := canFsOpen() && !testingForcePrivateProcRootOpenTree(nil)
459-
testProcOvermount(t, doGetProcRoot, privateProcMount)
460+
testProcOvermount(t, getProcRoot, privateProcMount)
460461
}
461462

462-
func TestProcOvermount_doGetProcRoot_Mocked(t *testing.T) {
463+
func TestProcOvermount_getProcRoot_Mocked(t *testing.T) {
463464
if !hasNewMountAPI() {
464465
t.Skip("test requires fsopen/open_tree support")
465466
}
466467
testForceGetProcRoot(t, func(t *testing.T, _ bool) {
467468
privateProcMount := canFsOpen() && !testingForcePrivateProcRootOpenTree(nil)
468-
testProcOvermount(t, doGetProcRoot, privateProcMount)
469+
testProcOvermount(t, getProcRoot, privateProcMount)
469470
})
470471
}
471472

0 commit comments

Comments
 (0)