From 12c61a3e3acb167688d37f9c6d9cb6b188b9dc2f Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Thu, 24 Aug 2023 13:09:46 +1000 Subject: [PATCH] mountinfo: linux: use /proc/thread-self/mountinfo 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/ 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 --- mountinfo/mountinfo_linux.go | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/mountinfo/mountinfo_linux.go b/mountinfo/mountinfo_linux.go index b9a14ef4..ea916575 100644 --- a/mountinfo/mountinfo_linux.go +++ b/mountinfo/mountinfo_linux.go @@ -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) { @@ -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// as a fallback. + f, err = os.Open("/proc/self/task/" + strconv.Itoa(unix.Gettid()) + "/mountinfo") + } if err != nil { return nil, err }