Skip to content

Squashfuse#164

Draft
simonpintarelli wants to merge 51 commits into
mainfrom
squashfuse
Draft

Squashfuse#164
simonpintarelli wants to merge 51 commits into
mainfrom
squashfuse

Conversation

@simonpintarelli

@simonpintarelli simonpintarelli commented Jun 11, 2026

Copy link
Copy Markdown
Member

add mutable root option (following strace -f bwrap)

  • make --sqfs optional (required for stackinator sandbox)
  • add --bind-mount, --tmpfs
  • if --mutable-root is given, non-existent mount points will be created. It's not checked if they reside in the mutable root, thus allows also to created directories in home.
  1. unshare mnt ns, become fake-root
  2. optional create mutable root (disable for setuid)
  3. mount tmpfs
  4. bind mounts
  5. mount sqfs images
  6. exit fake-root

--join flag

If --join is passed to uenv run, only the first task per node will setup the namespace and mount the squashfs image (fuse). The remaining tasks will join the namespace (user+mnt) of the first task. It uses the same construction as ch-run --join from charliecloud.

SPANK plugin with fuse

There are two options.

  1. call squashfs-mount after fork via exec and then join the namespace
  2. directly call the squashfuse_ll routines from the spank plugin

Option 2. requires to call prctl(PR_SET_DUMPABLE,1), which has been previously set to zero by the kernel (when slurm switched to the unprivildged user). This might have security implications.
When PR_SET_DUMPABLE is 0, the fakeroot sandbox cannot be created. Furthermore the fuse mount failed too.

Installation

# enable rootless (sqashsufse for squashfs-mount, slurm plugin: root)
meson setup --buildtype=debug -Dsquashfs_mount=true -Dfuse=true -Dfuse_version=3 -Dtests=enabled --wipe builddir

make sure to use -Dfuse_version2 if squashfuse has been built against fuse2. Afaik it will always build against fuse3 if it's present on the system (no matter what flags are given to configure). Mixing fuse versions will cause runtime errors.

Comment thread .editorconfig
@@ -0,0 +1,9 @@
root = true

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.

How does this interact with clang format? Because we have clang format, most editors should automatically apply the formatting rules on save.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It's redundant. It was just to avoid the nuisance of 2 space indent (my default) when entering a new line.

Comment thread install-alps-local.sh
@@ -1,11 +1,60 @@
#!/bin/bash

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 was thinking about removing this completely, because it is not up to date.

There is a meson wrap db for zstd: https://mensinda.github.io/meson/Wrapdb-projects.html

Comment thread install-alps-local.sh

if [ ! -d "squashfuse-${squashfuse_version}/install/lib/pkgconfig" ]; then
(
curl -L https://github.com/vasi/squashfuse/releases/download/${squashfuse_version}/squashfuse-${squashfuse_version}.tar.gz | tar xzf -

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.

Should we be trying to use squashfuse available on Alps systems? The container team install a version from RPM.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

squashfuse on alps is 0.5.1. The latest release is 0.6.2.

It is linked to fuse2:

[daint][simonpi@daint-ln001 ~]$ ldd /usr/lib64/libsquashfuse_ll.so.0
	linux-vdso.so.1 (0x000040001c560000)
	libz.so.1 => /usr/lib64/libz.so.1 (0x000040001c5b0000)
	liblzma.so.5 => /usr/lib64/liblzma.so.5 (0x000040001c5f0000)
	liblzo2.so.2 => /usr/lib64/liblzo2.so.2 (0x000040001c650000)
	liblz4.so.1 => /usr/lib64/liblz4.so.1 (0x000040001c690000)
	libzstd.so.1 => /usr/lib64/libzstd.so.1 (0x000040001c6e0000)
	libfuse.so.2 => /lib64/libfuse.so.2 (0x000040001c7b0000)
	libc.so.6 => /lib64/libc.so.6 (0x000040001c810000)
	/lib/ld-linux-aarch64.so.1 (0x000040001c4f0000)
	libdl.so.2 => /lib64/libdl.so.2 (0x000040001c9d0000)
	libpthread.so.0 => /lib64/libpthread.so.0 (0x000040001ca00000)
``

Afaik the multithreaded version requires fuse3.

Comment thread src/slurm/meson.build Outdated

dependencies = [uenv_dep, slurm_dep]
if slurm_fuse
module_src += ['../uenv/rootless.cpp', '../uenv/mutable_root.cpp', 'plugin_fuse.cpp']

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.

The files in src/uenv/ should be part of the uenv dependency, defined in the meson.build file in the root of the project.

Comment thread meson.build Outdated
if get_option('fuse')
squashfs_mount_build = executable('squashfs-mount',
sources: ['src/squashfs-mount/squashfs-mount-rootless.cpp',
'src/uenv/rootless.cpp',

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.

The rootless.cpp and mutable_root.cpp belong in the uenv_dep, I think.

Comment thread src/slurm/plugin_fuse.cpp
winner_pid = fork();
if (winner_pid.value() == 0) {
prctl(PR_SET_PDEATHSIG, SIGHUP);
execlp("squashfs-mount", "squashfs-mount", "--sqfs",

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.

Good to see that this works!

How do you feel about implementing the logic locally in the plugin, instead of calling out to squashfs-mount.

  • It is a little bit confusing (for me at least) to keep track of things.
  • What happens if squashfs-mount is setuid, and the slurm plugin is using squashfuse... does it still work?

@simonpintarelli simonpintarelli Jul 1, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Exiting the fakeroot environment (map_effective_user) fails with

error: failed map effective user unshare(CLONE_NEWUSER|CLONE_NEWNS) failed, Operation not permitted 1000 1000

when called inside slurm_spank_task_init. Also the call to sqfs_ll mount fails with an error. It's not clear why.

Instead of calling sqfs_mount in the forked process, one can still try to call squashfuse_ll directly in the fork. Actually, the squashfuse_ll already are in a forked process.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Attaching strace to slurmd revealed that PR_SET_DUMPABLE was set to zero by slurm. When adding prctl(PR_SET_DUMPABLE,1) right after the first unshare call, the fakeroot works and the sqfs_ll calls work as well. I'll change the code back to the squashfuse_ll version.

uenv::mount_list mounts) {

const uid_t uid = getuid();
const uid_t gid = getgid();

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.

These should always work as expected if squashfs-mount is called from within the slurm plugin?

I am guessing yes, because they are called in task_init, which runs as user.

}

// tmps
auto tmpfs = uenv::parse_tmpfs(tmpfs_str);

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.

The value of tmpfs_str is parsed/validated after operations like unshare_mount_map_root have been performed.

How about validating all inputs as close to the parsing of the CLI args as possible, and passing pre-validated and parsed values into this function?

bool tasks_join = false;
std::string raw_mounts;
std::vector<std::string> commands;
std::vector<std::string> tmpfs_str;

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.

For consistency with how we parse lists of mount points here and in other CLI tools, tmpfs_str and bind_mounts_str could be:

std::optional<std::string> tmpfs_arg;
std::optional<std::string> bind_mounts_arg;

Where the argument is a comma-separated list.

The parse calls would take a string instead of vector, and return a std::vector, like parse_mount_list.

You can also then test on the optional value to determine whether the user passed a specific flag.

Comment thread meson.build Outdated
meson.add_install_script('./meson-scripts/install-bash-completion.sh')
endif

if get_option('fuse') or get_option('slurm_fuse')

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.

The decision of whether to use fuse is in two variables fuse and slurm_fuse.

A single variable fuse would be clearer.

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.

2 participants