Skip to content

Commit

Permalink
mountinfo: linux: use /proc/thread-self/mountinfo
Browse files Browse the repository at this point in the history
In a Go program with many threads, which goroutine is running on the
thread-group leader thread is not something programs can controls. Thus,
even if you use runtime.LockOSThread() for threads that have different
mount namespaces, it's possible that /proc/self will refer to the
"wrong" thread.

The solution is to simply use /proc/thread-self, which will always
provide the correct result for the calling thread. For pre-3.17 kernels
we use /proc/self/task/<tid> as a fallback.

The usage of /proc/self/mountinfo caused isuses for a patch to runc
which creates a thread to create id-mapped mounts (which requires
joining the container mount namespace).

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
  • Loading branch information
cyphar committed Oct 27, 2023
1 parent c0711cd commit 12c61a3
Showing 1 changed file with 31 additions and 3 deletions.
34 changes: 31 additions & 3 deletions mountinfo/mountinfo_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@ import (
"fmt"
"io"
"os"
"runtime"
"strconv"
"strings"
"sync"

"golang.org/x/sys/unix"
)

// GetMountsFromReader retrieves a list of mounts from the
// reader provided, with an optional filter applied (use nil
// for no filter). This can be useful in tests or benchmarks
// that provide fake mountinfo data, or when a source other
// than /proc/self/mountinfo needs to be read from.
// than /proc/thread-self/mountinfo needs to be read from.
//
// This function is Linux-specific.
func GetMountsFromReader(r io.Reader, filter FilterFunc) ([]*Info, error) {
Expand Down Expand Up @@ -127,8 +131,32 @@ func GetMountsFromReader(r io.Reader, filter FilterFunc) ([]*Info, error) {
return out, nil
}

func parseMountTable(filter FilterFunc) ([]*Info, error) {
f, err := os.Open("/proc/self/mountinfo")
var (
haveProcThreadSelf bool
haveProcThreadSelfOnce sync.Once
)

func parseMountTable(filter FilterFunc) (_ []*Info, err error) {
haveProcThreadSelfOnce.Do(func() {
_, err := os.Stat("/proc/thread-self/mountinfo")
haveProcThreadSelf = err == nil
})

// We need to lock ourselves to the current OS thread in order to make sure
// that the thread referenced by /proc/thread-self stays alive until we
// finish parsing the file.
runtime.LockOSThread()
defer runtime.UnlockOSThread()

var f *os.File
if haveProcThreadSelf {
f, err = os.Open("/proc/thread-self/mountinfo")
} else {
// On pre-3.17 kernels (such as CentOS 7), we don't have
// /proc/thread-self/ so we need to manually construct
// /proc/self/task/<tid>/ as a fallback.
f, err = os.Open("/proc/self/task/" + strconv.Itoa(unix.Gettid()) + "/mountinfo")
}
if err != nil {
return nil, err
}
Expand Down

0 comments on commit 12c61a3

Please sign in to comment.