Skip to content

Commit 9cf74e4

Browse files
committed
Fix for Sysbox issue #712.
The problem manifested itself when Docker Engine (>= v24.0), running inside a Sysbox container, created a bind-mount using a source path such as /proc/self/task/<tid>/ns/net. That mount unexpectedly failed with EPERM. Turns out the problem was caused by a bug in sysbox-fs' mount syscall interception, where it was not properly resolving mount paths such as "/proc/self/task/<tid>/...". The reason is that <tid> is a thread-ID in the container's pid-namespace, not in sysbox's pid-namespace, so sysbox-fs could not resolve it properly in function process.ResolveProcSelf() (i.e., when it did stat() on the path it failed). I've not found a proper way to translate the <tid> from the container's pid-ns to Sysbox's pid-ns. That would have been the proper fix. For now, this commit works around the problem by assuming that <tid> = <pid>; that's not ideal, but it's usually (likely always) the case for mount syscalls we normally intercept. Signed-off-by: Cesar Talledo <cesar.talledo@docker.com>
1 parent 31f6da4 commit 9cf74e4

File tree

3 files changed

+67
-8
lines changed

3 files changed

+67
-8
lines changed

process/process.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,23 @@ func (p *process) getStatus(fields []string) error {
632632
return nil
633633
}
634634

635+
// Replaces the given path such as "/proc/self/*" with "/proc/<pid>/*", or
636+
// "/proc/self/task/<tid>/*" with "/proc/<pid>/task/<tid>/*".
637+
func replaceProcSelfWithProcPid(path string, pid uint32, tid uint32) string {
638+
var repl, p string
639+
640+
pidMatch := regexp.MustCompile(`^/proc/self/(.*)`)
641+
tidMatch := regexp.MustCompile(`^/proc/self/task/[0-9]+/(.*)`)
642+
643+
repl = fmt.Sprintf("/proc/self/task/%d/${1}", tid)
644+
p = tidMatch.ReplaceAllString(path, repl)
645+
646+
repl = fmt.Sprintf("/proc/%d/${1}", pid)
647+
p = pidMatch.ReplaceAllString(p, repl)
648+
649+
return p
650+
}
651+
635652
// Given a path "/proc/self/path/to/symlink" it resolves it to the location
636653
// pointed to by symlink. For example, if path is "/proc/self/fd/3" and
637654
// "/proc/self/fd/3" is a symlink to "/some/path", then this function returns
@@ -643,8 +660,16 @@ func (p *process) getStatus(fields []string) error {
643660

644661
func (p *process) ResolveProcSelf(path string) (string, error) {
645662

646-
// TODO: this function is not capable of dealing with relative paths
647-
// "../../proc/self/fd/X". It also assumes procfs is mounted on /proc.
663+
// NOTE: this function assumes procfs is mounted on /proc and path is
664+
// absolute.
665+
666+
if !filepath.IsAbs(path) {
667+
return path, nil
668+
}
669+
670+
if !strings.HasPrefix(path, "/proc/self/") {
671+
return path, nil
672+
}
648673

649674
currPath := path
650675
linkCnt := 0
@@ -654,6 +679,17 @@ func (p *process) ResolveProcSelf(path string) (string, error) {
654679
break
655680
}
656681

682+
// Note: for paths such as /proc/self/task/<tid>/*, it's easy to replace
683+
// /proc/self with /proc/<pid> since we have the container's process pid
684+
// in sysbox's pid-namespace. However, that's not the case for the <tid>,
685+
// which is in the container's pid namespace and we have no good/easy way
686+
// to translate it sysbox's pid-ns. For now, assume that <tid> = <pid>.
687+
// It's not ideal, but it's normally the case when we receive such paths in
688+
// mount syscalls.
689+
690+
tid := p.pid
691+
currPath = replaceProcSelfWithProcPid(currPath, p.pid, tid)
692+
657693
fi, err := os.Lstat(currPath)
658694
if err != nil {
659695
return "", err
@@ -669,12 +705,8 @@ func (p *process) ResolveProcSelf(path string) (string, error) {
669705
return "", syscall.ELOOP
670706
}
671707

672-
// path starts with "/proc/self/" and is a symlink, proceed to resolve it
673-
674-
procPid := fmt.Sprintf("/proc/%d/", p.pid)
675-
procPidPath := strings.Replace(currPath, "/proc/self/", procPid, 1)
676-
677-
currPath, err = os.Readlink(procPidPath)
708+
// path starts with "/proc/self/" and it's a symlink, resolve it
709+
currPath, err = os.Readlink(currPath)
678710
if err != nil {
679711
return "", err
680712
}

process/process_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,31 @@ func TestPathAccessSymlink(t *testing.T) {
920920
}
921921
}
922922

923+
func TestReplaceProcSelfWithProcPid(t *testing.T) {
924+
925+
type testInput struct {
926+
path string
927+
pid uint32
928+
tid uint32
929+
}
930+
931+
test := map[testInput]string{
932+
{"/proc/self/mem", 10, 100}: "/proc/10/mem",
933+
{"/proc/self/task/123/io", 20, 200}: "/proc/20/task/200/io",
934+
{"/proc/123/task/456/mem", 20, 200}: "/proc/123/task/456/mem",
935+
{"/some/other/path", 20, 200}: "/some/other/path",
936+
}
937+
938+
for k, v := range test {
939+
res := replaceProcSelfWithProcPid(k.path, k.pid, k.tid)
940+
if res != v {
941+
t.Fatalf("failed: replaceProcSelfWithProcPid(%s, %d, %d): got %s, want %s",
942+
k.path, k.pid, k.tid, res, v)
943+
}
944+
}
945+
}
946+
923947
// TODO:
948+
// Improve PathAccess tests:
924949
// * test symlink resolution limit
925950
// * test long path

seccomp/mount.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import (
2828
"golang.org/x/sys/unix"
2929
)
3030

31+
var interceptedMountTypes = []string{"proc", "sysfs", "overlay", "nfs", "cifs"}
32+
3133
// MountSyscall information structure.
3234
type mountSyscallInfo struct {
3335
syscallCtx // syscall generic info

0 commit comments

Comments
 (0)