Linux userland API discussions
 help / color / mirror / Atom feed
* Re: [PATCH] ovl: add ioctls to retrieve layer file descriptors
From: Miklos Szeredi @ 2026-07-08 14:01 UTC (permalink / raw)
  To: Giuseppe Scrivano; +Cc: Amir Goldstein, linux-unionfs, linux-fsdevel, linux-api
In-Reply-To: <871pdd8ukx.fsf@redhat.com>

On Wed, 8 Jul 2026 at 15:44, Giuseppe Scrivano <gscrivan@redhat.com> wrote:
>
> Amir Goldstein <amir73il@gmail.com> writes:
>
> > On Wed, Jul 8, 2026 at 2:31 PM Giuseppe Scrivano <gscrivan@redhat.com> wrote:
> >>
> >> Miklos Szeredi <miklos@szeredi.hu> writes:
> >>
> >> > On Wed, 8 Jul 2026 at 12:00, Giuseppe Scrivano <gscrivan@redhat.com> wrote:
> >> >>
> >> >> Add two ioctls to overlay filesystem to allow userspace to retrieve
> >> >> information about the overlay layers:
> >> >>
> >> >> OVL_IOC_OPEN_LAYER:     return an O_PATH fd to the root of a layer.
> >> >>                         arg == 0 returns the upper layer (-ENOENT if
> >> >>                         no upper is configured), arg >= 1 returns
> >> >>                         lower layers (-ENOENT if index is out of
> >> >>                         range).
> >> >
> >> > We could do this with a plain open() call.  Something like the magic
> >> > symlinks we have under /proc/PID/fd/.   Question is where could these
> >> > live...
> >>
> >> is there any existing user of such a mechanism?  I don't see any mount
> >> specific info under /proc or /sys.
> >>
> >> >
> >> >> OVL_IOC_GET_LAYERS_INFO: copy a struct ovl_layers_info to userspace
> >> >>                         with numlower, numlowerdata, and has_upper.
> >> >
> >> > Isn't this info obtainable via statmount(2) already?  If not, it
> >> > should be there, instead of a specialized ioctl.
> >>
> >> no that is not exposed by statmount and I don't see any way to export
> >> file system specific data through it.  Do you've anything in mind?
> >>
> >> >> --- a/fs/overlayfs/ovl_entry.h
> >> >> +++ b/fs/overlayfs/ovl_entry.h
> >> >> @@ -35,6 +35,8 @@ struct ovl_layer {
> >> >>         struct vfsmount *mnt;
> >> >>         /* Trap in ovl inode cache */
> >> >>         struct inode *trap;
> >> >> +       /* Keeps the original fsmount file alive for OVL_IOC_OPEN_LAYER */
> >> >> +       struct file *origin;
> >> >
> >> > Don't need to keep the file open: the only info missing is the
> >> > original vfsmount, everything else is already there to reconstruct the
> >> > file.
> >>
> >> I didn't manage to get that to work.  As soon as the userspace process
> >> closes the mount fd that was passed to fsconfig, the anonymous mount
> >> namespace is destroyed and dissolve_on_fput sets mnt->mnt_ns to NULL.
> >>
> >> So whenever I try to use this mount again from userspace, it is not
> >> usable because the mount namespace is empty, causing check_mnt() to
> >> fail.
> >>
> >> Do you have any suggestions on how to solve this problem?
> >
> > The suggestion was to store origin->f_path->mnt instead of storing origin file,
> > because you only end up using the origin vfsmount.
>
> the reason I am keeping the file and not just the vfsmount is that the
> file is what keeps the mount namespace alive (preventing
> dissolve_on_fput from fire).
>
> Should we export open_detached_copy from fs/namespace.c?  I've not
> tested it, but it might work.  Are there other ways to solve it that I
> am not seeing?

Using an anon namespace sounds good to me, that means the original
vfsmount isn't needed at all.

Not sure if it's okay for the case where the original ns is not anon,
but we can save the vfsmount in that case if it turns out to be a
problem.

Thanks,
Miklos

^ permalink raw reply

* Re: [PATCH] ovl: add ioctls to retrieve layer file descriptors
From: Giuseppe Scrivano @ 2026-07-08 13:44 UTC (permalink / raw)
  To: Amir Goldstein; +Cc: Miklos Szeredi, linux-unionfs, linux-fsdevel, linux-api
In-Reply-To: <CAOQ4uxiiVsp0BsdqfH3rCrpP6fBi-vTyfXd-TcVmFcS34MxzUQ@mail.gmail.com>

Amir Goldstein <amir73il@gmail.com> writes:

> On Wed, Jul 8, 2026 at 2:31 PM Giuseppe Scrivano <gscrivan@redhat.com> wrote:
>>
>> Miklos Szeredi <miklos@szeredi.hu> writes:
>>
>> > On Wed, 8 Jul 2026 at 12:00, Giuseppe Scrivano <gscrivan@redhat.com> wrote:
>> >>
>> >> Add two ioctls to overlay filesystem to allow userspace to retrieve
>> >> information about the overlay layers:
>> >>
>> >> OVL_IOC_OPEN_LAYER:     return an O_PATH fd to the root of a layer.
>> >>                         arg == 0 returns the upper layer (-ENOENT if
>> >>                         no upper is configured), arg >= 1 returns
>> >>                         lower layers (-ENOENT if index is out of
>> >>                         range).
>> >
>> > We could do this with a plain open() call.  Something like the magic
>> > symlinks we have under /proc/PID/fd/.   Question is where could these
>> > live...
>>
>> is there any existing user of such a mechanism?  I don't see any mount
>> specific info under /proc or /sys.
>>
>> >
>> >> OVL_IOC_GET_LAYERS_INFO: copy a struct ovl_layers_info to userspace
>> >>                         with numlower, numlowerdata, and has_upper.
>> >
>> > Isn't this info obtainable via statmount(2) already?  If not, it
>> > should be there, instead of a specialized ioctl.
>>
>> no that is not exposed by statmount and I don't see any way to export
>> file system specific data through it.  Do you've anything in mind?
>>
>> >> --- a/fs/overlayfs/ovl_entry.h
>> >> +++ b/fs/overlayfs/ovl_entry.h
>> >> @@ -35,6 +35,8 @@ struct ovl_layer {
>> >>         struct vfsmount *mnt;
>> >>         /* Trap in ovl inode cache */
>> >>         struct inode *trap;
>> >> +       /* Keeps the original fsmount file alive for OVL_IOC_OPEN_LAYER */
>> >> +       struct file *origin;
>> >
>> > Don't need to keep the file open: the only info missing is the
>> > original vfsmount, everything else is already there to reconstruct the
>> > file.
>>
>> I didn't manage to get that to work.  As soon as the userspace process
>> closes the mount fd that was passed to fsconfig, the anonymous mount
>> namespace is destroyed and dissolve_on_fput sets mnt->mnt_ns to NULL.
>>
>> So whenever I try to use this mount again from userspace, it is not
>> usable because the mount namespace is empty, causing check_mnt() to
>> fail.
>>
>> Do you have any suggestions on how to solve this problem?
>
> The suggestion was to store origin->f_path->mnt instead of storing origin file,
> because you only end up using the origin vfsmount.

the reason I am keeping the file and not just the vfsmount is that the
file is what keeps the mount namespace alive (preventing
dissolve_on_fput from fire).

Should we export open_detached_copy from fs/namespace.c?  I've not
tested it, but it might work.  Are there other ways to solve it that I
am not seeing?

Thanks,
Giuseppe


^ permalink raw reply

* Re: [PATCH] ovl: add ioctls to retrieve layer file descriptors
From: Amir Goldstein @ 2026-07-08 13:23 UTC (permalink / raw)
  To: Giuseppe Scrivano; +Cc: Miklos Szeredi, linux-unionfs, linux-fsdevel, linux-api
In-Reply-To: <878q7l8y4y.fsf@redhat.com>

On Wed, Jul 8, 2026 at 2:31 PM Giuseppe Scrivano <gscrivan@redhat.com> wrote:
>
> Miklos Szeredi <miklos@szeredi.hu> writes:
>
> > On Wed, 8 Jul 2026 at 12:00, Giuseppe Scrivano <gscrivan@redhat.com> wrote:
> >>
> >> Add two ioctls to overlay filesystem to allow userspace to retrieve
> >> information about the overlay layers:
> >>
> >> OVL_IOC_OPEN_LAYER:     return an O_PATH fd to the root of a layer.
> >>                         arg == 0 returns the upper layer (-ENOENT if
> >>                         no upper is configured), arg >= 1 returns
> >>                         lower layers (-ENOENT if index is out of
> >>                         range).
> >
> > We could do this with a plain open() call.  Something like the magic
> > symlinks we have under /proc/PID/fd/.   Question is where could these
> > live...
>
> is there any existing user of such a mechanism?  I don't see any mount
> specific info under /proc or /sys.
>
> >
> >> OVL_IOC_GET_LAYERS_INFO: copy a struct ovl_layers_info to userspace
> >>                         with numlower, numlowerdata, and has_upper.
> >
> > Isn't this info obtainable via statmount(2) already?  If not, it
> > should be there, instead of a specialized ioctl.
>
> no that is not exposed by statmount and I don't see any way to export
> file system specific data through it.  Do you've anything in mind?
>
> >> --- a/fs/overlayfs/ovl_entry.h
> >> +++ b/fs/overlayfs/ovl_entry.h
> >> @@ -35,6 +35,8 @@ struct ovl_layer {
> >>         struct vfsmount *mnt;
> >>         /* Trap in ovl inode cache */
> >>         struct inode *trap;
> >> +       /* Keeps the original fsmount file alive for OVL_IOC_OPEN_LAYER */
> >> +       struct file *origin;
> >
> > Don't need to keep the file open: the only info missing is the
> > original vfsmount, everything else is already there to reconstruct the
> > file.
>
> I didn't manage to get that to work.  As soon as the userspace process
> closes the mount fd that was passed to fsconfig, the anonymous mount
> namespace is destroyed and dissolve_on_fput sets mnt->mnt_ns to NULL.
>
> So whenever I try to use this mount again from userspace, it is not
> usable because the mount namespace is empty, causing check_mnt() to
> fail.
>
> Do you have any suggestions on how to solve this problem?

The suggestion was to store origin->f_path->mnt instead of storing origin file,
because you only end up using the origin vfsmount.

Thanks,
Amir.

^ permalink raw reply

* Re: [PATCH 2/2] erofs: add ioctl to retrieve the backing source file descriptor
From: Gao Xiang @ 2026-07-08 12:51 UTC (permalink / raw)
  To: Giuseppe Scrivano
  Cc: linux-erofs, linux-fsdevel, linux-api, Christian Brauner
In-Reply-To: <20260708093446.3370200-3-gscrivan@redhat.com>

Hi Giuseppe,

On Wed, Jul 08, 2026 at 11:34:27AM +0200, Giuseppe Scrivano wrote:
> Add EROFS_IOC_GET_SOURCE_FD ioctl that returns a file descriptor to the
> backing image file for file-backed erofs mounts.
> 
> Returns -ENOENT for block-device-backed erofs mounts where there is no
> backing file.
> 
> The UAPI constant is defined in include/uapi/linux/erofs.h.
> 
> Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>

Thanks for the patch!

The functionality is indeed useful to users, but similar to overlayfs
one, I'm not sure if the interface is the best way. Since after we add
this to EROFS, we will support this way forever.

I wonder if there could be a way as a common vfs uapi to get source
fds, but I don't have better ideas for now... (that is why I suggested
to Cc linux-api too..)

Thanks,
Gao Xiang

^ permalink raw reply

* Re: [PATCH 1/2] erofs: accept source file descriptor via fsconfig
From: Gao Xiang @ 2026-07-08 12:45 UTC (permalink / raw)
  To: Giuseppe Scrivano; +Cc: linux-erofs, linux-fsdevel, linux-api
In-Reply-To: <20260708093446.3370200-2-gscrivan@redhat.com>

Hi Giuseppe,

On Wed, Jul 08, 2026 at 11:34:26AM +0200, Giuseppe Scrivano wrote:
> Add fsparam_fd("source") so that userspace can pass an already-opened
> file descriptor instead of a path string.  When the fd is provided via
> fsconfig(FSCONFIG_SET_FD, "source", NULL, fd), it is stored directly
> in sbi->dif0.file and erofs_fc_get_tree() skips the filp_open() call.
> 
> This is useful for mount namespaces where the backing file may not be
> reachable by path, and for tools that already hold an fd to the image
> (e.g. composefs reusing an erofs mount's backing file).
> 
> Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
> ---
>  fs/erofs/super.c | 36 +++++++++++++++++++++++++-----------
>  1 file changed, 25 insertions(+), 11 deletions(-)
> 
> diff --git a/fs/erofs/super.c b/fs/erofs/super.c
> index 86fa5c6a0c70..8ad1689f74b2 100644
> --- a/fs/erofs/super.c
> +++ b/fs/erofs/super.c
> @@ -386,6 +386,7 @@ static void erofs_default_options(struct erofs_sb_info *sbi)
>  enum {
>  	Opt_user_xattr, Opt_acl, Opt_cache_strategy, Opt_dax, Opt_dax_enum,
>  	Opt_device, Opt_domain_id, Opt_directio, Opt_fsoffset, Opt_inode_share,
> +	Opt_source_fd,
>  };
>  
>  static const struct constant_table erofs_param_cache_strategy[] = {
> @@ -413,6 +414,7 @@ static const struct fs_parameter_spec erofs_fs_parameters[] = {
>  	fsparam_flag_no("directio",	Opt_directio),
>  	fsparam_u64("fsoffset",		Opt_fsoffset),
>  	fsparam_flag("inode_share",	Opt_inode_share),
> +	fsparam_fd("source",		Opt_source_fd),
>  	{}
>  };
>  
> @@ -524,6 +526,15 @@ static int erofs_fc_parse_param(struct fs_context *fc,
>  		else
>  			set_opt(&sbi->opt, INODE_SHARE);
>  		break;
> +	case Opt_source_fd:
> +		if (!IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE)) {
> +			errorfc(fc, "source fd option not supported");

Thanks for the patch!
For this commit, it looks good to me overall, just some nits:

I guess we could just move this one into erofs_fc_get_tree(), see below.

> +			return -EINVAL;
> +		}
> +		if (sbi->dif0.file)

Do we need to allow multi-shot source_fd?

I guess we could just bail out directly instead.

> +			fput(sbi->dif0.file);
> +		sbi->dif0.file = get_file(param->file);
> +		break;
>  	}
>  	return 0;
>  }
> @@ -752,14 +763,18 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
>  
>  static int erofs_fc_get_tree(struct fs_context *fc)
>  {
> -	int ret;
> +	struct erofs_sb_info *sbi = fc->s_fs_info;

Nit:

	struct file *file = sbi->dif0.file;

>  
> -	ret = get_tree_bdev_flags(fc, erofs_fc_fill_super,
> -		IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE) ?
> -			GET_TREE_BDEV_QUIET_LOOKUP : 0);
> -	if (IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE) && ret == -ENOTBLK) {
> -		struct erofs_sb_info *sbi = fc->s_fs_info;
> +	if (!IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE) || !sbi->dif0.file) {

	if (!IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE) || !file) {

>  		struct file *file;
> +		int ret;
> +
> +		ret = get_tree_bdev_flags(fc, erofs_fc_fill_super,
> +			IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE) ?
> +				GET_TREE_BDEV_QUIET_LOOKUP : 0);
> +		if (!IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE) ||
> +		    ret != -ENOTBLK)
> +			return ret;
>  
>  		if (!fc->source)
>  			return invalf(fc, "No source specified");
> @@ -767,12 +782,11 @@ static int erofs_fc_get_tree(struct fs_context *fc)
>  		if (IS_ERR(file))
>  			return PTR_ERR(file);
>  		sbi->dif0.file = file;
> -
> -		if (S_ISREG(file_inode(sbi->dif0.file)->i_mode) &&
> -		    sbi->dif0.file->f_mapping->a_ops->read_folio)
> -			return get_tree_nodev(fc, erofs_fc_fill_super);
>  	}
> -	return ret;
> +	if (S_ISREG(file_inode(sbi->dif0.file)->i_mode) &&
> +	    sbi->dif0.file->f_mapping->a_ops->read_folio)
> +		return get_tree_nodev(fc, erofs_fc_fill_super);
> +	return -EINVAL;

