Linux userland API discussions
 help / color / mirror / Atom feed
From: "Eric W. Biederman" <ebiederm@xmission.com>
To: Andy Lutomirski <luto@amacapital.net>
Cc: Linux FS Devel <linux-fsdevel@vger.kernel.org>,
	 Linux API <linux-api@vger.kernel.org>
Subject: Re: Accidental mountns/tmpfs API break from 2016?
Date: Tue, 03 Nov 2026 21:37:48 -0600	[thread overview]
Message-ID: <87se1hclmb.fsf@email.froward.int.ebiederm.org> (raw)
In-Reply-To: <CALCETrWDJKr=LMzfPjwwRRuKapFKQ_OaYF6qYz-9Cs91xAXUgA@mail.gmail.com> (Andy Lutomirski's message of "Tue, 3 Nov 2026 11:58:38 -0800")

Andy Lutomirski <luto@amacapital.net> writes:

> Hi-
>
> I (well, an LLM following my rather explicit instructions) just wrote
> a little tool to make a tmpfs as an unprivileged user: it unshares
> userns and mountns, makes a tmpfs, fchdirs to it, and prints
> /proc/PID/cwd to stdout.  Full source below.
>
> I was a bit surprised that trying to create anything in the tmpfs did
> not work without a uid_map and gid_map.  It looks like it came from:
>
> commit 036d523641c66bef713042894a17f4335f199e49
> Author: Eric W. Biederman <ebiederm@xmission.com>
> Date:   Fri Jul 1 12:52:06 2016 -0500
>
>     vfs: Don't create inodes with a uid or gid unknown to the vfs
>
>     It is expected that filesystems can not represent uids and gids from
>     outside of their user namespace.  Keep things simple by not even
>     trying to create filesystem nodes with non-sense uids and gids.
>
>     Acked-by: Seth Forshee <seth.forshee@canonical.com>
>     Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
>
>
> Now my tool won't actually run on a pre-2016 kernel due to use of
> fsopen, and I'm not about to say that a decade-old commit broke my ABI
> and needs to be reverted.  But I am rather curious whether this rule
> actually makes sense.

I am pretty certain it was deliberate at the time. 

> I'm not creating an idmapped mount, and I'm
> using tmpfs, which could not care less about the mapping as far as I
> know.
>
> Would it make sense to only enforce this for filesystems that care?

Interesting question.  You are in a user namespace.  You are
running as a user that does not exist in your user namespace.
You want to create files.

You can map a that single uid and gid without privileges.
So there is no hardship in getting a uid and gid in your user namespace.

Is it just the extra bit of code to perform that write that is the
problem?

Why would you want to be able to create files as a owned by a user
from another user namespace?  Even if you created them reading them
would show an 0xfffe the invalid uid/gid.  I think there are then
some difficulties in using those files as well.

Why would you want to do much of anything as without having a
properly setup uid or gid?

At a quick look tmpfs just uses vfs inodes which use kuid_t and kgid_t
so it theoretically would be ok.

Still what would be the motivation for supporting something so silly?

If there is a good use case I could see it making sense to support it.

Short of a good use case we are talking about having to support weird
corner cases that no one really cares about that will just bit-rot.

Note.  You can identity map your original uid and gid, instead of
mapping your uid and gid 0.


We have occasionally talked about having a uid space that doesn't map to
the outside world.  Perhaps by having a 64bit kuid, while uid's remain
32bit.  I don't think anyone has pushed forward and merged that yet.  I
did see a proposal form "cells" the other day that appeared to have a
version of that idea implemented.  I mention it just in case that is
what you were expecting to exist in a fresh user namespace.

Eric

> --Andy
>
>
> #include <cerrno>
> #include <cstdio>
> #include <fcntl.h>
> #include <linux/mount.h>
> #include <sched.h>
> #include <string>
> #include <string_view>
> #include <sys/syscall.h>
> #include <system_error>
> #include <unistd.h>
>
> namespace {
> void Check(long result, const char *operation) {
>     if (result < 0)
>         throw std::system_error(errno, std::generic_category(), operation);
> }
>
> struct OwnedFd {
>     int fd;
>
>     ~OwnedFd() { close(fd); }
> };
>
> void WriteAll(int fd, std::string_view bytes) {
>     while (!bytes.empty()) {
>         auto n = write(fd, bytes.data(), bytes.size());
>         if (n < 0 && errno == EINTR)
>             continue;
>         Check(n, "write");
>         if (n == 0)
>             throw std::system_error(EIO, std::generic_category(),
> "zero-length write");
>         bytes.remove_prefix(n);
>     }
> }
>
> void WriteFile(const char *path, std::string_view bytes) {
>     OwnedFd file{open(path, O_WRONLY | O_CLOEXEC)};
>     Check(file.fd, path);
>     WriteAll(file.fd, bytes);
> }
> }
>
> int cmd_temp_shm(int argc, char **) {
>     if (argc != 1) {
>         fprintf(stderr, "Usage: lktool temp_shm\n");
>         return 1;
>     }
>
>     try {
>         auto uid = getuid();
>         auto gid = getgid();
>         Check(unshare(CLONE_NEWUSER | CLONE_NEWNS), "unshare user and
> mount namespaces");
>
>         /*
>          * The outside UID and GID need to be mapped in order for the
> outside tasks
>          * to create dentries.  This comes from commit 036d523641c6
> ("vfs: Don't create
>          * inodes with a uid or gid unknown to the vfs"), which I
> think may have been
>          * a mistake.
>          */
>         WriteFile("/proc/self/setgroups", "deny\n");
>         WriteFile("/proc/self/uid_map", "0 " + std::to_string(uid) + " 1\n");
>         WriteFile("/proc/self/gid_map", "0 " + std::to_string(gid) + " 1\n");
>
>         OwnedFd context{static_cast<int>(syscall(SYS_fsopen, "tmpfs",
> FSOPEN_CLOEXEC))};
>         Check(context.fd, "fsopen tmpfs");
>         Check(syscall(SYS_fsconfig, context.fd, FSCONFIG_CMD_CREATE,
> nullptr, nullptr, 0),
>               "fsconfig create tmpfs");
>
>         OwnedFd mount{static_cast<int>(syscall(SYS_fsmount,
> context.fd, FSMOUNT_CLOEXEC, 0))};
>         Check(mount.fd, "fsmount tmpfs");
>         Check(fchdir(mount.fd), "fchdir tmpfs");
>
>         // The detached mount is kept alive by cwd and the mount descriptor.
>         // No mount is attached to the inherited filesystem hierarchy.
>         std::string path = "/proc/" + std::to_string(getpid()) + "/cwd";
>         path.push_back('\0');
>         WriteAll(STDOUT_FILENO, path);
>         for (;;)
>             pause();
>     } catch (const std::system_error &e) {
>         fprintf(stderr, "lktool temp_shm: %s\n", e.what());
>         return 1;
>     }
> }

  reply	other threads:[~2026-11-04  3:38 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03 19:58 Accidental mountns/tmpfs API break from 2016? Andy Lutomirski
2026-11-04  3:37 ` Eric W. Biederman [this message]
2026-11-04  4:02   ` Andy Lutomirski
2026-11-06  3:48     ` Eric W. Biederman
2026-11-09 12:05       ` Eric W. Biederman

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87se1hclmb.fsf@email.froward.int.ebiederm.org \
    --to=ebiederm@xmission.com \
    --cc=linux-api@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=luto@amacapital.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox