1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
// SPDX-License-Identifier: MPL-2.0
//go:build linux
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package procfs_test
import (
"fmt"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
"github.com/cyphar/filepath-securejoin/pathrs-lite/procfs"
)
// This code is all actually tested in internal/procfs, this is mainly
// necessary to make sure our one-line wrappers are correct.
func TestOpenProcRoot(t *testing.T) {
t.Run("OpenProcRoot", func(t *testing.T) {
proc, err := procfs.OpenProcRoot()
require.NoError(t, err, "OpenProcRoot")
assert.NotNil(t, proc, "procfs *Handle")
assert.NoError(t, proc.Close(), "close handle")
})
t.Run("OpenUnsafeProcRoot", func(t *testing.T) {
proc, err := procfs.OpenUnsafeProcRoot()
require.NoError(t, err, "OpenUnsafeProcRoot")
assert.NotNil(t, proc, "procfs *Handle")
defer proc.Close() //nolint:errcheck // test code
// Make sure the handle actually is !subset=pid.
f, err := proc.OpenRoot(".")
require.NoError(t, err, "open root .")
err = fd.Faccessat(f, "uptime", unix.F_OK, unix.AT_SYMLINK_NOFOLLOW)
assert.NoError(t, err, "/proc/uptime should exist") //nolint:testifylint // this is an isolated operation so we can continue despite an error
assert.NoError(t, proc.Close(), "close handle")
})
}
type procRootFunc func() (*procfs.Handle, error)
func TestProcRoot(t *testing.T) {
for _, test := range []struct {
name string
procRootFn procRootFunc
}{
{"OpenProcRoot", procfs.OpenProcRoot},
{"OpenUnsafeProcRoot", procfs.OpenUnsafeProcRoot},
} {
test := test // copy iterator
t.Run(test.name, func(t *testing.T) {
proc, err := test.procRootFn()
require.NoError(t, err)
defer proc.Close() //nolint:errcheck // test code
t.Run("OpenThreadSelf", func(t *testing.T) {
// Make sure our tid checks below are correct.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
stat, closer, err := proc.OpenThreadSelf("stat")
require.NoError(t, err, "open /proc/thread-self/stat")
if assert.NotNil(t, closer, "closer should be non-nil for /proc/thread-self") {
defer closer()
}
require.NotNil(t, stat, "open /proc/thread-self/stat")
defer stat.Close() //nolint:errcheck // test code
statData, err := os.ReadFile(fmt.Sprintf("/proc/self/fd/%d", stat.Fd()))
runtime.KeepAlive(stat)
require.NoError(t, err)
assert.Regexp(t, fmt.Sprintf("^%d ", unix.Gettid()), string(statData), "/proc/thread-self/stat should have tid prefix")
// Confirm that this is /proc/$pid/task/$tid, not /proc/$pid.
f, closer, err := proc.OpenThreadSelf("task")
require.ErrorIs(t, err, os.ErrNotExist, "/proc/thread-self should not have a 'task' dir")
if !assert.Nil(t, closer, "returned closer on error") {
defer closer()
}
if !assert.Nil(t, f, "returned *os.File on error") {
_ = f.Close()
}
})
t.Run("OpenSelf", func(t *testing.T) {
stat, err := proc.OpenSelf("stat")
require.NoError(t, err, "open /proc/self/stat")
require.NotNil(t, stat, "open /proc/self/stat")
defer stat.Close() //nolint:errcheck // test code
statData, err := os.ReadFile(fmt.Sprintf("/proc/self/fd/%d", stat.Fd()))
runtime.KeepAlive(stat)
require.NoError(t, err)
assert.Regexp(t, fmt.Sprintf("^%d ", os.Getpid()), string(statData), "/proc/self/stat should have pid prefix")
// Confirm that this is /proc/$pid, not /proc/$pid/task/$tid.
f, err := proc.OpenSelf("task")
require.NoError(t, err, "/proc/self has a 'task' dir")
require.NotNil(t, f, "open /proc/self/task")
_ = f.Close()
})
t.Run("OpenPid", func(t *testing.T) {
stat, err := proc.OpenPid(1, "stat")
require.NoError(t, err, "open /proc/1/stat")
require.NotNil(t, stat, "open /proc/1/stat")
defer stat.Close() //nolint:errcheck // test code
statData, err := os.ReadFile(fmt.Sprintf("/proc/self/fd/%d", stat.Fd()))
runtime.KeepAlive(stat)
require.NoError(t, err)
assert.Regexp(t, "^1 ", string(statData), "/proc/1/stat should have pid1 prefix")
// Confirm that this is /proc/$pid, not /proc/$pid/task/$tid.
f, err := proc.OpenPid(1, "task")
require.NoError(t, err, "/proc/1 has a 'task' dir")
require.NotNil(t, f, "open /proc/1/task")
_ = f.Close()
})
t.Run("OpenRoot", func(t *testing.T) {
t.Skip("Debian: doesn't work reliably in lxc")
uptime, err := proc.OpenRoot("uptime")
require.NoError(t, err, "open /proc/uptime")
require.NotNil(t, uptime, "open /proc/uptime")
defer uptime.Close() //nolint:errcheck // test code
})
})
}
}
func TestProcSelfFdReadlink(t *testing.T) {
root, err := os.Open(".")
require.NoError(t, err)
fullPath, err := procfs.ProcSelfFdReadlink(root)
require.NoError(t, err, "ProcSelfFdReadlink")
cwd, err := os.Getwd()
require.NoError(t, err, "getwd")
cwd, err = filepath.EvalSymlinks(cwd)
require.NoError(t, err, "expand symlinks getwd")
assert.Equal(t, cwd, fullPath, "ProcSelfFdReadlink('.')")
}
|