Currently we don't support bdev-backed mounts for this, so I'm fine to
support file-backed mounts only for now.

So nit:

	if (!S_ISREG(file_inode(file)->i_mode) ||
	    !file->f_mapping->a_ops->read_folio) {
		errorfc(fc, "source is unsupported");
		return -EINVAL;
	}
	return get_tree_nodev(fc, erofs_fc_fill_super);

Thanks,
Gao Xiang

>  }
>  
>  static int erofs_fc_reconfigure(struct fs_context *fc)
> -- 
> 2.55.0
> 
> 

^ permalink raw reply

* Re: [PATCH] ovl: add ioctls to retrieve layer file descriptors
From: Giuseppe Scrivano @ 2026-07-08 12:27 UTC (permalink / raw)
  To: Miklos Szeredi; +Cc: linux-unionfs, linux-fsdevel, linux-api
In-Reply-To: <CAJfpegsJON=1_84PCGMjASYPFL=Wqsz7dnTAbO3Tdz5DfRQU+g@mail.gmail.com>

Miklos Szeredi <miklos@szeredi.hu> writes:

> On Wed, 8 Jul 2026 at 12:00, Giuseppe Scrivano <gscrivan@redhat.com> wrote:
>>
>> Add two ioctls to overlay filesystem to allow userspace to retrieve
>> information about the overlay layers:
>>
>> OVL_IOC_OPEN_LAYER:     return an O_PATH fd to the root of a layer.
>>                         arg == 0 returns the upper layer (-ENOENT if
>>                         no upper is configured), arg >= 1 returns
>>                         lower layers (-ENOENT if index is out of
>>                         range).
>
> We could do this with a plain open() call.  Something like the magic
> symlinks we have under /proc/PID/fd/.   Question is where could these
> live...

is there any existing user of such a mechanism?  I don't see any mount
specific info under /proc or /sys.

>
>> OVL_IOC_GET_LAYERS_INFO: copy a struct ovl_layers_info to userspace
>>                         with numlower, numlowerdata, and has_upper.
>
> Isn't this info obtainable via statmount(2) already?  If not, it
> should be there, instead of a specialized ioctl.

no that is not exposed by statmount and I don't see any way to export
file system specific data through it.  Do you've anything in mind?

