Skip to content

userns: detect initial namespace by inode - #240

Open
aleivag wants to merge 1 commit into
moby:mainfrom
aleivag:fix-userns-detection-proc-inode
Open

userns: detect initial namespace by inode#240
aleivag wants to merge 1 commit into
moby:mainfrom
aleivag:fix-userns-detection-proc-inode

Conversation

@aleivag

@aleivag aleivag commented Jul 28, 2026

Copy link
Copy Markdown

The current uid_map heuristic treats a private user namespace with an identity uid_map as the initial user namespace. systemd 260 can create that shape with PrivateUsers=full, causing callers to believe they can perform initial-namespace-only operations such as cgroup device BPF setup.

Use /proc/self/ns/user's inode instead and compare it with the kernel's PROC_USER_INIT_INO value. This detects the initial user namespace directly instead of inferring it from the uid_map layout.

Related: #239

Comment thread userns/userns_linux.go
@aleivag
aleivag force-pushed the fix-userns-detection-proc-inode branch from 3a4e7f4 to 372f6db Compare July 28, 2026 18:52
Comment thread userns/userns_linux.go Outdated

@kolyshkin kolyshkin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I think that TestInodeInUserNS (and a separate func inodeInUserNS) makes little sense now when the code is very simple and clean. I mean, we just do stat(2) and compare st.Ino -- this needs no unit tests.

  2. The problem is, this (predefined inode number for PROC_USER_INIT_INO) only appears in kernel v3.8 (it comes from https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=98f842e675f9), and since Go itself (since Go 1.24) requires kernel v3.2 (see https://go.dev/wiki/MinimumRequirements#linuxlinux), we do need a fallback to old code. It will still work since most people run linux >= v3.8, and no one sane will run systemd v260+ on an older kernel.

@kolyshkin

Copy link
Copy Markdown
Collaborator

since Go itself (since Go 1.24) requires kernel v3.2

Note this package (github.com/moby/sys/userns) only requires Go 1.18 which itself has a lower linux kernel version requirement. Meaning, we definitely need the fallback.

@aleivag
aleivag force-pushed the fix-userns-detection-proc-inode branch from 372f6db to 41c4209 Compare July 29, 2026 04:53

@kolyshkin kolyshkin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comments above

@aleivag
aleivag force-pushed the fix-userns-detection-proc-inode branch from 41c4209 to 54934d6 Compare July 30, 2026 15:01
@aleivag

aleivag commented Jul 30, 2026

Copy link
Copy Markdown
Author

Meaning, we definitely need the fallback.

cool... I went with "if /proc/self/ns/user exists use that one, if not, continue the old path" ... this should make the code more robust on new kernels, and , like you said, on on old kernels it matter less ..

@aleivag
aleivag requested a review from kolyshkin July 30, 2026 16:41
@kolyshkin

Copy link
Copy Markdown
Collaborator

Meaning, we definitely need the fallback.

cool... I went with "if /proc/self/ns/user exists use that one, if not, continue the old path" ... this should make the code more robust on new kernels, and , like you said, on on old kernels it matter less ..

OK, the /proc/self/ns/user appeared in v3.8 as well so it's OK to only fallback if the file itself does not exist.

Two notes though:

  • add a TODO to remove the fallback once kernel < 3.8 is no longer supported (fingers crossed it will happen in a few years);
  • keep the existing unit test intact, to minimize the change.

Comment thread userns/userns_linux_test.go
@aleivag
aleivag force-pushed the fix-userns-detection-proc-inode branch from 54934d6 to a8eaedb Compare August 3, 2026 03:41
Comment thread userns/userns_linux.go
if err := syscall.Stat("/proc/self/ns/user", &st); err == nil {
return st.Ino != procUserInitIno
} else if !os.IsNotExist(err) {
return false

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should still fall back to the legacy method?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should still fall back to the legacy method?

I can do this... but i think this might be the wrong way of doing it... consider thisL If the file "/proc/self/ns/user" is there, it means a modern kernel, so if stat fails for any other reason than "file does not exists" it mens that you are still on a modern kernel but something unexpected is wrong with /proc, permissions, mount state, or namespace access, its safer to assume that you are not in the init namespace than to fallback in the old criteria (specially if you plan to remove this line once older kernel are not supported) ...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this logic; the only thing i'm wondering is; do we need a comment here to describe the logic?

Not a blocker, just putting it on the table; a future visitor of the code may think we either accidentally fall through, or return too early.

@aleivag

aleivag commented Aug 4, 2026

Copy link
Copy Markdown
Author

@AkihiroSuda @kolyshkin may i bug you with one more review. ppllleeeeeeasseee? 😃

@thaJeztah thaJeztah left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

left a comment #240 (comment), but not a blocker; @kolyshkin can you PTAL?

The current uid_map heuristic treats a private user namespace with an identity uid_map as the initial user namespace. systemd 260 can create that shape with PrivateUsers=full, causing callers to believe they can perform initial-namespace-only operations such as cgroup device BPF setup.

Use /proc/self/ns/user's inode instead and compare it with the kernel's PROC_USER_INIT_INO value. This detects the initial user namespace directly instead of inferring it from the uid_map layout.

Related: containers/crun#2150
Signed-off-by: Alvaro Leiva Geisse <aleivag@gmail.com>
@aleivag
aleivag force-pushed the fix-userns-detection-proc-inode branch from a8eaedb to 023b764 Compare August 6, 2026 17:34
@aleivag

aleivag commented Aug 6, 2026

Copy link
Copy Markdown
Author

LGTM

left a comment #240 (comment), but not a blocker;

@thaJeztah updated with the comment... @kolyshkin its up to you now! 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants