Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions go-selinux/selinux.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,15 @@ func CalculateGlbLub(sourceRange, targetRange string) (string, error) {
// of the program is finished to guarantee another goroutine does not migrate to the current
// thread before execution is complete.
func SetExecLabel(label string) error {
return writeCon(attrPath("exec"), label)
return writeConThreadSelf("attr/exec", label)
}

// SetTaskLabel sets the SELinux label for the current thread, or an error.
// This requires the dyntransition permission. Calls to SetTaskLabel should
// be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() to guarantee
// the current thread does not run in a new mislabeled thread.
func SetTaskLabel(label string) error {
return writeCon(attrPath("current"), label)
return writeConThreadSelf("attr/current", label)
}

// SetSocketLabel takes a process label and tells the kernel to assign the
Expand All @@ -170,12 +170,12 @@ func SetTaskLabel(label string) error {
// the socket is created to guarantee another goroutine does not migrate
// to the current thread before execution is complete.
func SetSocketLabel(label string) error {
return writeCon(attrPath("sockcreate"), label)
return writeConThreadSelf("attr/sockcreate", label)
}

// SocketLabel retrieves the current socket label setting
func SocketLabel() (string, error) {
return readCon(attrPath("sockcreate"))
return readConThreadSelf("attr/sockcreate")
}

// PeerLabel retrieves the label of the client on the other side of a socket
Expand All @@ -198,7 +198,7 @@ func SetKeyLabel(label string) error {

// KeyLabel retrieves the current kernel keyring label setting
func KeyLabel() (string, error) {
return readCon("/proc/self/attr/keycreate")
return keyLabel()
}

// Get returns the Context as a string
Expand Down
266 changes: 183 additions & 83 deletions go-selinux/selinux_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ import (
"strings"
"sync"

"github.com/opencontainers/selinux/pkg/pwalkdir"
"github.com/cyphar/filepath-securejoin/pathrs-lite"
"github.com/cyphar/filepath-securejoin/pathrs-lite/procfs"
"golang.org/x/sys/unix"

"github.com/opencontainers/selinux/pkg/pwalkdir"
)

const (
Expand Down Expand Up @@ -73,10 +76,6 @@ var (
mcsList: make(map[string]bool),
}

// for attrPath()
attrPathOnce sync.Once
haveThreadSelf bool

// for policyRoot()
policyRootOnce sync.Once
policyRootVal string
Expand Down Expand Up @@ -256,48 +255,183 @@ func readConfig(target string) string {
return ""
}

func isProcHandle(fh *os.File) error {
var buf unix.Statfs_t
func readConFd(in *os.File) (string, error) {
data, err := io.ReadAll(in)
if err != nil {
return "", err
}
return string(bytes.TrimSuffix(data, []byte{0})), nil
}

for {
err := unix.Fstatfs(int(fh.Fd()), &buf)
if err == nil {
break
}
if err != unix.EINTR {
return &os.PathError{Op: "fstatfs", Path: fh.Name(), Err: err}
}
func writeConFd(out *os.File, val string) error {
var err error
if val != "" {
_, err = out.Write([]byte(val))
} else {
_, err = out.Write(nil)
}
if buf.Type != unix.PROC_SUPER_MAGIC {
return fmt.Errorf("file %q is not on procfs", fh.Name())
return err
}

// openProcThreadSelf is a small wrapper around [procfs.Handle.OpenThreadSelf]
// and [pathrs.Reopen] to make "one-shot opens" slightly more ergonomic. The
// provided mode must be os.O_* flags to indicate what mode the returned file
// should be opened with (flags like os.O_CREAT and os.O_EXCL are not
// supported).
//
// If no error occurred, the returned handle is guaranteed to be exactly
// /proc/thread-self/<subpath> with no tricky mounts or symlinks causing you to
// operate on an unexpected path (with some caveats on pre-openat2 or
// pre-fsopen kernels).
func openProcThreadSelf(subpath string, mode int) (*os.File, procfs.ProcThreadSelfCloser, error) {
if subpath == "" {
return nil, nil, ErrEmptyPath
}

return nil
}
proc, err := procfs.OpenProcRoot()
if err != nil {
return nil, nil, err
}
defer proc.Close()

func readCon(fpath string) (string, error) {
if fpath == "" {
return "", ErrEmptyPath
handle, closer, err := proc.OpenThreadSelf(subpath)
if err != nil {
return nil, nil, fmt.Errorf("open /proc/thread-self/%s handle: %w", subpath, err)
}
defer handle.Close() // we will return a re-opened handle

file, err := pathrs.Reopen(handle, mode)
if err != nil {
closer()
return nil, nil, fmt.Errorf("reopen /proc/thread-self/%s handle (%#x): %w", subpath, mode, err)
}
return file, closer, nil
}

in, err := os.Open(fpath)
// Read the contents of /proc/thread-self/<fpath>.
func readConThreadSelf(fpath string) (string, error) {
in, closer, err := openProcThreadSelf(fpath, os.O_RDONLY|unix.O_CLOEXEC)
if err != nil {
return "", err
}
defer closer()
defer in.Close()

if err := isProcHandle(in); err != nil {
return readConFd(in)
}

// Write <val> to /proc/thread-self/<fpath>.
func writeConThreadSelf(fpath, val string) error {
if val == "" {
if !getEnabled() {
return nil
}
}

out, closer, err := openProcThreadSelf(fpath, os.O_WRONLY|unix.O_CLOEXEC)
if err != nil {
return err
}
defer closer()
defer out.Close()

return writeConFd(out, val)
}

// openProcSelf is a small wrapper around [procfs.Handle.OpenSelf] and
// [pathrs.Reopen] to make "one-shot opens" slightly more ergonomic. The
// provided mode must be os.O_* flags to indicate what mode the returned file
// should be opened with (flags like os.O_CREAT and os.O_EXCL are not
// supported).
//
// If no error occurred, the returned handle is guaranteed to be exactly
// /proc/self/<subpath> with no tricky mounts or symlinks causing you to
// operate on an unexpected path (with some caveats on pre-openat2 or
// pre-fsopen kernels).
func openProcSelf(subpath string, mode int) (*os.File, error) {
if subpath == "" {
return nil, ErrEmptyPath
}

proc, err := procfs.OpenProcRoot()
if err != nil {
return nil, err
}
defer proc.Close()

handle, err := proc.OpenSelf(subpath)
if err != nil {
return nil, fmt.Errorf("open /proc/self/%s handle: %w", subpath, err)
}
defer handle.Close() // we will return a re-opened handle

file, err := pathrs.Reopen(handle, mode)
if err != nil {
return nil, fmt.Errorf("reopen /proc/self/%s handle (%#x): %w", subpath, mode, err)
}
return file, nil
}

// Read the contents of /proc/self/<fpath>.
func readConSelf(fpath string) (string, error) {
in, err := openProcSelf(fpath, os.O_RDONLY|unix.O_CLOEXEC)
if err != nil {
return "", err
}
defer in.Close()

return readConFd(in)
}

func readConFd(in *os.File) (string, error) {
data, err := io.ReadAll(in)
// Write <val> to /proc/self/<fpath>.
func writeConSelf(fpath, val string) error {
if val == "" {
if !getEnabled() {
return nil
}
}

out, err := openProcSelf(fpath, os.O_WRONLY|unix.O_CLOEXEC)
if err != nil {
return "", err
return err
}
return string(bytes.TrimSuffix(data, []byte{0})), nil
defer out.Close()

return writeConFd(out, val)
}

// openProcPid is a small wrapper around [procfs.Handle.OpenPid] and
// [pathrs.Reopen] to make "one-shot opens" slightly more ergonomic. The
// provided mode must be os.O_* flags to indicate what mode the returned file
// should be opened with (flags like os.O_CREAT and os.O_EXCL are not
// supported).
//
// If no error occurred, the returned handle is guaranteed to be exactly
// /proc/self/<subpath> with no tricky mounts or symlinks causing you to
// operate on an unexpected path (with some caveats on pre-openat2 or
// pre-fsopen kernels).
func openProcPid(pid int, subpath string, mode int) (*os.File, error) {
if subpath == "" {
return nil, ErrEmptyPath
}

proc, err := procfs.OpenProcRoot()
if err != nil {
return nil, err
}
defer proc.Close()

handle, err := proc.OpenPid(pid, subpath)
if err != nil {
return nil, fmt.Errorf("open /proc/%d/%s handle: %w", pid, subpath, err)
}
defer handle.Close() // we will return a re-opened handle

file, err := pathrs.Reopen(handle, mode)
if err != nil {
return nil, fmt.Errorf("reopen /proc/%d/%s handle (%#x): %w", pid, subpath, mode, err)
}
return file, nil
}

// classIndex returns the int index for an object class in the loaded policy,
Expand Down Expand Up @@ -393,78 +527,34 @@ func lFileLabel(fpath string) (string, error) {
}

func setFSCreateLabel(label string) error {
return writeCon(attrPath("fscreate"), label)
return writeConThreadSelf("attr/fscreate", label)
}

// fsCreateLabel returns the default label the kernel which the kernel is using
// for file system objects created by this task. "" indicates default.
func fsCreateLabel() (string, error) {
return readCon(attrPath("fscreate"))
return readConThreadSelf("attr/fscreate")
}

// currentLabel returns the SELinux label of the current process thread, or an error.
func currentLabel() (string, error) {
return readCon(attrPath("current"))
return readConThreadSelf("attr/current")
}

// pidLabel returns the SELinux label of the given pid, or an error.
func pidLabel(pid int) (string, error) {
return readCon(fmt.Sprintf("/proc/%d/attr/current", pid))
it, err := openProcPid(pid, "attr/current", os.O_RDONLY|unix.O_CLOEXEC)
if err != nil {
return "", nil
}
defer it.Close()
return readConFd(it)
}

// ExecLabel returns the SELinux label that the kernel will use for any programs
// that are executed by the current process thread, or an error.
func execLabel() (string, error) {
return readCon(attrPath("exec"))
}

func writeCon(fpath, val string) error {
if fpath == "" {
return ErrEmptyPath
}
if val == "" {
if !getEnabled() {
return nil
}
}

out, err := os.OpenFile(fpath, os.O_WRONLY, 0)
if err != nil {
return err
}
defer out.Close()

if err := isProcHandle(out); err != nil {
return err
}

if val != "" {
_, err = out.Write([]byte(val))
} else {
_, err = out.Write(nil)
}
if err != nil {
return err
}
return nil
}

func attrPath(attr string) string {
// Linux >= 3.17 provides this
const threadSelfPrefix = "/proc/thread-self/attr"

attrPathOnce.Do(func() {
st, err := os.Stat(threadSelfPrefix)
if err == nil && st.Mode().IsDir() {
haveThreadSelf = true
}
})

if haveThreadSelf {
return filepath.Join(threadSelfPrefix, attr)
}

return filepath.Join("/proc/self/task", strconv.Itoa(unix.Gettid()), "attr", attr)
return readConThreadSelf("exec")
}

// canonicalizeContext takes a context string and writes it to the kernel
Expand Down Expand Up @@ -728,7 +818,9 @@ func peerLabel(fd uintptr) (string, error) {
// setKeyLabel takes a process label and tells the kernel to assign the
// label to the next kernel keyring that gets created
func setKeyLabel(label string) error {
err := writeCon("/proc/self/attr/keycreate", label)
// Rather than using /proc/thread-self, we want to use /proc/self to
// operate on the thread-group leader.
err := writeConSelf("attr/keycreate", label)
if errors.Is(err, os.ErrNotExist) {
return nil
}
Expand All @@ -741,6 +833,14 @@ func setKeyLabel(label string) error {
return err
}

// KeyLabel retrieves the current kernel keyring label setting for this
// thread-group.
func keyLabel() (string, error) {
// Rather than using /proc/thread-self, we want to use /proc/self to
// operate on the thread-group leader.
return readConSelf("attr/keycreate")
}

// get returns the Context as a string
func (c Context) get() string {
if l := c["level"]; l != "" {
Expand Down
Loading