>> --- a/fs/overlayfs/ovl_entry.h
>> +++ b/fs/overlayfs/ovl_entry.h
>> @@ -35,6 +35,8 @@ struct ovl_layer {
>>         struct vfsmount *mnt;
>>         /* Trap in ovl inode cache */
>>         struct inode *trap;
>> +       /* Keeps the original fsmount file alive for OVL_IOC_OPEN_LAYER */
>> +       struct file *origin;
>
> Don't need to keep the file open: the only info missing is the
> original vfsmount, everything else is already there to reconstruct the
> file.

I didn't manage to get that to work.  As soon as the userspace process
closes the mount fd that was passed to fsconfig, the anonymous mount
namespace is destroyed and dissolve_on_fput sets mnt->mnt_ns to NULL.

So whenever I try to use this mount again from userspace, it is not
usable because the mount namespace is empty, causing check_mnt() to
fail.

Do you have any suggestions on how to solve this problem?

Regards,
Giuseppe


^ permalink raw reply

* Re: [PATCH] ovl: add ioctls to retrieve layer file descriptors
From: Amir Goldstein @ 2026-07-08 10:40 UTC (permalink / raw)
  To: Giuseppe Scrivano; +Cc: linux-unionfs, linux-fsdevel, linux-api, Miklos Szeredi
In-Reply-To: <20260708095831.3381978-1-gscrivan@redhat.com>

On Wed, Jul 8, 2026 at 12:00 PM Giuseppe Scrivano <gscrivan@redhat.com> wrote:
>
> Add two ioctls to overlay filesystem to allow userspace to retrieve
> information about the overlay layers:
>
> OVL_IOC_OPEN_LAYER:     return an O_PATH fd to the root of a layer.
>                         arg == 0 returns the upper layer (-ENOENT if
>                         no upper is configured), arg >= 1 returns
>                         lower layers (-ENOENT if index is out of
>                         range).
> OVL_IOC_GET_LAYERS_INFO: copy a struct ovl_layers_info to userspace
>                         with numlower, numlowerdata, and has_upper.
>
> The ioctls work on any overlayfs file or directory and require
> CAP_SYS_ADMIN in the mounter's user namespace.
>
> The UAPI constants and struct are defined in include/uapi/linux/overlay.h.
>
> Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
> ---
>  MAINTAINERS                  |  1 +
>  fs/overlayfs/file.c          |  2 ++
>  fs/overlayfs/overlayfs.h     |  4 +++
>  fs/overlayfs/ovl_entry.h     |  2 ++
>  fs/overlayfs/params.c        | 10 ++++++
>  fs/overlayfs/params.h        |  1 +
>  fs/overlayfs/readdir.c       |  2 ++
>  fs/overlayfs/super.c         | 70 ++++++++++++++++++++++++++++++++++++
>  include/uapi/linux/overlay.h | 30 ++++++++++++++++
>  9 files changed, 122 insertions(+)
>  create mode 100644 include/uapi/linux/overlay.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 25453040dffb..b64c696686e4 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -20368,6 +20368,7 @@ S:      Supported
>  T:     git git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs.git
>  F:     Documentation/filesystems/overlayfs.rst
>  F:     fs/overlayfs/
> +F:     include/uapi/linux/overlay.h


overlayfs.h please

>
>  P54 WIRELESS DRIVER
>  M:     Christian Lamparter <chunkeey@googlemail.com>
> diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c
> index 27cc07738f33..fc9c448c5959 100644
> --- a/fs/overlayfs/file.c
> +++ b/fs/overlayfs/file.c
> @@ -649,4 +649,6 @@ const struct file_operations ovl_file_operations = {
>         .copy_file_range        = ovl_copy_file_range,
>         .remap_file_range       = ovl_remap_file_range,
>         .setlease               = generic_setlease,
> +       .unlocked_ioctl         = ovl_ioctl,
> +       .compat_ioctl           = compat_ptr_ioctl,
>  };
> diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
> index b75df37f70ac..10f7ef8cb78c 100644
> --- a/fs/overlayfs/overlayfs.h
> +++ b/fs/overlayfs/overlayfs.h
> @@ -8,6 +8,7 @@
>  #include <linux/uuid.h>
>  #include <linux/fs.h>
>  #include <linux/fsverity.h>
> +#include <uapi/linux/overlay.h>
>  #include <linux/namei.h>
>  #include <linux/posix_acl.h>
>  #include <linux/posix_acl_xattr.h>
> @@ -908,6 +909,9 @@ void ovl_tempname(char name[OVL_TEMPNAME_SIZE]);
>  struct dentry *ovl_create_temp(struct ovl_fs *ofs, struct dentry *workdir,
>                                struct ovl_cattr *attr);
>
> +/* super.c */
> +long ovl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
> +
>  /* file.c */
>  extern const struct file_operations ovl_file_operations;
>  int ovl_real_fileattr_get(const struct path *realpath, struct file_kattr *fa);
> diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h
> index 80cad4ea96a3..b8f4bca89a27 100644
> --- a/fs/overlayfs/ovl_entry.h
> +++ b/fs/overlayfs/ovl_entry.h
> @@ -35,6 +35,8 @@ struct ovl_layer {
>         struct vfsmount *mnt;
>         /* Trap in ovl inode cache */
>         struct inode *trap;
> +       /* Keeps the original fsmount file alive for OVL_IOC_OPEN_LAYER */
> +       struct file *origin;

as Miklos wrote, keep the file is an overkill

>         struct ovl_sb *fs;
>         /* Index of this layer in fs root (upper idx == 0) */
>         int idx;
> diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c
> index c93fcaa45d4a..92d1a56178f2 100644
> --- a/fs/overlayfs/params.c
> +++ b/fs/overlayfs/params.c
> @@ -482,6 +482,11 @@ static int ovl_parse_layer(struct fs_context *fc, struct fs_parameter *param,
>                         return PTR_ERR(layer_name);
>
>                 err = ovl_do_parse_layer(fc, layer_name, &layer_path, layer);
> +               if (!err && !is_upper_layer(layer)) {
> +                       struct ovl_fs_context *ctx = fc->fs_private;
> +
> +                       ctx->lower[ctx->nr - 1].origin = get_file(param->file);
> +               }
>                 break;
>         }
>         default:
> @@ -504,6 +509,9 @@ static void ovl_reset_lowerdirs(struct ovl_fs_context *ctx)
>                 path_put(&l->path);
>                 kfree(l->name);
>                 l->name = NULL;
> +               if (l->origin)
> +                       fput(l->origin);
> +               l->origin = NULL;
>         }
>         ctx->nr = 0;
>         ctx->nr_data = 0;
> @@ -856,6 +864,8 @@ void ovl_free_fs(struct ovl_fs *ofs)
>         mounts = (struct vfsmount **) ofs->config.lowerdirs;
>         for (i = 0; i < ofs->numlayer; i++) {
>                 iput(ofs->layers[i].trap);
> +               if (ofs->layers[i].origin)
> +                       fput(ofs->layers[i].origin);
>                 kfree(ofs->config.lowerdirs[i]);
>                 mounts[i] = ofs->layers[i].mnt;
>         }
> diff --git a/fs/overlayfs/params.h b/fs/overlayfs/params.h
> index ffd53cdd8482..1d8fe8fbaca2 100644
> --- a/fs/overlayfs/params.h
> +++ b/fs/overlayfs/params.h
> @@ -22,6 +22,7 @@ struct ovl_opt_set {
>  struct ovl_fs_context_layer {
>         char *name;
>         struct path path;
> +       struct file *origin;
>  };
>
>  struct ovl_fs_context {
> diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
> index e7fe29cb6028..7bab71c8bcc2 100644
> --- a/fs/overlayfs/readdir.c
> +++ b/fs/overlayfs/readdir.c
> @@ -1069,6 +1069,8 @@ const struct file_operations ovl_dir_operations = {
>         .iterate_shared = shared_ovl_iterate,
>         .llseek         = ovl_dir_llseek,
>         .fsync          = ovl_dir_fsync,
> +       .unlocked_ioctl = ovl_ioctl,
> +       .compat_ioctl   = compat_ptr_ioctl,
>         .release        = ovl_dir_release,
>         .setlease       = generic_setlease,
>  };
> diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
> index 60f0b7ceef0a..d143002e74b2 100644
> --- a/fs/overlayfs/super.c
> +++ b/fs/overlayfs/super.c
> @@ -1107,6 +1107,8 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
>                  */
>                 mnt->mnt_flags |= MNT_READONLY | MNT_NOATIME;
>
> +               layers[ofs->numlayer].origin = l->origin;
> +               l->origin = NULL;
>                 layers[ofs->numlayer].trap = trap;
>                 layers[ofs->numlayer].mnt = mnt;
>                 layers[ofs->numlayer].idx = ofs->numlayer;
> @@ -1568,6 +1570,74 @@ int ovl_fill_super(struct super_block *sb, struct fs_context *fc)
>         return err;
>  }
>
> +static long ovl_ioctl_open_layer(struct file *filp, unsigned long arg)
> +{
> +       struct super_block *sb = file_inode(filp)->i_sb;
> +       struct ovl_fs *ofs = OVL_FS(sb);
> +       struct path root;
> +       struct file *f;
> +       int fd;
> +
> +       if (arg >= ofs->numlayer)
> +               return -ENOENT;
> +       if (arg == 0 && !ovl_upper_mnt(ofs))
> +               return -ENOENT;
> +       if (!ofs->layers[arg].origin)
> +               return -EOPNOTSUPP;
> +
> +       root.mnt = mntget(ofs->layers[arg].origin->f_path.mnt);
> +       root.dentry = dget(root.mnt->mnt_root);
> +
> +       fd = get_unused_fd_flags(O_CLOEXEC);
> +       if (fd < 0) {
> +               path_put(&root);
> +               return fd;
> +       }
> +
> +       f = dentry_open(&root, O_PATH | O_NOFOLLOW, current_cred());
> +       path_put(&root);
> +       if (IS_ERR(f)) {
> +               put_unused_fd(fd);
> +               return PTR_ERR(f);
> +       }
> +
> +       fd_install(fd, f);
> +       return fd;
> +}
> +
> +static long ovl_ioctl_get_layers_info(struct file *filp, unsigned long arg)
> +{
> +       struct super_block *sb = file_inode(filp)->i_sb;
> +       struct ovl_fs *ofs = OVL_FS(sb);
> +       struct ovl_layers_info info = {
> +               .numlower = ofs->numlayer - 1,
> +               .numlowerdata = ofs->numdatalayer,
> +               .has_upper = !!ovl_upper_mnt(ofs),
> +       };
> +
> +       if (copy_to_user((void __user *)arg, &info, sizeof(info)))
> +               return -EFAULT;
> +
> +       return 0;
> +}
> +
> +long ovl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> +{
> +       struct ovl_fs *ofs = OVL_FS(file_inode(filp)->i_sb);
> +
> +       if (!ns_capable(ofs->creator_cred->user_ns, CAP_SYS_ADMIN))
> +               return -EPERM;
> +
> +       switch (cmd) {
> +       case OVL_IOC_OPEN_LAYER:
> +               return ovl_ioctl_open_layer(filp, arg);
> +       case OVL_IOC_GET_LAYERS_INFO:
> +               return ovl_ioctl_get_layers_info(filp, arg);
> +       default:
> +               return -ENOTTY;
> +       }
> +}
> +
>  struct file_system_type ovl_fs_type = {
>         .owner                  = THIS_MODULE,
>         .name                   = "overlay",
> diff --git a/include/uapi/linux/overlay.h b/include/uapi/linux/overlay.h
> new file mode 100644
> index 000000000000..c92ccecd9e21
> --- /dev/null
> +++ b/include/uapi/linux/overlay.h

overlayfs.h please

> @@ -0,0 +1,30 @@
> +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> +#ifndef _UAPI_LINUX_OVERLAY_H
> +#define _UAPI_LINUX_OVERLAY_H
> +
> +#include <linux/ioctl.h>
> +#include <linux/types.h>
> +
> +/**
> + * struct ovl_layers_info - overlay layer configuration summary
> + * @numlower:     number of lower (metadata) layers
> + * @numlowerdata: number of data-only lower layers
> + * @has_upper:    1 if an upper layer is configured, 0 otherwise
> + */
> +struct ovl_layers_info {
> +       __u32 numlower;
> +       __u32 numlowerdata;
> +       __u32 has_upper;
> +};

Whether this stays as ioctl or statmount blob

please use:

__u32 flags;
__u32 pad;

and use a flag for has_upper so we can extend this data struct in the future.

Thanks,
Amir.

^ permalink raw reply

* Re: [PATCH] ovl: add ioctls to retrieve layer file descriptors
From: Miklos Szeredi @ 2026-07-08 10:24 UTC (permalink / raw)
  To: Giuseppe Scrivano; +Cc: linux-unionfs, linux-fsdevel, linux-api
In-Reply-To: <20260708095831.3381978-1-gscrivan@redhat.com>

On Wed, 8 Jul 2026 at 12:00, Giuseppe Scrivano <gscrivan@redhat.com> wrote:
>
> Add two ioctls to overlay filesystem to allow userspace to retrieve
> information about the overlay layers:
>
> OVL_IOC_OPEN_LAYER:     return an O_PATH fd to the root of a layer.
>                         arg == 0 returns the upper layer (-ENOENT if
>                         no upper is configured), arg >= 1 returns
>                         lower layers (-ENOENT if index is out of
>                         range).

We could do this with a plain open() call.  Something like the magic
symlinks we have under /proc/PID/fd/.   Question is where could these
live...

> OVL_IOC_GET_LAYERS_INFO: copy a struct ovl_layers_info to userspace
>                         with numlower, numlowerdata, and has_upper.

Isn't this info obtainable via statmount(2) already?  If not, it
should be there, instead of a specialized ioctl.

> --- a/fs/overlayfs/ovl_entry.h
> +++ b/fs/overlayfs/ovl_entry.h
> @@ -35,6 +35,8 @@ struct ovl_layer {
>         struct vfsmount *mnt;
>         /* Trap in ovl inode cache */
>         struct inode *trap;
> +       /* Keeps the original fsmount file alive for OVL_IOC_OPEN_LAYER */
> +       struct file *origin;

Don't need to keep the file open: the only info missing is the
original vfsmount, everything else is already there to reconstruct the
file.

Thanks,
Miklos

^ permalink raw reply

* [PATCH] ovl: add ioctls to retrieve layer file descriptors
From: Giuseppe Scrivano @ 2026-07-08  9:58 UTC (permalink / raw)
  To: linux-unionfs; +Cc: linux-fsdevel, linux-api

Add two ioctls to overlay filesystem to allow userspace to retrieve
information about the overlay layers:

OVL_IOC_OPEN_LAYER:     return an O_PATH fd to the root of a layer.
                        arg == 0 returns the upper layer (-ENOENT if
                        no upper is configured), arg >= 1 returns
                        lower layers (-ENOENT if index is out of
                        range).
OVL_IOC_GET_LAYERS_INFO: copy a struct ovl_layers_info to userspace
                        with numlower, numlowerdata, and has_upper.

The ioctls work on any overlayfs file or directory and require
CAP_SYS_ADMIN in the mounter's user namespace.

The UAPI constants and struct are defined in include/uapi/linux/overlay.h.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
---
 MAINTAINERS                  |  1 +
 fs/overlayfs/file.c          |  2 ++
 fs/overlayfs/overlayfs.h     |  4 +++
 fs/overlayfs/ovl_entry.h     |  2 ++
 fs/overlayfs/params.c        | 10 ++++++
 fs/overlayfs/params.h        |  1 +
 fs/overlayfs/readdir.c       |  2 ++
 fs/overlayfs/super.c         | 70 ++++++++++++++++++++++++++++++++++++
 include/uapi/linux/overlay.h | 30 ++++++++++++++++
 9 files changed, 122 insertions(+)
 create mode 100644 include/uapi/linux/overlay.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 25453040dffb..b64c696686e4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20368,6 +20368,7 @@ S:	Supported
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs.git
 F:	Documentation/filesystems/overlayfs.rst
 F:	fs/overlayfs/
+F:	include/uapi/linux/overlay.h
 
 P54 WIRELESS DRIVER
 M:	Christian Lamparter <chunkeey@googlemail.com>
diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c
index 27cc07738f33..fc9c448c5959 100644
--- a/fs/overlayfs/file.c
+++ b/fs/overlayfs/file.c
@@ -649,4 +649,6 @@ const struct file_operations ovl_file_operations = {
 	.copy_file_range	= ovl_copy_file_range,
 	.remap_file_range	= ovl_remap_file_range,
 	.setlease		= generic_setlease,
+	.unlocked_ioctl		= ovl_ioctl,
+	.compat_ioctl		= compat_ptr_ioctl,
 };
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index b75df37f70ac..10f7ef8cb78c 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -8,6 +8,7 @@
 #include <linux/uuid.h>
 #include <linux/fs.h>
 #include <linux/fsverity.h>
+#include <uapi/linux/overlay.h>
 #include <linux/namei.h>
 #include <linux/posix_acl.h>
 #include <linux/posix_acl_xattr.h>
@@ -908,6 +909,9 @@ void ovl_tempname(char name[OVL_TEMPNAME_SIZE]);
 struct dentry *ovl_create_temp(struct ovl_fs *ofs, struct dentry *workdir,
 			       struct ovl_cattr *attr);
 
+/* super.c */
+long ovl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
+
 /* file.c */
 extern const struct file_operations ovl_file_operations;
 int ovl_real_fileattr_get(const struct path *realpath, struct file_kattr *fa);
diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h
index 80cad4ea96a3..b8f4bca89a27 100644
--- a/fs/overlayfs/ovl_entry.h
+++ b/fs/overlayfs/ovl_entry.h
@@ -35,6 +35,8 @@ struct ovl_layer {
 	struct vfsmount *mnt;
 	/* Trap in ovl inode cache */
 	struct inode *trap;
+	/* Keeps the original fsmount file alive for OVL_IOC_OPEN_LAYER */
+	struct file *origin;
 	struct ovl_sb *fs;
 	/* Index of this layer in fs root (upper idx == 0) */
 	int idx;
diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c
index c93fcaa45d4a..92d1a56178f2 100644
--- a/fs/overlayfs/params.c
+++ b/fs/overlayfs/params.c
@@ -482,6 +482,11 @@ static int ovl_parse_layer(struct fs_context *fc, struct fs_parameter *param,
 			return PTR_ERR(layer_name);
 
 		err = ovl_do_parse_layer(fc, layer_name, &layer_path, layer);
+		if (!err && !is_upper_layer(layer)) {
+			struct ovl_fs_context *ctx = fc->fs_private;
+
+			ctx->lower[ctx->nr - 1].origin = get_file(param->file);
+		}
 		break;
 	}
 	default:
@@ -504,6 +509,9 @@ static void ovl_reset_lowerdirs(struct ovl_fs_context *ctx)
 		path_put(&l->path);
 		kfree(l->name);
 		l->name = NULL;
+		if (l->origin)
+			fput(l->origin);
+		l->origin = NULL;
 	}
 	ctx->nr = 0;
 	ctx->nr_data = 0;
@@ -856,6 +864,8 @@ void ovl_free_fs(struct ovl_fs *ofs)
 	mounts = (struct vfsmount **) ofs->config.lowerdirs;
 	for (i = 0; i < ofs->numlayer; i++) {
 		iput(ofs->layers[i].trap);
+		if (ofs->layers[i].origin)
+			fput(ofs->layers[i].origin);
 		kfree(ofs->config.lowerdirs[i]);
 		mounts[i] = ofs->layers[i].mnt;
 	}
diff --git a/fs/overlayfs/params.h b/fs/overlayfs/params.h
index ffd53cdd8482..1d8fe8fbaca2 100644
--- a/fs/overlayfs/params.h
+++ b/fs/overlayfs/params.h
@@ -22,6 +22,7 @@ struct ovl_opt_set {
 struct ovl_fs_context_layer {
 	char *name;
 	struct path path;
+	struct file *origin;
 };
 
 struct ovl_fs_context {
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
index e7fe29cb6028..7bab71c8bcc2 100644
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -1069,6 +1069,8 @@ const struct file_operations ovl_dir_operations = {
 	.iterate_shared	= shared_ovl_iterate,
 	.llseek		= ovl_dir_llseek,
 	.fsync		= ovl_dir_fsync,
+	.unlocked_ioctl	= ovl_ioctl,
+	.compat_ioctl	= compat_ptr_ioctl,
 	.release	= ovl_dir_release,
 	.setlease	= generic_setlease,
 };
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 60f0b7ceef0a..d143002e74b2 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -1107,6 +1107,8 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
 		 */
 		mnt->mnt_flags |= MNT_READONLY | MNT_NOATIME;
 
+		layers[ofs->numlayer].origin = l->origin;
+		l->origin = NULL;
 		layers[ofs->numlayer].trap = trap;
 		layers[ofs->numlayer].mnt = mnt;
 		layers[ofs->numlayer].idx = ofs->numlayer;
@@ -1568,6 +1570,74 @@ int ovl_fill_super(struct super_block *sb, struct fs_context *fc)
 	return err;
 }
 
+static long ovl_ioctl_open_layer(struct file *filp, unsigned long arg)
+{
+	struct super_block *sb = file_inode(filp)->i_sb;
+	struct ovl_fs *ofs = OVL_FS(sb);
+	struct path root;
+	struct file *f;
+	int fd;
+
+	if (arg >= ofs->numlayer)
+		return -ENOENT;
+	if (arg == 0 && !ovl_upper_mnt(ofs))
+		return -ENOENT;
+	if (!ofs->layers[arg].origin)
+		return -EOPNOTSUPP;
+
+	root.mnt = mntget(ofs->layers[arg].origin->f_path.mnt);
+	root.dentry = dget(root.mnt->mnt_root);
+
+	fd = get_unused_fd_flags(O_CLOEXEC);
+	if (fd < 0) {
+		path_put(&root);
+		return fd;
+	}
+
+	f = dentry_open(&root, O_PATH | O_NOFOLLOW, current_cred());
+	path_put(&root);
+	if (IS_ERR(f)) {
+		put_unused_fd(fd);
+		return PTR_ERR(f);
+	}
+
+	fd_install(fd, f);
+	return fd;
+}
+
+static long ovl_ioctl_get_layers_info(struct file *filp, unsigned long arg)
+{
+	struct super_block *sb = file_inode(filp)->i_sb;
+	struct ovl_fs *ofs = OVL_FS(sb);
+	struct ovl_layers_info info = {
+		.numlower = ofs->numlayer - 1,
+		.numlowerdata = ofs->numdatalayer,
+		.has_upper = !!ovl_upper_mnt(ofs),
+	};
+
+	if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+		return -EFAULT;
+
+	return 0;
+}
+
+long ovl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	struct ovl_fs *ofs = OVL_FS(file_inode(filp)->i_sb);
+
+	if (!ns_capable(ofs->creator_cred->user_ns, CAP_SYS_ADMIN))
+		return -EPERM;
+
+	switch (cmd) {
+	case OVL_IOC_OPEN_LAYER:
+		return ovl_ioctl_open_layer(filp, arg);
+	case OVL_IOC_GET_LAYERS_INFO:
+		return ovl_ioctl_get_layers_info(filp, arg);
+	default:
+		return -ENOTTY;
+	}
+}
+
 struct file_system_type ovl_fs_type = {
 	.owner			= THIS_MODULE,
 	.name			= "overlay",
diff --git a/include/uapi/linux/overlay.h b/include/uapi/linux/overlay.h
new file mode 100644
index 000000000000..c92ccecd9e21
--- /dev/null
+++ b/include/uapi/linux/overlay.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_LINUX_OVERLAY_H
+#define _UAPI_LINUX_OVERLAY_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/**
+ * struct ovl_layers_info - overlay layer configuration summary
+ * @numlower:     number of lower (metadata) layers
+ * @numlowerdata: number of data-only lower layers
+ * @has_upper:    1 if an upper layer is configured, 0 otherwise
+ */
+struct ovl_layers_info {
+	__u32 numlower;
+	__u32 numlowerdata;
+	__u32 has_upper;
+};
+
+/*
+ * Return an O_PATH fd to the root of the specified overlay layer.
+ * arg == 0: upper layer (returns -ENOENT if no upper is configured)
+ * arg >= 1: lower layers (returns -ENOENT if index is out of range)
+ */
+#define OVL_IOC_OPEN_LAYER	_IO('O', 1)
+
+/* Retrieve overlay layer configuration into struct ovl_layers_info. */
+#define OVL_IOC_GET_LAYERS_INFO	_IOR('O', 2, struct ovl_layers_info)
+
+#endif /* _UAPI_LINUX_OVERLAY_H */
-- 
2.55.0


^ permalink raw reply related

* [PATCH 2/2] erofs: add ioctl to retrieve the backing source file descriptor
From: Giuseppe Scrivano @ 2026-07-08  9:34 UTC (permalink / raw)
  To: linux-erofs; +Cc: linux-fsdevel, linux-api
In-Reply-To: <20260708093446.3370200-1-gscrivan@redhat.com>

Add EROFS_IOC_GET_SOURCE_FD ioctl that returns a file descriptor to the
backing image file for file-backed erofs mounts.

Returns -ENOENT for block-device-backed erofs mounts where there is no
backing file.

The UAPI constant is defined in include/uapi/linux/erofs.h.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
---
 fs/erofs/inode.c           | 25 +++++++++++++++++++++++++
 include/uapi/linux/erofs.h |  9 +++++++++
 2 files changed, 34 insertions(+)
 create mode 100644 include/uapi/linux/erofs.h

diff --git a/fs/erofs/inode.c b/fs/erofs/inode.c
index 45afe5c50de8..0a38cb5cfc5d 100644
--- a/fs/erofs/inode.c
+++ b/fs/erofs/inode.c
@@ -6,6 +6,8 @@
  */
 #include "xattr.h"
 #include <linux/compat.h>
+#include <linux/file.h>
+#include <uapi/linux/erofs.h>
 #include <trace/events/erofs.h>
 
 static int erofs_fill_symlink(struct inode *inode, void *bptr, unsigned int ofs)
@@ -356,6 +358,27 @@ static int erofs_ioctl_get_volume_label(struct inode *inode, void __user *arg)
 	return ret ? -EFAULT : 0;
 }
 
+static int erofs_ioctl_get_source_fd(struct file *filp)
+{
+	struct erofs_sb_info *sbi = EROFS_I_SB(file_inode(filp));
+	struct file *f;
+	int fd;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
+	if (!erofs_is_fileio_mode(sbi))
+		return -ENOENT;
+
+	fd = get_unused_fd_flags(O_CLOEXEC);
+	if (fd < 0)
+		return fd;
+
+	f = get_file(sbi->dif0.file);
+	fd_install(fd, f);
+	return fd;
+}
+
 long erofs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 {
 	struct inode *inode = file_inode(filp);
@@ -364,6 +387,8 @@ long erofs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 	switch (cmd) {
 	case FS_IOC_GETFSLABEL:
 		return erofs_ioctl_get_volume_label(inode, argp);
+	case EROFS_IOC_GET_SOURCE_FD:
+		return erofs_ioctl_get_source_fd(filp);
 	default:
 		return -ENOTTY;
 	}
diff --git a/include/uapi/linux/erofs.h b/include/uapi/linux/erofs.h
new file mode 100644
index 000000000000..17c835785ea9
--- /dev/null
+++ b/include/uapi/linux/erofs.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_LINUX_EROFS_H
+#define _UAPI_LINUX_EROFS_H
+
+#include <linux/ioctl.h>
+
+#define EROFS_IOC_GET_SOURCE_FD	_IO('e', 1)
+
+#endif /* _UAPI_LINUX_EROFS_H */
-- 
2.55.0


^ permalink raw reply related

* [PATCH 1/2] erofs: accept source file descriptor via fsconfig
From: Giuseppe Scrivano @ 2026-07-08  9:34 UTC (permalink / raw)
  To: linux-erofs; +Cc: linux-fsdevel, linux-api
In-Reply-To: <20260708093446.3370200-1-gscrivan@redhat.com>

Add fsparam_fd("source") so that userspace can pass an already-opened
file descriptor instead of a path string.  When the fd is provided via
fsconfig(FSCONFIG_SET_FD, "source", NULL, fd), it is stored directly
in sbi->dif0.file and erofs_fc_get_tree() skips the filp_open() call.

This is useful for mount namespaces where the backing file may not be
reachable by path, and for tools that already hold an fd to the image
(e.g. composefs reusing an erofs mount's backing file).

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
---
 fs/erofs/super.c | 36 +++++++++++++++++++++++++-----------
 1 file changed, 25 insertions(+), 11 deletions(-)

diff --git a/fs/erofs/super.c b/fs/erofs/super.c
index 86fa5c6a0c70..8ad1689f74b2 100644
--- a/fs/erofs/super.c
+++ b/fs/erofs/super.c
@@ -386,6 +386,7 @@ static void erofs_default_options(struct erofs_sb_info *sbi)
 enum {
 	Opt_user_xattr, Opt_acl, Opt_cache_strategy, Opt_dax, Opt_dax_enum,
 	Opt_device, Opt_domain_id, Opt_directio, Opt_fsoffset, Opt_inode_share,
+	Opt_source_fd,
 };
 
 static const struct constant_table erofs_param_cache_strategy[] = {
@@ -413,6 +414,7 @@ static const struct fs_parameter_spec erofs_fs_parameters[] = {
 	fsparam_flag_no("directio",	Opt_directio),
 	fsparam_u64("fsoffset",		Opt_fsoffset),
 	fsparam_flag("inode_share",	Opt_inode_share),
+	fsparam_fd("source",		Opt_source_fd),
 	{}
 };
 
@@ -524,6 +526,15 @@ static int erofs_fc_parse_param(struct fs_context *fc,
 		else
 			set_opt(&sbi->opt, INODE_SHARE);
 		break;
+	case Opt_source_fd:
+		if (!IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE)) {
+			errorfc(fc, "source fd option not supported");
+			return -EINVAL;
+		}
+		if (sbi->dif0.file)
+			fput(sbi->dif0.file);
+		sbi->dif0.file = get_file(param->file);
+		break;
 	}
 	return 0;
 }
@@ -752,14 +763,18 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
 
 static int erofs_fc_get_tree(struct fs_context *fc)
 {
-	int ret;
+	struct erofs_sb_info *sbi = fc->s_fs_info;
 
-	ret = get_tree_bdev_flags(fc, erofs_fc_fill_super,
-		IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE) ?
-			GET_TREE_BDEV_QUIET_LOOKUP : 0);
-	if (IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE) && ret == -ENOTBLK) {
-		struct erofs_sb_info *sbi = fc->s_fs_info;
+	if (!IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE) || !sbi->dif0.file) {
 		struct file *file;
+		int ret;
+
+		ret = get_tree_bdev_flags(fc, erofs_fc_fill_super,
+			IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE) ?
+				GET_TREE_BDEV_QUIET_LOOKUP : 0);
+		if (!IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE) ||
+		    ret != -ENOTBLK)
+			return ret;
 
 		if (!fc->source)
 			return invalf(fc, "No source specified");
@@ -767,12 +782,11 @@ static int erofs_fc_get_tree(struct fs_context *fc)
 		if (IS_ERR(file))
 			return PTR_ERR(file);
 		sbi->dif0.file = file;
-
-		if (S_ISREG(file_inode(sbi->dif0.file)->i_mode) &&
-		    sbi->dif0.file->f_mapping->a_ops->read_folio)
-			return get_tree_nodev(fc, erofs_fc_fill_super);
 	}
-	return ret;
+	if (S_ISREG(file_inode(sbi->dif0.file)->i_mode) &&
+	    sbi->dif0.file->f_mapping->a_ops->read_folio)
+		return get_tree_nodev(fc, erofs_fc_fill_super);
+	return -EINVAL;
 }
 
 static int erofs_fc_reconfigure(struct fs_context *fc)
-- 
2.55.0


^ permalink raw reply related

* [PATCH 0/2] erofs: fd-based source and backing file introspection
From: Giuseppe Scrivano @ 2026-07-08  9:34 UTC (permalink / raw)
  To: linux-erofs; +Cc: linux-fsdevel, linux-api

This series adds two features to erofs for file-backed mounts:

1. Accept a source file descriptor via fsconfig(FSCONFIG_SET_FD,
  "source", NULL, fd) as an alternative to a path string.  This is
  useful when the backing file isn't reachable by path in the caller's
  mount namespace.  For example, composefs reusing an already-mounted
  erofs image's backing file.

2. Add an EROFS_IOC_GET_SOURCE_FD ioctl that returns a file descriptor
  to the backing image file.  This allows userspace to retrieve the
  backing file from an existing erofs mount without needing to know or
  parse the original source path.

Giuseppe Scrivano (2):
  erofs: accept source file descriptor via fsconfig
  erofs: add ioctl to retrieve the backing source file descriptor

 fs/erofs/inode.c           | 25 +++++++++++++++++++++++++
 fs/erofs/super.c           | 36 +++++++++++++++++++++++++-----------
 include/uapi/linux/erofs.h |  9 +++++++++
 3 files changed, 59 insertions(+), 11 deletions(-)
 create mode 100644 include/uapi/linux/erofs.h

--
2.55.0


^ permalink raw reply

* Re: [RFC] Null Namespaces
From: Andy Lutomirski @ 2026-07-06 17:10 UTC (permalink / raw)
  To: Jann Horn
  Cc: Christian Brauner, Andy Lutomirski, John Ericson, Li Chen,
	Cong Wang, linux-arch, linux-kernel, linux-fsdevel, linux-api,
	Arnd Bergmann, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
	Dave Hansen, H. Peter Anvin, Jan Kara, Jonathan Corbet,
	Shuah Khan, Alexander Viro, Kees Cook, Sergei Zimmerman,
	Farid Zakaria
In-Reply-To: <CAG48ez0sh7vEzBxtaG1HCJ0bmUMmhF_JR3=xXcc4+vpfB6LUOQ@mail.gmail.com>

On Mon, Jul 6, 2026 at 9:55 AM Jann Horn <jannh@google.com> wrote:
>

> I mostly agree, though we might want to gate this on no_new_privs just
> to be sure - you could theoretically have a setuid root program that
> gives the caller more privileges if it can't find its config file.
> That's kind of a far-fetched scenario, and in reality it would
> probably fail because the dynamic linker can't be found, but there is
> precedent for other sandboxing stuff also requiring no_new_privs, so
> we might as well require that here, too...
>

My ancient patch did this gating.

FWIW, if we are contemplating letting unprivileged tasks chroot to an
*empty* mountns and they don't have privileges to bind anything there,
then the mnt_may_suid() will prevent them from using execveat to run a
setuid program.

> (I think it actually might be fine to just make chroot entirely
> unprivileged as long as no_new_privs is set, but I don't think we
> should actually do that, that would just be unnecessarily playing with
> fire and would probably confuse some security monitoring tools or
> such.)
>
> We should probably also reject if current_chrooted() is true, for the
> same reason we reject userns creation when that's true.
>

These empty-tree proposals might expose a little issue in
current_chrooted().  We don't want to check whether our root is the
namespace root -- I think we may want to check whether our root has a
parent.  Otherwise once you chroot to an empty tree once, you can't
chroot again, which is silly.  Maybe if we add this empty tree thing,
the definition of current_chrooted should change.

(This is already an issue with open_tree and probably even with
detached mounts before that, but I don't think it's as easily
observable.)

^ permalink raw reply

* Re: [RFC] Null Namespaces
From: Andy Lutomirski @ 2026-07-06 17:04 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Andy Lutomirski, Jann Horn, John Ericson, Li Chen, Cong Wang,
	linux-arch, linux-kernel, linux-fsdevel, linux-api, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan,
	Alexander Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria
In-Reply-To: <20260706-dabei-radeln-glitzer-71ecb835029c@brauner>

On Mon, Jul 6, 2026 at 8:31 AM Christian Brauner <brauner@kernel.org> wrote:
>
> On Thu, Jul 02, 2026 at 11:34:01AM +0200, Christian Brauner wrote:
> > On Mon, Jun 29, 2026 at 02:06:55PM -0700, Andy Lutomirski wrote:
> > > On Mon, Jun 29, 2026 at 4:45 AM Christian Brauner <brauner@kernel.org> wrote:
> > > >
> > >
> > > > But I guess the even simpler model would be to copy what I've been doing
> > > > for pidfs:
> > > >
> > > > +static struct path nullfs_root_path = {};
> > > > +
> > > > +void nullfs_get_root(struct path *path)
> > > > +{
> > > > +       *path = nullfs_root_path;
> > > > +       path_get(path);
> > > > +}
> > > > +
> > > >  static void __init init_mount_tree(void)
> > > >  {
> > > >         struct vfsmount *mnt, *nullfs_mnt;
> > > > @@ -6209,6 +6217,8 @@ static void __init init_mount_tree(void)
> > > >         /* Mount mutable rootfs on top of nullfs. */
> > > >         root.mnt                = nullfs_mnt;
> > > >         root.dentry             = nullfs_mnt->mnt_root;
> > > > +       nullfs_root_path.mnt    = nullfs_mnt;
> > > > +       pidfs_root_path.dentry  = nullfs_mnt->mnt_root;
> > > >
> > > >         LOCK_MOUNT_EXACT(mp, &root);
> > > >         if (unlikely(IS_ERR(mp.parent)))
> > > > diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
> > > > index aadfbf6e0cb3..f55c87c70b78 100644
> > > > --- a/include/uapi/linux/fcntl.h
> > > > +++ b/include/uapi/linux/fcntl.h
> > > > @@ -124,6 +124,7 @@ struct delegation {
> > > >
> > > >  #define FD_PIDFS_ROOT                  -10002 /* Root of the pidfs filesystem */
> > > >  #define FD_NSFS_ROOT                   -10003 /* Root of the nsfs filesystem */
> > > > +#define FD_NULLFS_ROOT                 -10004 /* Root of the nullfs filesystem */
> > > >  #define FD_INVALID                     -10009 /* Invalid file descriptor: -10000 - EBADF = -10009 */
> > > >
> > > >  /* Generic flags for the *at(2) family of syscalls. */
> > > >
> > > > we then add fchroot() (overdue anyway) and then teach both fchdir() and
> > > > fchroot() to honor FD_NULLFS_ROOT. Then a process may shed its fs state
> > > > and move itself into nullfs. Restrict *chdir() and *chroot() for said
> > > > process via seccomp and it's locked in forever as well.
> > > >
> > >
> > > One thing comes to mind that might need a bit of care: this would give
> > > an API for any task to get an fd to a directory that lives in the init
> > > mount namespace.  It's not at all obvious to me that this is dangerous
> > > or even observable (you're not about to find a setuid program in
> > > nullfs), but I think it's at least worth a tiny bit of consideration.
> >
> > Yes, I thought about this as well. But it doesn't have to be this way.
> > Every mount namespaces has nullfs as it's root ever since I introduced
> > it. Which means FD_NULLFS_ROOT can also just mean "nullfs within that
> > specific mount namespace". That's fine.
> >
> > For my FD_FAILFS_ROOT proposal it would be enough if we make failfs
> > SB_KERNMOUNT which means it's logically distinct from every mount
> > namespace. I think that might be the right thing to do. I need to spend
> > one or more brain cycles on this though.
>
> I had to take a long drive on Sunday and I kept thinking about both
> FD_NULLFS_ROOT and FD_FAILFS_ROOT and ofc there are some things to
> consider/discuss.
>
> I think the straightforward solution to FD_NULLFS_ROOT would be to just:
>
> - make it always available
> - refer to the caller's mount namespace nullfs
> - work with fchroot()/fchdir()
>
> So I considered two chroot() use-cases for the sake of simplicity:
>
> (1) You want to isolate yourself for the sake of lookup
>
> (2) You want to isolate yourself to assemble a "private mount tree" but
>     not really be in a separate namespace (very odd use-case... but it
>     helps to make a point).
>
> The problem with this approach is that everyone who chroots into the
> nullfs root would suffer from the problem that any mount on top of it is
> still visible. So that kinda makes it pointless for both (1) and (2).

Ugh.

You're at least functionally correct, although this all reminds me
that I have never felt like Linux's vfs mount hierarchy makes any
sense:

root@debian:/mnt/empty# mkdir hidden
root@debian:/mnt/empty# mount --bind /usr .
root@debian:/mnt/empty# ls
hidden
root@debian:/mnt/empty# ls hidden/..
bin  games  include  lib  libexec  local  sbin share  src

>
> FD_FAILFS_ROOT on the other hand should work fine. It would be a shared
> single fs with SB_KERNMOUNT and you can't do anything at all:
>
> - no lookup
> - no creation (duh)
> - no stat
> - no mounting
>
> We could certainly allow a chroot() into this which would mean from that
> point onward all your lookup bust be relative to a given file
> descriptor. Anything that requires absolute paths would fail. Which also
> means any absolute symlink would fail afaict. It's kinda like an
> fs_struct variant of: RESOLVE_BENEATH where a FD_FAILFS_ROOT fs_struct
> forces you to provide an actual dirfd...
>
> chroot()ing back into anything non-empty would necessarily require
> CAP_SYS_CHROOT. And since you're chroot()ed you can't unshare a userns.
> So the only way to get out of this is by having access to a file
> descriptor to a mount namespace that the caller has privilege over and
> can setns() into. So it's mostly a "throw-away-the-key" moment.
>
> > > But if this happens, maybe we could finally land one of the patches to
> > > enable unprivileged chroot?  It's been tried a few times.
> > >
> > > https://lore.kernel.org/lkml/0e2f0f54e19bff53a3739ecfddb4ffa9a6dbde4d.1327858005.git.luto@amacapital.net/
> > >
> > > https://lore.kernel.org/all/20210316203633.424794-2-mic@digikod.net/
> > >
> > > I think the need for it has reduced a tiny bit with user namespaces,
> > > as you can sort of emulate it by unsharing your user namespace and
> > > thus getting enough privilege, but this is rather heavyweight and
> > > limiting.
> >
> > I think we could make that work with both FD_NULLFS_ROOT and
> > FD_FAILFS_ROOT...
> >
> > >
> > >
> > > If all of the above landed, then the old chroot /var/empty kludge that
> > > security-minded programs have done for decades could finally be
> > > modernized and not require any privilege :)
> >
> > I think I like it.
> >
> > > Hmm, thinking aloud: every now and then someone brings up the idea of
> > > having an fd (really an OFD) that points to a file or a directory but
> > > carries less in the way of permissions/capabilities than the usual
> > > OFDs.  If we had a way to make an OFD to a directory that forced
> > > RESOLVE_BENEATH (or RESOLVE_IN_ROOT) and that propagated that
> > > restriction to anything you open using it, and if an unprivileged
> > > process could chroot itself to nullfs, then we would be getting quite
> > > close to what Capsicum can do.
> >
> > Next steps. I hear you volunteering...
>
> Thanks for the braindump. I need to find time to process it all.
>

^ permalink raw reply

* Re: [RFC] Null Namespaces
From: Jann Horn @ 2026-07-06 16:32 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Andy Lutomirski, John Ericson, Li Chen, Cong Wang, linux-arch,
	linux-kernel, linux-fsdevel, linux-api, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan,
	Alexander Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria
In-Reply-To: <20260706-dabei-radeln-glitzer-71ecb835029c@brauner>

On Mon, Jul 6, 2026 at 4:52 PM Christian Brauner <brauner@kernel.org> wrote:
> I think the straightforward solution to FD_NULLFS_ROOT would be to just:
>
> - make it always available
> - refer to the caller's mount namespace nullfs
> - work with fchroot()/fchdir()
>
> So I considered two chroot() use-cases for the sake of simplicity:
>
> (1) You want to isolate yourself for the sake of lookup
>
> (2) You want to isolate yourself to assemble a "private mount tree" but
>     not really be in a separate namespace (very odd use-case... but it
>     helps to make a point).
>
> The problem with this approach is that everyone who chroots into the
> nullfs root would suffer from the problem that any mount on top of it is
> still visible. So that kinda makes it pointless for both (1) and (2).
>
> Also all mounts that someone else would do would also be visible
> allowing multiple chroot()ers to affect each others state. That also
> would somewhat defeat the purpose of the chroot(). So I'm not convinced
> this is what we should do.
>
> IOW, I think FD_NULLFS_ROOT to chroot to the nullfs of your mount
> namespace is mostly useless and just not workable for unprivileged
> fchroot().
>
> Instead, this made me consider whether it wouldn't make more sense to
> allow unprivileged mount namespace unsharing for both CLONE_EMPTY_MNTNS
> and UNSHARE_EMPTY_MNTNS. Look, there's no real risk at all. It is
> literally just placing the caller into a new mount namespace with only
> nullfs in there. I fail to see any attack vector here. It's literally
> self-sandboxing and you give up access to anything that you didn't have
> a file descriptor open for. It's actually a bonus, because you don't
> need to use userns for this. You just throw away your filesystem state.

I mostly agree, though we might want to gate this on no_new_privs just
to be sure - you could theoretically have a setuid root program that
gives the caller more privileges if it can't find its config file.
That's kind of a far-fetched scenario, and in reality it would
probably fail because the dynamic linker can't be found, but there is
precedent for other sandboxing stuff also requiring no_new_privs, so
we might as well require that here, too...

(I think it actually might be fine to just make chroot entirely
unprivileged as long as no_new_privs is set, but I don't think we
should actually do that, that would just be unnecessarily playing with
fire and would probably confuse some security monitoring tools or
such.)

We should probably also reject if current_chrooted() is true, for the
same reason we reject userns creation when that's true.

^ permalink raw reply

* Re: [RFC] Null Namespaces
From: Christian Brauner @ 2026-07-06 14:52 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: Jann Horn, John Ericson, Li Chen, Cong Wang, linux-arch,
	linux-kernel, linux-fsdevel, linux-api, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan,
	Alexander Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria
In-Reply-To: <20260702-entladen-farbkombinationen-klarheit-fe24cb608f23@brauner>

On Thu, Jul 02, 2026 at 11:34:01AM +0200, Christian Brauner wrote:
> On Mon, Jun 29, 2026 at 02:06:55PM -0700, Andy Lutomirski wrote:
> > On Mon, Jun 29, 2026 at 4:45 AM Christian Brauner <brauner@kernel.org> wrote:
> > >
> > 
> > > But I guess the even simpler model would be to copy what I've been doing
> > > for pidfs:
> > >
> > > +static struct path nullfs_root_path = {};
> > > +
> > > +void nullfs_get_root(struct path *path)
> > > +{
> > > +       *path = nullfs_root_path;
> > > +       path_get(path);
> > > +}
> > > +
> > >  static void __init init_mount_tree(void)
> > >  {
> > >         struct vfsmount *mnt, *nullfs_mnt;
> > > @@ -6209,6 +6217,8 @@ static void __init init_mount_tree(void)
> > >         /* Mount mutable rootfs on top of nullfs. */
> > >         root.mnt                = nullfs_mnt;
> > >         root.dentry             = nullfs_mnt->mnt_root;
> > > +       nullfs_root_path.mnt    = nullfs_mnt;
> > > +       pidfs_root_path.dentry  = nullfs_mnt->mnt_root;
> > >
> > >         LOCK_MOUNT_EXACT(mp, &root);
> > >         if (unlikely(IS_ERR(mp.parent)))
> > > diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
> > > index aadfbf6e0cb3..f55c87c70b78 100644
> > > --- a/include/uapi/linux/fcntl.h
> > > +++ b/include/uapi/linux/fcntl.h
> > > @@ -124,6 +124,7 @@ struct delegation {
> > >
> > >  #define FD_PIDFS_ROOT                  -10002 /* Root of the pidfs filesystem */
> > >  #define FD_NSFS_ROOT                   -10003 /* Root of the nsfs filesystem */
> > > +#define FD_NULLFS_ROOT                 -10004 /* Root of the nullfs filesystem */
> > >  #define FD_INVALID                     -10009 /* Invalid file descriptor: -10000 - EBADF = -10009 */
> > >
> > >  /* Generic flags for the *at(2) family of syscalls. */
> > >
> > > we then add fchroot() (overdue anyway) and then teach both fchdir() and
> > > fchroot() to honor FD_NULLFS_ROOT. Then a process may shed its fs state
> > > and move itself into nullfs. Restrict *chdir() and *chroot() for said
> > > process via seccomp and it's locked in forever as well.
> > >
> > 
> > One thing comes to mind that might need a bit of care: this would give
> > an API for any task to get an fd to a directory that lives in the init
> > mount namespace.  It's not at all obvious to me that this is dangerous
> > or even observable (you're not about to find a setuid program in
> > nullfs), but I think it's at least worth a tiny bit of consideration.
> 
> Yes, I thought about this as well. But it doesn't have to be this way.
> Every mount namespaces has nullfs as it's root ever since I introduced
> it. Which means FD_NULLFS_ROOT can also just mean "nullfs within that
> specific mount namespace". That's fine.
> 
> For my FD_FAILFS_ROOT proposal it would be enough if we make failfs
> SB_KERNMOUNT which means it's logically distinct from every mount
> namespace. I think that might be the right thing to do. I need to spend
> one or more brain cycles on this though.

I had to take a long drive on Sunday and I kept thinking about both
FD_NULLFS_ROOT and FD_FAILFS_ROOT and ofc there are some things to
consider/discuss.

I think the straightforward solution to FD_NULLFS_ROOT would be to just:

- make it always available
- refer to the caller's mount namespace nullfs
- work with fchroot()/fchdir()

So I considered two chroot() use-cases for the sake of simplicity:

(1) You want to isolate yourself for the sake of lookup

(2) You want to isolate yourself to assemble a "private mount tree" but
    not really be in a separate namespace (very odd use-case... but it
    helps to make a point).

The problem with this approach is that everyone who chroots into the
nullfs root would suffer from the problem that any mount on top of it is
still visible. So that kinda makes it pointless for both (1) and (2).

Also all mounts that someone else would do would also be visible
allowing multiple chroot()ers to affect each others state. That also
would somewhat defeat the purpose of the chroot(). So I'm not convinced
this is what we should do.

IOW, I think FD_NULLFS_ROOT to chroot to the nullfs of your mount
namespace is mostly useless and just not workable for unprivileged
fchroot().

Instead, this made me consider whether it wouldn't make more sense to
allow unprivileged mount namespace unsharing for both CLONE_EMPTY_MNTNS
and UNSHARE_EMPTY_MNTNS. Look, there's no real risk at all. It is
literally just placing the caller into a new mount namespace with only
nullfs in there. I fail to see any attack vector here. It's literally
self-sandboxing and you give up access to anything that you didn't have
a file descriptor open for. It's actually a bonus, because you don't
need to use userns for this. You just throw away your filesystem state.

FD_FAILFS_ROOT on the other hand should work fine. It would be a shared
single fs with SB_KERNMOUNT and you can't do anything at all:

- no lookup
- no creation (duh)
- no stat
- no mounting

We could certainly allow a chroot() into this which would mean from that
point onward all your lookup bust be relative to a given file
descriptor. Anything that requires absolute paths would fail. Which also
means any absolute symlink would fail afaict. It's kinda like an
fs_struct variant of: RESOLVE_BENEATH where a FD_FAILFS_ROOT fs_struct
forces you to provide an actual dirfd...

chroot()ing back into anything non-empty would necessarily require
CAP_SYS_CHROOT. And since you're chroot()ed you can't unshare a userns.
So the only way to get out of this is by having access to a file
descriptor to a mount namespace that the caller has privilege over and
can setns() into. So it's mostly a "throw-away-the-key" moment. 

> > But if this happens, maybe we could finally land one of the patches to
> > enable unprivileged chroot?  It's been tried a few times.
> > 
> > https://lore.kernel.org/lkml/0e2f0f54e19bff53a3739ecfddb4ffa9a6dbde4d.1327858005.git.luto@amacapital.net/
> > 
> > https://lore.kernel.org/all/20210316203633.424794-2-mic@digikod.net/
> > 
> > I think the need for it has reduced a tiny bit with user namespaces,
> > as you can sort of emulate it by unsharing your user namespace and
> > thus getting enough privilege, but this is rather heavyweight and
> > limiting.
> 
> I think we could make that work with both FD_NULLFS_ROOT and
> FD_FAILFS_ROOT...
> 
> > 
> > 
> > If all of the above landed, then the old chroot /var/empty kludge that
> > security-minded programs have done for decades could finally be
> > modernized and not require any privilege :)
> 
> I think I like it.
> 
> > Hmm, thinking aloud: every now and then someone brings up the idea of
> > having an fd (really an OFD) that points to a file or a directory but
> > carries less in the way of permissions/capabilities than the usual
> > OFDs.  If we had a way to make an OFD to a directory that forced
> > RESOLVE_BENEATH (or RESOLVE_IN_ROOT) and that propagated that
> > restriction to anything you open using it, and if an unprivileged
> > process could chroot itself to nullfs, then we would be getting quite
> > close to what Capsicum can do.
> 
> Next steps. I hear you volunteering...

Thanks for the braindump. I need to find time to process it all.

^ permalink raw reply

* Re: [RFC] Null Namespaces
From: Li Chen @ 2026-07-04 13:20 UTC (permalink / raw)
  To: John Ericson
  Cc: Cong Wang, Christian Brauner, linux-arch, linux-kernel,
	linux-fsdevel, linux-api, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan,
	Alexander Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria
In-Reply-To: <a49ce818-f38d-41b0-bbf7-80b8aad998b1@app.fastmail.com>

Hi John,

Sorry for the silence. I got pulled into some work stuff recently and
only caught up with these threads now. I still have not studied every
patch line by line, but I wanted to say that I really appreciate you
pushing on this.

The embryonic-process framing lines up well with the direction I have
been trying to move toward after the earlier feedback: make the
not-yet-runnable state explicit, and keep task creation separate from the
state installed into the new task.

For my next process-builder RFC, I plan to keep the first step small: a
pidfd-based builder for posix_spawn-style semantics, with limited action
support; it will not yet have the pristine/no-source backend. 
So cwd/root, non-CLOEXEC fds, and
other selected source state can still be inherited unless actions or
attributes say otherwise.

I think the lower-authority/null-namespace side should be an explicit
mode, not the default for the posix_spawn-compatible path. Otherwise it
would change existing expectations around cwd/root, inherited fds, and
other source state.

Longer term, I would like to add that lower-authority side as follow-up
work: pristine/no-source process creation, where resources are installed
deliberately instead of starting from the parent's fs/fd/mm state. I had
an earlier experimental local branch that tried to separate pristine task
allocation from source-state installation, but that is not the RFC I plan
to send first.

For that later mode, Christian's nullfs/failfs direction also looks like
the more practical way to express the low-authority fs state.

Thanks again for pushing this forward.

Regards,
Li​



^ permalink raw reply

* Directory capability brain dump (Re: [RFC] Null Namespaces)
From: Andy Lutomirski @ 2026-07-03 17:35 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Andy Lutomirski, John Ericson, Li Chen, Cong Wang, linux-arch,
	LKML, Linux FS Devel, Linux API, Arnd Bergmann, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, Dave Hansen, H. Peter Anvin,
	Jan Kara, Jonathan Corbet, Shuah Khan, Alexander Viro, Kees Cook,
	Sergei Zimmerman, Farid Zakaria
In-Reply-To: <20260702-entladen-farbkombinationen-klarheit-fe24cb608f23@brauner>

On Thu, Jul 2, 2026 at 3:02 AM Christian Brauner <brauner@kernel.org> wrote:
>
> On Mon, Jun 29, 2026 at 02:06:55PM -0700, Andy Lutomirski wrote:
> > On Mon, Jun 29, 2026 at 4:45 AM Christian Brauner <brauner@kernel.org> wrote:
> > >
> >
> > > But I guess the even simpler model would be to copy what I've been doing
> > > for pidfs:
> > >
> > > +static struct path nullfs_root_path = {};
> > > +
> > > +void nullfs_get_root(struct path *path)
> > > +{
> > > +       *path = nullfs_root_path;
> > > +       path_get(path);
> > > +}
> > > +
> > >  static void __init init_mount_tree(void)
> > >  {
> > >         struct vfsmount *mnt, *nullfs_mnt;
> > > @@ -6209,6 +6217,8 @@ static void __init init_mount_tree(void)
> > >         /* Mount mutable rootfs on top of nullfs. */
> > >         root.mnt                = nullfs_mnt;
> > >         root.dentry             = nullfs_mnt->mnt_root;
> > > +       nullfs_root_path.mnt    = nullfs_mnt;
> > > +       pidfs_root_path.dentry  = nullfs_mnt->mnt_root;
> > >
> > >         LOCK_MOUNT_EXACT(mp, &root);
> > >         if (unlikely(IS_ERR(mp.parent)))
> > > diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
> > > index aadfbf6e0cb3..f55c87c70b78 100644
> > > --- a/include/uapi/linux/fcntl.h
> > > +++ b/include/uapi/linux/fcntl.h
> > > @@ -124,6 +124,7 @@ struct delegation {
> > >
> > >  #define FD_PIDFS_ROOT                  -10002 /* Root of the pidfs filesystem */
> > >  #define FD_NSFS_ROOT                   -10003 /* Root of the nsfs filesystem */
> > > +#define FD_NULLFS_ROOT                 -10004 /* Root of the nullfs filesystem */
> > >  #define FD_INVALID                     -10009 /* Invalid file descriptor: -10000 - EBADF = -10009 */
> > >
> > >  /* Generic flags for the *at(2) family of syscalls. */
> > >
> > > we then add fchroot() (overdue anyway) and then teach both fchdir() and
> > > fchroot() to honor FD_NULLFS_ROOT. Then a process may shed its fs state
> > > and move itself into nullfs. Restrict *chdir() and *chroot() for said
> > > process via seccomp and it's locked in forever as well.
> > >
> >
> > One thing comes to mind that might need a bit of care: this would give
> > an API for any task to get an fd to a directory that lives in the init
> > mount namespace.  It's not at all obvious to me that this is dangerous
> > or even observable (you're not about to find a setuid program in
> > nullfs), but I think it's at least worth a tiny bit of consideration.
>
> Yes, I thought about this as well. But it doesn't have to be this way.
> Every mount namespaces has nullfs as it's root ever since I introduced
> it. Which means FD_NULLFS_ROOT can also just mean "nullfs within that
> specific mount namespace". That's fine.
>
> For my FD_FAILFS_ROOT proposal it would be enough if we make failfs
> SB_KERNMOUNT which means it's logically distinct from every mount
> namespace. I think that might be the right thing to do. I need to spend
> one or more brain cycles on this though.
>
> >
> > But if this happens, maybe we could finally land one of the patches to
> > enable unprivileged chroot?  It's been tried a few times.
> >
> > https://lore.kernel.org/lkml/0e2f0f54e19bff53a3739ecfddb4ffa9a6dbde4d.1327858005.git.luto@amacapital.net/
> >
> > https://lore.kernel.org/all/20210316203633.424794-2-mic@digikod.net/
> >
> > I think the need for it has reduced a tiny bit with user namespaces,
> > as you can sort of emulate it by unsharing your user namespace and
> > thus getting enough privilege, but this is rather heavyweight and
> > limiting.
>
> I think we could make that work with both FD_NULLFS_ROOT and
> FD_FAILFS_ROOT...
>
> >
> >
> > If all of the above landed, then the old chroot /var/empty kludge that
> > security-minded programs have done for decades could finally be
> > modernized and not require any privilege :)
>
> I think I like it.
>
> > Hmm, thinking aloud: every now and then someone brings up the idea of
> > having an fd (really an OFD) that points to a file or a directory but
> > carries less in the way of permissions/capabilities than the usual
> > OFDs.  If we had a way to make an OFD to a directory that forced
> > RESOLVE_BENEATH (or RESOLVE_IN_ROOT) and that propagated that
> > restriction to anything you open using it, and if an unprivileged
> > process could chroot itself to nullfs, then we would be getting quite
> > close to what Capsicum can do.
>
> Next steps. I hear you volunteering...
>

Haha.  I volunteer to give somewhat of a brain dump, which is very
incomplete.  Doing a decent job of this would be maybe 90% figuring
out nasty details and 10% implementation.  I might even volunteer to
do some implementation if there is anything resembling agreement on a
design.

First of all, I was way too glib with "OFD", because OFD doesn't cover
cwd, root, or any of the magic links in /proc, nor does OFD cover
mountpoints.

Second, I confess to having my brain dump be hopelessly polluted by a
substantial ulterior motive: I want fds (or whatevers)-as-capabilities
to go both ways, which means that I want to be able to open an fd that
captures certain of my privileges such that someone with different
current->cred can use it.  And I want *that* ability to work with bind
mounts and such.  The main motivation for this is that I *despise* the
way that outside-a-container filesystem modes interact with
containers.  It makes me extremely disappointed that I can take an
ordinary subdirectory of my home directory and mount it into a Linux
container on a Mac running MacOS without mucking with permissions at
all, but trying to do the same thing on the average Linux distro plus
the average container runtime is an exercise in hair pulling,
especially when SELinux is involved.  *Why* can't it just work?

So let's call the directory version of this thing a "directory
capability", and directory capabilities can live in OFDs or in cwd,
root, or a vfsmount.

The default directory capability (IMO) ought to be exactly equivalent
to O_PATH or a cwd.  But I think one should be able to make a
restricted one with bits like:

- read: if not set, you can't read the files in here. executing
unconditionally counts as reading for this purpose.

- execute: If not set, you can't execute the files in here.  (This
kind of "execute" has nothing to do with opening subdirectories.
we're not talking about POSIX.)  Everyone can argue about whether
running a shell script and/or loading a shared library counts as
executing.

- write: If not set, you can't write the files in here.

- create: If not set, you can't create files in here.  (How does this
interact with write?)

- maybe some bits to prevent writing metadata, fs-specific ioctls, etc.

- dotdot and/or ancestor: needs thought.  One way would be what I
mentioned above: any path resolution starting at this directory
capability or anything derived from it forces RESOLVE_BENEATH.

- open question: what happens if i open a directory and then unmount
something that was mounted on one of its subdirectories?  Does this
matter?

The general idea of using these things is that one might open an fd
with constraints:

fd = open_with_constraints(..., read | execute);

Now using fd in openat forces RESOLVE_BENEATH (because I didn't say
dotdot), and any attempt to open a file for write or to create a new
file will fail.  And if I fchdir or fchroot to fd, then those
constraints still apply.  And if I do:

fd = open_with_constraints(..., read);
fd2 = open_with_constraints(fd, ..., read | execute);

then we need to decide whether to fail outright or to return something
that doesn't have execute.

If I do:

fd = open_with_constraints(..., read);
fchdir(fd);
open("/proc/self/cwd/../foo") needs to fail (or perhaps just open ~/foo?)

If I take that same fd and bind-mount it on /mnt/restricted, then I
should not be able to find its ancestors via /mnt/restricted/..
(obviously -- that doesn't work anyway).  (But maybe there should be
an extra flag "seethrough" which, if not set, would prevent a
non-recursive bind mount like that, since it would let someone see
through mounts on the tree referred to by fd.)  And I should also not
be able to write to /mnt/restricted/foo.


For added fun (see above), I want an ability to "delegate" my own
permissions into an fd.

fd = open_delegate(..., read);

Now *anyone* with a copy of fd (or access via cwd, etc) can read files
in the subtree using my credentials, as captured when I called
open_delegate.  And this should survive bind mounting, so I could
finally do:

my_favorite_container_tool --delegate /home/username/foo,/mnt/mountpoint,read

and the things in the container, even if they use a dynamic subuid or
have some funky SELinux context, could actually read via
/mnt/mountpoint, and I could stop cursing at my computer.

- Open question: what happens to /proc/.../exe?  IMO it would be
*really* nice if we could get away with applying this sort of system
to /proc/.../exe.  There are ABI compatibility concerns here.  A whole
class of old nasty vulnerabilities would go away if writing through
/proc/.../exe were simply impossible.


Okay, there's my brain dump.  Feel free to make fun of it.  No LLMs
were roused from their slumber in the writing of this email :)

^ permalink raw reply

* Re: [RFC] Null Namespaces
From: Christian Brauner @ 2026-07-03  8:59 UTC (permalink / raw)
  To: John Ericson
  Cc: Andy Lutomirski, Li Chen, Cong Wang, linux-arch, LKML,
	linux-fsdevel, linux-api, Arnd Bergmann, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, Dave Hansen, H. Peter Anvin,
	Jan Kara, Jonathan Corbet, Shuah Khan, Al Viro, Kees Cook,
	Sergei Zimmerman, Farid Zakaria
In-Reply-To: <5abd7c81-3c6b-45d1-aaab-195493c1cdc9@app.fastmail.com>

On Thu, Jul 02, 2026 at 11:43:31AM -0400, John Ericson wrote:
> On Thu, Jul 2, 2026, at 4:34 AM, Christian Brauner wrote:
> > I think we could make that work with both FD_NULLFS_ROOT and
> > FD_FAILFS_ROOT...
> 
> Fantastic!!
> 
> What comes next? Do you want to submit the patches for this, Christian,
> or do you want one of us to? (Just trying to be helpful.)

I'll send a patch soon-ish.

^ permalink raw reply

* Re: [RFC] Null Namespaces
From: H. Peter Anvin @ 2026-07-02 21:28 UTC (permalink / raw)
  To: Jori Koolstra, Christian Brauner
  Cc: John Ericson, Al Viro, Li Chen, Cong Wang, linux-arch, LKML,
	linux-fsdevel, linux-api, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria
In-Reply-To: <akTgQH1YIg2blzRe@lt-jori.localdomain>

On July 1, 2026 2:49:42 AM PDT, Jori Koolstra <jkoolstra@xs4all.nl> wrote:
>On Mon, Jun 29, 2026 at 12:39:56PM +0200, Christian Brauner wrote:
>> > The kernel rightfully has consolidated path resolution in a few key
>> > places as much as possible -- the internal `struct path` does not suffer
>> > from these issues. I barely modify those places to support null root and
>> > CWD, and because of that consolidation, we shouldn't expect new places
>> > to crop up in the future. (Duplicative path resolution logic is a bad
>> > idea whether or not we have a nascent, little-used NULL-cwd/root code
>> > path.) Therefore, I think existing code review, even among people
>> > totally ignorant of this feature, will protect us --- the vast majority
>> > of code will just be working with `struct path`, and be totally
>> > unaffected by this change.
>> 
>> I actually did laugh out loud reading this. I'm sorry, I can't really
>> take this argument seriously. May I introduce you to drivers/ for a
>> start and the history of path lookup exploits of the last - say 10
>> years.
>> 
>> You have to excuse me but it's a mixture of amusement and slight anger.
>> Amusement because this is really naive and thus also a bit endearing.
>> Anger because it single-handedly dismisses how big of an attack surface
>> and problem space path lookup is. The equivalent of every math
>> professor's "trivial. excercise left to the reader".
>
>I could easily show you why path lookup is trivial, but I have no space
>left in the margins of this email.

🤣🤣🤣

^ permalink raw reply

* [PATCH] drm/fourcc: Add P212, P410 and P412 formats
From: Jonah Walker @ 2026-07-02 21:23 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter
  Cc: dri-devel, linux-kernel, linux-api, Jonah Walker

Add P212, P410 and P412 formats, including their FourCC codes and
drm_format_info entries.

  - P212 is a two-plane, 12-bit, 4:2:2 YCbCr format
  - P410 is a two-plane, 10-bit, 4:4:4 YCbCr format
  - P412 is a two-plane, 12-bit, 4:4:4 YCbCr format

Assisted-by: Cursor:GPT-5.5
Signed-off-by: Jonah Walker <jonahw@nvidia.com>
---
 drivers/gpu/drm/drm_fourcc.c  | 12 ++++++++++++
 include/uapi/drm/drm_fourcc.h | 21 +++++++++++++++++++++
 2 files changed, 33 insertions(+)

diff --git a/drivers/gpu/drm/drm_fourcc.c b/drivers/gpu/drm/drm_fourcc.c
index 60cd02b7e..31558a212 100644
--- a/drivers/gpu/drm/drm_fourcc.c
+++ b/drivers/gpu/drm/drm_fourcc.c
@@ -324,6 +324,18 @@ const struct drm_format_info *__drm_format_info(u32 format)
 		  .num_planes = 2, .char_per_block = { 2, 4, 0 },
 		  .block_w = { 1, 1, 0 }, .block_h = { 1, 1, 0 }, .hsub = 2,
 		  .vsub = 1, .is_yuv = true },
+		{ .format = DRM_FORMAT_P212,		.depth = 0,
+		  .num_planes = 2, .char_per_block = { 2, 4, 0 },
+		  .block_w = { 1, 1, 0 }, .block_h = { 1, 1, 0 }, .hsub = 2,
+		  .vsub = 1, .is_yuv = true },
+		{ .format = DRM_FORMAT_P410,		.depth = 0,
+		  .num_planes = 2, .char_per_block = { 2, 4, 0 },
+		  .block_w = { 1, 1, 0 }, .block_h = { 1, 1, 0 }, .hsub = 1,
+		  .vsub = 1, .is_yuv = true },
+		{ .format = DRM_FORMAT_P412,		.depth = 0,
+		  .num_planes = 2, .char_per_block = { 2, 4, 0 },
+		  .block_w = { 1, 1, 0 }, .block_h = { 1, 1, 0 }, .hsub = 1,
+		  .vsub = 1, .is_yuv = true },
 		{ .format = DRM_FORMAT_VUY101010,	.depth = 0,
 		  .num_planes = 1, .cpp = { 0, 0, 0 }, .hsub = 1, .vsub = 1,
 		  .is_yuv = true },
diff --git a/include/uapi/drm/drm_fourcc.h b/include/uapi/drm/drm_fourcc.h
index 3a4d4dc63..d4a6463d2 100644
--- a/include/uapi/drm/drm_fourcc.h
+++ b/include/uapi/drm/drm_fourcc.h
@@ -334,6 +334,27 @@ extern "C" {
  */
 #define DRM_FORMAT_P210		fourcc_code('P', '2', '1', '0') /* 2x1 subsampled Cr:Cb plane, 10 bit per channel */
 
+/*
+ * 2 plane YCbCr MSB aligned
+ * index 0 = Y plane, [15:0] Y:x [12:4] little endian
+ * index 1 = Cr:Cb plane, [31:0] Cr:x:Cb:x [12:4:12:4] little endian
+ */
+#define DRM_FORMAT_P212		fourcc_code('P', '2', '1', '2') /* 2x1 subsampled Cr:Cb plane 12 bits per channel */
+
+/*
+ * 2 plane YCbCr MSB aligned
+ * index 0 = Y plane, [15:0] Y:x [10:6] little endian
+ * index 1 = Cr:Cb plane, [31:0] Cr:x:Cb:x [10:6:10:6] little endian
+ */
+#define DRM_FORMAT_P410		fourcc_code('P', '4', '1', '0') /* non-subsampled Cr:Cb plane 10 bits per channel */
+
+/*
+ * 2 plane YCbCr MSB aligned
+ * index 0 = Y plane, [15:0] Y:x [12:4] little endian
+ * index 1 = Cr:Cb plane, [31:0] Cr:x:Cb:x [12:4:12:4] little endian
+ */
+#define DRM_FORMAT_P412		fourcc_code('P', '4', '1', '2') /* non-subsampled Cr:Cb plane 12 bits per channel */
+
 /*
  * 2 plane YCbCr MSB aligned
  * index 0 = Y plane, [15:0] Y:x [10:6] little endian

base-commit: cff96362794a5c1f3adb013b4a46c7233149a629
-- 
2.43.0


^ permalink raw reply related

* Re: [RFC] Null Namespaces
From: John Ericson @ 2026-07-02 15:43 UTC (permalink / raw)
  To: Christian Brauner, Andy Lutomirski
  Cc: Li Chen, Cong Wang, linux-arch, LKML, linux-fsdevel, linux-api,
	Arnd Bergmann, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
	Dave Hansen, H. Peter Anvin, Jan Kara, Jonathan Corbet,
	Shuah Khan, Al Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria
In-Reply-To: <20260702-entladen-farbkombinationen-klarheit-fe24cb608f23@brauner>

On Thu, Jul 2, 2026, at 4:34 AM, Christian Brauner wrote:
> I think we could make that work with both FD_NULLFS_ROOT and
> FD_FAILFS_ROOT...

Fantastic!!

What comes next? Do you want to submit the patches for this, Christian,
or do you want one of us to? (Just trying to be helpful.)

John

^ permalink raw reply

* Re: [RFC] Null Namespaces
From: Christian Brauner @ 2026-07-02  9:34 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: John Ericson, Li Chen, Cong Wang, linux-arch, linux-kernel,
	linux-fsdevel, linux-api, Arnd Bergmann, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, Dave Hansen, H. Peter Anvin,
	Jan Kara, Jonathan Corbet, Shuah Khan, Alexander Viro, Kees Cook,
	Sergei Zimmerman, Farid Zakaria
In-Reply-To: <CALCETrVuh0-biOw=TgYN9ERTFAoiki57XeZ3S2T3dO2+hL54gA@mail.gmail.com>

On Mon, Jun 29, 2026 at 02:06:55PM -0700, Andy Lutomirski wrote:
> On Mon, Jun 29, 2026 at 4:45 AM Christian Brauner <brauner@kernel.org> wrote:
> >
> 
> > But I guess the even simpler model would be to copy what I've been doing
> > for pidfs:
> >
> > +static struct path nullfs_root_path = {};
> > +
> > +void nullfs_get_root(struct path *path)
> > +{
> > +       *path = nullfs_root_path;
> > +       path_get(path);
> > +}
> > +
> >  static void __init init_mount_tree(void)
> >  {
> >         struct vfsmount *mnt, *nullfs_mnt;
> > @@ -6209,6 +6217,8 @@ static void __init init_mount_tree(void)
> >         /* Mount mutable rootfs on top of nullfs. */
> >         root.mnt                = nullfs_mnt;
> >         root.dentry             = nullfs_mnt->mnt_root;
> > +       nullfs_root_path.mnt    = nullfs_mnt;
> > +       pidfs_root_path.dentry  = nullfs_mnt->mnt_root;
> >
> >         LOCK_MOUNT_EXACT(mp, &root);
> >         if (unlikely(IS_ERR(mp.parent)))
> > diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
> > index aadfbf6e0cb3..f55c87c70b78 100644
> > --- a/include/uapi/linux/fcntl.h
> > +++ b/include/uapi/linux/fcntl.h
> > @@ -124,6 +124,7 @@ struct delegation {
> >
> >  #define FD_PIDFS_ROOT                  -10002 /* Root of the pidfs filesystem */
> >  #define FD_NSFS_ROOT                   -10003 /* Root of the nsfs filesystem */
> > +#define FD_NULLFS_ROOT                 -10004 /* Root of the nullfs filesystem */
> >  #define FD_INVALID                     -10009 /* Invalid file descriptor: -10000 - EBADF = -10009 */
> >
> >  /* Generic flags for the *at(2) family of syscalls. */
> >
> > we then add fchroot() (overdue anyway) and then teach both fchdir() and
> > fchroot() to honor FD_NULLFS_ROOT. Then a process may shed its fs state
> > and move itself into nullfs. Restrict *chdir() and *chroot() for said
> > process via seccomp and it's locked in forever as well.
> >
> 
> One thing comes to mind that might need a bit of care: this would give
> an API for any task to get an fd to a directory that lives in the init
> mount namespace.  It's not at all obvious to me that this is dangerous
> or even observable (you're not about to find a setuid program in
> nullfs), but I think it's at least worth a tiny bit of consideration.

Yes, I thought about this as well. But it doesn't have to be this way.
Every mount namespaces has nullfs as it's root ever since I introduced
it. Which means FD_NULLFS_ROOT can also just mean "nullfs within that
specific mount namespace". That's fine.

For my FD_FAILFS_ROOT proposal it would be enough if we make failfs
SB_KERNMOUNT which means it's logically distinct from every mount
namespace. I think that might be the right thing to do. I need to spend
one or more brain cycles on this though.

> 
> But if this happens, maybe we could finally land one of the patches to
> enable unprivileged chroot?  It's been tried a few times.
> 
> https://lore.kernel.org/lkml/0e2f0f54e19bff53a3739ecfddb4ffa9a6dbde4d.1327858005.git.luto@amacapital.net/
> 
> https://lore.kernel.org/all/20210316203633.424794-2-mic@digikod.net/
> 
> I think the need for it has reduced a tiny bit with user namespaces,
> as you can sort of emulate it by unsharing your user namespace and
> thus getting enough privilege, but this is rather heavyweight and
> limiting.

I think we could make that work with both FD_NULLFS_ROOT and
FD_FAILFS_ROOT...

> 
> 
> If all of the above landed, then the old chroot /var/empty kludge that
> security-minded programs have done for decades could finally be
> modernized and not require any privilege :)

I think I like it.

> Hmm, thinking aloud: every now and then someone brings up the idea of
> having an fd (really an OFD) that points to a file or a directory but
> carries less in the way of permissions/capabilities than the usual
> OFDs.  If we had a way to make an OFD to a directory that forced
> RESOLVE_BENEATH (or RESOLVE_IN_ROOT) and that propagated that
> restriction to anything you open using it, and if an unprivileged
> process could chroot itself to nullfs, then we would be getting quite
> close to what Capsicum can do.

Next steps. I hear you volunteering...

^ permalink raw reply

* Re: [RFC] Null Namespaces
From: Christian Brauner @ 2026-07-02  9:29 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: John Ericson, Li Chen, Cong Wang, linux-arch, LKML, linux-fsdevel,
	linux-api, Arnd Bergmann, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, H. Peter Anvin, Jan Kara,
	Jonathan Corbet, Shuah Khan, Al Viro, Kees Cook, Sergei Zimmerman,
	Farid Zakaria
In-Reply-To: <CALCETrV8T3KOWgjZL6wy2j7E+Y6zOjfN1aCDsVH4PadvE+yxSw@mail.gmail.com>

> > > I agree with the sentiment
> >
> > Thanks, I appreciate it :).
> >
> > > You know what the easy solution is: don't allow a struct path to be
> > > empty...
> >
> > Just so we're clear, my quibble here is purely behavioral: the nullfs
> > directory can be opened, right? And that open directory can also be
> > getdents64ed (yielding no entries, since it is empty), right? If I am
> > wrong about these things then sure, no objections from me --- let's ship
> > nullfs FDs right away!
> >
> 
> Christian, how would you feel about a variant of nullfs that fails all
> operations instead of acting as if it were empty?  (I'm far from
> convinced that this would actually be better, but it at least seems
> pretty straightforwardly possible.  And obviously the
> nullfs-at-the-root-of-everything would not want this variant.)

I think it would have to be a separate fs type: failfs. The problem is -
if taken to its logical extreme - it would have to refuse statfs and
fstatfs as well which means you can't discover it. You'd still be able
to get your cwd and root of course via /proc/self/{cwd,root}. So for
that we could just add:

FD_FAILFS_ROOT

I don't have quarrels with that. I can add that and it seems genuinely
useful. I'd again keep it as a kernel-only thing (not mountable) for now
until we have an idea whether the semantics work out...

(Btw, I've done something vaguely similar for kthreads in work to be
merged coming cycle. Their root and pwd are in a _separate_ nullfs that
can't even be overmounted. Which means you can't do arbitrary lookup or
I/O in init's fs state from kthreads anymore without having to use an
explicit scoped_with_init_fs() override with init's fs state
temporarily. Which means that hopefully things like ksmbd will not
happen again...)

^ permalink raw reply

* [PATCH v3 9/9] vdso/gettimeofday: Verify COMPAT_32BIT_TIME interactions
From: Thomas Weißschuh @ 2026-07-02  8:21 UTC (permalink / raw)
  To: Andy Lutomirski, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
	Dave Hansen, x86, H. Peter Anvin, Russell King, Catalin Marinas,
	Will Deacon, Madhavan Srinivasan, Michael Ellerman,
	Nicholas Piggin, Christophe Leroy (CS GROUP), Thomas Bogendoerfer,
	Vincenzo Frascino, John Stultz, Stephen Boyd, David S. Miller,
	Andreas Larsson
  Cc: Thomas Weißschuh, linux-kernel, linux-arm-kernel,
	linuxppc-dev, linux-mips, Arnd Bergmann, linux-api, sparclinux,
	Philippe Mathieu-Daudé
In-Reply-To: <20260702-vdso-compat_32bit_time-v3-0-db9f36d8d432@linutronix.de>

If CONFIG_COMPAT_32BIT_TIME is disabled then the vDSO should not
provide any 32-bit time related functionality.

Add some build-time validations to make sure the architecture-specific
glue satisfies this requirement.

Signed-off-by: Thomas Weißschuh <thomas.weissschuh@linutronix.de>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Reviewed-by: Arnd Bergmann <arnd@arndb.de>
---
 lib/vdso/gettimeofday.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/lib/vdso/gettimeofday.c b/lib/vdso/gettimeofday.c
index b8c1fc85eb74..f7a591aba59f 100644
--- a/lib/vdso/gettimeofday.c
+++ b/lib/vdso/gettimeofday.c
@@ -25,6 +25,8 @@
  */
 #include <asm/vdso/gettimeofday.h>
 
+#include <linux/build_bug.h>
+
 /* Bring in default accessors */
 #include <vdso/vsyscall.h>
 
@@ -325,6 +327,8 @@ __cvdso_clock_gettime32_data(const struct vdso_time_data *vd, clockid_t clock,
 	struct __kernel_timespec ts;
 	bool ok;
 
+	BUILD_BUG_ON(!IS_ENABLED(CONFIG_COMPAT_32BIT_TIME));
+
 	ok = __cvdso_clock_gettime_common(vd, clock, &ts);
 
 	if (unlikely(!ok))
@@ -354,6 +358,8 @@ __cvdso_gettimeofday_data(const struct vdso_time_data *vd,
 	BUILD_BUG();
 #endif
 
+	BUILD_BUG_ON(sizeof(tv->tv_sec) != 8 && !IS_ENABLED(CONFIG_COMPAT_32BIT_TIME));
+
 	if (likely(tv != NULL)) {
 		struct __kernel_timespec ts;
 
@@ -392,6 +398,8 @@ __cvdso_time_data(const struct vdso_time_data *vd, __kernel_old_time_t *time)
 	BUILD_BUG();
 #endif
 
+	BUILD_BUG_ON(sizeof(*time) != 8 && !IS_ENABLED(CONFIG_COMPAT_32BIT_TIME));
+
 	if (vdso_is_timens_clock(vc)) {
 		vd = vdso_timens_data(vd);
 		vc = vd->clock_data;
@@ -481,6 +489,8 @@ __cvdso_clock_getres_time32_data(const struct vdso_time_data *vd, clockid_t cloc
 	struct __kernel_timespec ts;
 	bool ok;
 
+	BUILD_BUG_ON(!IS_ENABLED(CONFIG_COMPAT_32BIT_TIME));
+
 	ok = __cvdso_clock_getres_common(vd, clock, &ts);
 
 	if (unlikely(!ok))

-- 
2.55.0


^ permalink raw reply related


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