Linux userland API discussions
 help / color / mirror / Atom feed
* Accidental mountns/tmpfs API break from 2016?
@ 2026-11-03 19:58 Andy Lutomirski
  2026-11-04  3:37 ` Eric W. Biederman
  0 siblings, 1 reply; 5+ messages in thread
From: Andy Lutomirski @ 2026-11-03 19:58 UTC (permalink / raw)
  To: Eric W. Biederman, Linux FS Devel, Linux API

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'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?

--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;
    }
}

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-11-09 12:05 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-03 19:58 Accidental mountns/tmpfs API break from 2016? Andy Lutomirski
2026-11-04  3:37 ` Eric W. Biederman
2026-11-04  4:02   ` Andy Lutomirski
2026-11-06  3:48     ` Eric W. Biederman
2026-11-09 12:05       ` Eric W. Biederman

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox