fs/libfs.c | 1 + fs/pidfs.c | 57 +++++++++++++++++++++++++++++++++++++++ include/linux/pid.h | 1 + include/linux/pseudo_fs.h | 1 + kernel/pid.c | 10 +++++-- 5 files changed, 68 insertions(+), 2 deletions(-)
Since the introduction of pidfs, we have had 64-bit process identifiers that will not be reused for the entire uptime of the system. This greatly facilitates process tracking in userspace. There are two limitations at present: * These identifiers are currently only exposed to processes on 64-bit systems. On 32-bit systems, inode space is also limited to 32 bits and therefore is subject to the same reuse issues. * There is no way to go from one of these unique identifiers to a pid or pidfd. Patch 1 & 2 in this stack implements fh_export for pidfs. This means userspace can retrieve a unique process identifier even on 32-bit systems via name_to_handle_at. Patch 3 & 4 in this stack implement fh_to_dentry for pidfs. This means userspace can convert back from a file handle to the corresponding pidfd. To support us going from a file handle to a pidfd, we have to store a pid inside the file handle. To ensure file handles are invariant and can move between pid namespaces, we stash a pid from the initial namespace inside the file handle. I'm not quite sure if stashing an initial-namespace pid inside the file handle is the right approach here; if not, I think that patch 1 & 2 are useful on their own. Erin Shepherd (4): pseudofs: add support for export_ops pidfs: implement file handle export support pid: introduce find_get_pid_ns pidfs: implement fh_to_dentry fs/libfs.c | 1 + fs/pidfs.c | 57 +++++++++++++++++++++++++++++++++++++++ include/linux/pid.h | 1 + include/linux/pseudo_fs.h | 1 + kernel/pid.c | 10 +++++-- 5 files changed, 68 insertions(+), 2 deletions(-) -- 2.46.1
On Fri, Nov 01, 2024 at 01:54:48PM +0000, Erin Shepherd wrote: > Since the introduction of pidfs, we have had 64-bit process identifiers > that will not be reused for the entire uptime of the system. This greatly > facilitates process tracking in userspace. > > There are two limitations at present: > > * These identifiers are currently only exposed to processes on 64-bit > systems. On 32-bit systems, inode space is also limited to 32 bits and > therefore is subject to the same reuse issues. > * There is no way to go from one of these unique identifiers to a pid or > pidfd. > > Patch 1 & 2 in this stack implements fh_export for pidfs. This means > userspace can retrieve a unique process identifier even on 32-bit systems > via name_to_handle_at. > > Patch 3 & 4 in this stack implement fh_to_dentry for pidfs. This means > userspace can convert back from a file handle to the corresponding pidfd. > To support us going from a file handle to a pidfd, we have to store a pid > inside the file handle. To ensure file handles are invariant and can move > between pid namespaces, we stash a pid from the initial namespace inside > the file handle. > > I'm not quite sure if stashing an initial-namespace pid inside the file > handle is the right approach here; if not, I think that patch 1 & 2 are > useful on their own. Sorry for the delayed reply (I'm recovering from a lengthy illness.). I like the idea in general. I think this is really useful. A few of my thoughts but I need input from Amir and Jeff: * In the last patch of the series you already implement decoding of pidfd file handles by adding a .fh_to_dentry export_operations method. There are a few things to consider because of how open_by_handle_at() works. - open_by_handle_at() needs to be restricted so it only creates pidfds from pidfs file handles that resolve to a struct pid that is reachable in the caller's pid namespace. In other words, it should mirror pidfd_open(). Put another way, open_by_handle_at() must not be usable to open arbitrary pids to prevent a container from constructing a pidfd file handle for a process that lives outside it's pid namespace hierarchy. With this restriction in place open_by_handle_at() can be available to let unprivileged processes open pidfd file handles. Related to that, I don't think we need to make open_by_handle_at() open arbitrary pidfd file handles via CAP_DAC_READ_SEARCH. Simply because any process in the initial pid namespace can open any other process via pidfd_open() anyway because pid namespaces are hierarchical. IOW, CAP_DAC_READ_SEARCH must not override the restriction that the provided pidfs file handle must be reachable from the caller's pid namespace. - open_by_handle_at() uses may_decode_fh() to determine whether it's possible to decode a file handle as an unprivileged user. The current checks don't make sense for pidfs. Conceptually, I think there don't need to place any restrictions based on global CAP_DAC_READ_SEARCH, owning user namespace of the superblock or mount on pidfs file handles. The only restriction that matters is that the requested pidfs file handle is reachable from the caller's pid namespace. - A pidfd always has exactly a single inode and a single dentry. There's no aliases. - Generally, in my naive opinion, I think that decoding pidfs file handles should be a lot simpler than decoding regular path based file handles. Because there should be no need to verify any ancestors, or reconnect paths. Pidfs also doesn't have directory inodes, only regular inodes. In other words, any dentry is acceptable. Essentially, the only thing we need is for exportfs_decode_fh_raw() to verify that the provided pidfs file handle is resolvable in the caller's pid namespace. If so we're done. The challenge is how to nicely plumb this into the code without it sticking out like a sore thumb. - Pidfs should not be exportable via NFS. It doesn't make sense.
On 12/11/2024 14:10, Christian Brauner wrote: > Sorry for the delayed reply (I'm recovering from a lengthy illness.). No worries on my part, and I hope you're feeling better! > I like the idea in general. I think this is really useful. A few of my > thoughts but I need input from Amir and Jeff: > > * In the last patch of the series you already implement decoding of > pidfd file handles by adding a .fh_to_dentry export_operations method. > > There are a few things to consider because of how open_by_handle_at() > works. > > - open_by_handle_at() needs to be restricted so it only creates pidfds > from pidfs file handles that resolve to a struct pid that is > reachable in the caller's pid namespace. In other words, it should > mirror pidfd_open(). > > Put another way, open_by_handle_at() must not be usable to open > arbitrary pids to prevent a container from constructing a pidfd file > handle for a process that lives outside it's pid namespace > hierarchy. > > With this restriction in place open_by_handle_at() can be available > to let unprivileged processes open pidfd file handles. > > Related to that, I don't think we need to make open_by_handle_at() > open arbitrary pidfd file handles via CAP_DAC_READ_SEARCH. Simply > because any process in the initial pid namespace can open any other > process via pidfd_open() anyway because pid namespaces are > hierarchical. > > IOW, CAP_DAC_READ_SEARCH must not override the restriction that the > provided pidfs file handle must be reachable from the caller's pid > namespace. The pid_vnr(pid) == 0 check catches this case -- we return an error to the caller if there isn't a pid mapping in the caller's namespace Perhaps I should have called this out explicitly. > - open_by_handle_at() uses may_decode_fh() to determine whether it's > possible to decode a file handle as an unprivileged user. The > current checks don't make sense for pidfs. Conceptually, I think > there don't need to place any restrictions based on global > CAP_DAC_READ_SEARCH, owning user namespace of the superblock or > mount on pidfs file handles. > > The only restriction that matters is that the requested pidfs file > handle is reachable from the caller's pid namespace. I wonder if this could be handled through an addition to export_operations' flags member? > - A pidfd always has exactly a single inode and a single dentry. > There's no aliases. > > - Generally, in my naive opinion, I think that decoding pidfs file > handles should be a lot simpler than decoding regular path based > file handles. Because there should be no need to verify any > ancestors, or reconnect paths. Pidfs also doesn't have directory > inodes, only regular inodes. In other words, any dentry is > acceptable. > > Essentially, the only thing we need is for exportfs_decode_fh_raw() > to verify that the provided pidfs file handle is resolvable in the > caller's pid namespace. If so we're done. The challenge is how to > nicely plumb this into the code without it sticking out like a sore > thumb. Theoretically you should be able to use PIDFD_SELF as well (assuming that makes its way into mainline this release :-)) but I am a bit concerned about potentially polluting the open_by_handle_at logic with pidfd specificities. > - Pidfs should not be exportable via NFS. It doesn't make sense. Hmm, I guess I might have made that possible, though I'm certainly not familiar enough with the internals of nfsd to be able to test if I've done so. I guess probably this case calls for another export_ops flag? Not like we're short on them Thanks, - Erin
On Wed, Nov 13, 2024 at 12:03:08AM +0100, Erin Shepherd wrote: > > On 12/11/2024 14:10, Christian Brauner wrote: > > Sorry for the delayed reply (I'm recovering from a lengthy illness.). > No worries on my part, and I hope you're feeling better! > > I like the idea in general. I think this is really useful. A few of my > > thoughts but I need input from Amir and Jeff: > > > > * In the last patch of the series you already implement decoding of > > pidfd file handles by adding a .fh_to_dentry export_operations method. > > > > There are a few things to consider because of how open_by_handle_at() > > works. > > > > - open_by_handle_at() needs to be restricted so it only creates pidfds > > from pidfs file handles that resolve to a struct pid that is > > reachable in the caller's pid namespace. In other words, it should > > mirror pidfd_open(). > > > > Put another way, open_by_handle_at() must not be usable to open > > arbitrary pids to prevent a container from constructing a pidfd file > > handle for a process that lives outside it's pid namespace > > hierarchy. > > > > With this restriction in place open_by_handle_at() can be available > > to let unprivileged processes open pidfd file handles. > > > > Related to that, I don't think we need to make open_by_handle_at() > > open arbitrary pidfd file handles via CAP_DAC_READ_SEARCH. Simply > > because any process in the initial pid namespace can open any other > > process via pidfd_open() anyway because pid namespaces are > > hierarchical. > > > > IOW, CAP_DAC_READ_SEARCH must not override the restriction that the > > provided pidfs file handle must be reachable from the caller's pid > > namespace. > > The pid_vnr(pid) == 0 check catches this case -- we return an error to the > caller if there isn't a pid mapping in the caller's namespace > > Perhaps I should have called this out explicitly. > > > - open_by_handle_at() uses may_decode_fh() to determine whether it's > > possible to decode a file handle as an unprivileged user. The > > current checks don't make sense for pidfs. Conceptually, I think > > there don't need to place any restrictions based on global > > CAP_DAC_READ_SEARCH, owning user namespace of the superblock or > > mount on pidfs file handles. > > > > The only restriction that matters is that the requested pidfs file > > handle is reachable from the caller's pid namespace. > > I wonder if this could be handled through an addition to export_operations' > flags member? > > > - A pidfd always has exactly a single inode and a single dentry. > > There's no aliases. > > > > - Generally, in my naive opinion, I think that decoding pidfs file > > handles should be a lot simpler than decoding regular path based > > file handles. Because there should be no need to verify any > > ancestors, or reconnect paths. Pidfs also doesn't have directory > > inodes, only regular inodes. In other words, any dentry is > > acceptable. > > > > Essentially, the only thing we need is for exportfs_decode_fh_raw() > > to verify that the provided pidfs file handle is resolvable in the > > caller's pid namespace. If so we're done. The challenge is how to > > nicely plumb this into the code without it sticking out like a sore > > thumb. > > Theoretically you should be able to use PIDFD_SELF as well (assuming that > makes its way into mainline this release :-)) but I am a bit concerned about > potentially polluting the open_by_handle_at logic with pidfd specificities. > > > - Pidfs should not be exportable via NFS. It doesn't make sense. > > Hmm, I guess I might have made that possible, though I'm certainly not > familiar enough with the internals of nfsd to be able to test if I've done > so. AFAIK check_export() in fs/nfsd/export.c spells this it out: /* There are two requirements on a filesystem to be exportable. * 1: We must be able to identify the filesystem from a number. * either a device number (so FS_REQUIRES_DEV needed) * or an FSID number (so NFSEXP_FSID or ->uuid is needed). * 2: We must be able to find an inode from a filehandle. * This means that s_export_op must be set. * 3: We must not currently be on an idmapped mount. */ Granted I've been wrong on account of stale docs before. :$ Though it would be kinda funny if you *could* mess with another machine's processes over NFS. --D > I guess probably this case calls for another export_ops flag? Not like we're > short on them > > Thanks, > - Erin > >
On 13/11/2024 01:40, Darrick J. Wong wrote: >> Hmm, I guess I might have made that possible, though I'm certainly not >> familiar enough with the internals of nfsd to be able to test if I've done >> so. > AFAIK check_export() in fs/nfsd/export.c spells this it out: > > /* There are two requirements on a filesystem to be exportable. > * 1: We must be able to identify the filesystem from a number. > * either a device number (so FS_REQUIRES_DEV needed) > * or an FSID number (so NFSEXP_FSID or ->uuid is needed). > * 2: We must be able to find an inode from a filehandle. > * This means that s_export_op must be set. > * 3: We must not currently be on an idmapped mount. > */ > > Granted I've been wrong on account of stale docs before. :$ > > Though it would be kinda funny if you *could* mess with another > machine's processes over NFS. > > --D To be clear I'm not familiar enough with the workings of nfsd to tell if pidfs fails those requirements and therefore wouldn't become exportable as a result of this patch, though I gather from you're message that we're in the clear? Regardless I think my question is: do we think either those requirements could change in the future, or the properties of pidfs could change in the future, in ways that could accidentally make the filesystem exportable? I guess though that the same concern would apply to cgroupfs and it hasn't posed an issue so far. - Erin
On Wed, 2024-11-13 at 11:17 +0100, Erin Shepherd wrote: > On 13/11/2024 01:40, Darrick J. Wong wrote: > > > Hmm, I guess I might have made that possible, though I'm certainly not > > > familiar enough with the internals of nfsd to be able to test if I've done > > > so. > > AFAIK check_export() in fs/nfsd/export.c spells this it out: > > > > /* There are two requirements on a filesystem to be exportable. > > * 1: We must be able to identify the filesystem from a number. > > * either a device number (so FS_REQUIRES_DEV needed) > > * or an FSID number (so NFSEXP_FSID or ->uuid is needed). > > * 2: We must be able to find an inode from a filehandle. > > * This means that s_export_op must be set. > > * 3: We must not currently be on an idmapped mount. > > */ > > > > Granted I've been wrong on account of stale docs before. :$ > > > > Though it would be kinda funny if you *could* mess with another > > machine's processes over NFS. > > > > --D > > To be clear I'm not familiar enough with the workings of nfsd to tell if > pidfs fails those requirements and therefore wouldn't become exportable as > a result of this patch, though I gather from you're message that we're in the > clear? > > Regardless I think my question is: do we think either those requirements could > change in the future, or the properties of pidfs could change in the future, > in ways that could accidentally make the filesystem exportable? > > I guess though that the same concern would apply to cgroupfs and it hasn't posed > an issue so far. We have other filesystems that do this sort of thing (like cgroupfs), and we don't allow them to be exportable. We'll need to make sure that that's the case before we merge this, of course, as I forget the details of how that works. -- Jeff Layton <jlayton@kernel.org>
On Wed, Nov 13, 2024 at 2:29 PM Jeff Layton <jlayton@kernel.org> wrote: > > On Wed, 2024-11-13 at 11:17 +0100, Erin Shepherd wrote: > > On 13/11/2024 01:40, Darrick J. Wong wrote: > > > > Hmm, I guess I might have made that possible, though I'm certainly not > > > > familiar enough with the internals of nfsd to be able to test if I've done > > > > so. > > > AFAIK check_export() in fs/nfsd/export.c spells this it out: > > > > > > /* There are two requirements on a filesystem to be exportable. > > > * 1: We must be able to identify the filesystem from a number. > > > * either a device number (so FS_REQUIRES_DEV needed) > > > * or an FSID number (so NFSEXP_FSID or ->uuid is needed). > > > * 2: We must be able to find an inode from a filehandle. > > > * This means that s_export_op must be set. > > > * 3: We must not currently be on an idmapped mount. > > > */ > > > > > > Granted I've been wrong on account of stale docs before. :$ > > > > > > Though it would be kinda funny if you *could* mess with another > > > machine's processes over NFS. > > > > > > --D > > > > To be clear I'm not familiar enough with the workings of nfsd to tell if > > pidfs fails those requirements and therefore wouldn't become exportable as > > a result of this patch, though I gather from you're message that we're in the > > clear? > > > > Regardless I think my question is: do we think either those requirements could > > change in the future, or the properties of pidfs could change in the future, > > in ways that could accidentally make the filesystem exportable? > > > > I guess though that the same concern would apply to cgroupfs and it hasn't posed > > an issue so far. > > We have other filesystems that do this sort of thing (like cgroupfs), > and we don't allow them to be exportable. We'll need to make sure that > that's the case before we merge this, of course, as I forget the > details of how that works. TBH, I cannot find how export of cgroups with NFSEXP_FSID is prevented. We should probably block nfs export of SB_NOUSER and anyway, this should be tied to the flag for relaxing CAP_DAC_READ_SEARCH, because this is a strong indication that it's not a traditional nfs file handle. Thanks, Amir.
> On Nov 13, 2024, at 8:29 AM, Jeff Layton <jlayton@kernel.org> wrote: > > On Wed, 2024-11-13 at 11:17 +0100, Erin Shepherd wrote: >> On 13/11/2024 01:40, Darrick J. Wong wrote: >>>> Hmm, I guess I might have made that possible, though I'm certainly not >>>> familiar enough with the internals of nfsd to be able to test if I've done >>>> so. >>> AFAIK check_export() in fs/nfsd/export.c spells this it out: >>> >>> /* There are two requirements on a filesystem to be exportable. >>> * 1: We must be able to identify the filesystem from a number. >>> * either a device number (so FS_REQUIRES_DEV needed) >>> * or an FSID number (so NFSEXP_FSID or ->uuid is needed). >>> * 2: We must be able to find an inode from a filehandle. >>> * This means that s_export_op must be set. >>> * 3: We must not currently be on an idmapped mount. >>> */ >>> >>> Granted I've been wrong on account of stale docs before. :$ >>> >>> Though it would be kinda funny if you *could* mess with another >>> machine's processes over NFS. >>> >>> --D >> >> To be clear I'm not familiar enough with the workings of nfsd to tell if >> pidfs fails those requirements and therefore wouldn't become exportable as >> a result of this patch, though I gather from you're message that we're in the >> clear? >> >> Regardless I think my question is: do we think either those requirements could >> change in the future, or the properties of pidfs could change in the future, >> in ways that could accidentally make the filesystem exportable? >> >> I guess though that the same concern would apply to cgroupfs and it hasn't posed >> an issue so far. > > We have other filesystems that do this sort of thing (like cgroupfs), > and we don't allow them to be exportable. We'll need to make sure that > that's the case before we merge this, of course, as I forget the > details of how that works. It's far easier to add exportability later than it is to remove it if we think it was a mistake. I would err on the side of caution if there isn't an immediate need/use-case for exposure via NFS. -- Chuck Lever
On Wed, Nov 13, 2024 at 02:41:29PM +0000, Chuck Lever III wrote: > > > > On Nov 13, 2024, at 8:29 AM, Jeff Layton <jlayton@kernel.org> wrote: > > > > On Wed, 2024-11-13 at 11:17 +0100, Erin Shepherd wrote: > >> On 13/11/2024 01:40, Darrick J. Wong wrote: > >>>> Hmm, I guess I might have made that possible, though I'm certainly not > >>>> familiar enough with the internals of nfsd to be able to test if I've done > >>>> so. > >>> AFAIK check_export() in fs/nfsd/export.c spells this it out: > >>> > >>> /* There are two requirements on a filesystem to be exportable. > >>> * 1: We must be able to identify the filesystem from a number. > >>> * either a device number (so FS_REQUIRES_DEV needed) > >>> * or an FSID number (so NFSEXP_FSID or ->uuid is needed). > >>> * 2: We must be able to find an inode from a filehandle. > >>> * This means that s_export_op must be set. > >>> * 3: We must not currently be on an idmapped mount. > >>> */ > >>> > >>> Granted I've been wrong on account of stale docs before. :$ > >>> > >>> Though it would be kinda funny if you *could* mess with another > >>> machine's processes over NFS. > >>> > >>> --D > >> > >> To be clear I'm not familiar enough with the workings of nfsd to tell if > >> pidfs fails those requirements and therefore wouldn't become exportable as > >> a result of this patch, though I gather from you're message that we're in the > >> clear? > >> > >> Regardless I think my question is: do we think either those requirements could > >> change in the future, or the properties of pidfs could change in the future, > >> in ways that could accidentally make the filesystem exportable? > >> > >> I guess though that the same concern would apply to cgroupfs and it hasn't posed > >> an issue so far. > > > > We have other filesystems that do this sort of thing (like cgroupfs), > > and we don't allow them to be exportable. We'll need to make sure that > > that's the case before we merge this, of course, as I forget the > > details of how that works. > > It's far easier to add exportability later than it is > to remove it if we think it was a mistake. I would err > on the side of caution if there isn't an immediate > need/use-case for exposure via NFS. Tbh, the idea itself of exporting pidfs via nfs is a) crazy and b) pretty interesting. If we could really export pidfs over NFS cleanly somehow then we would have a filesystem-like representation of a remote machine's processes. There could be a lot of interesting things in this. But I would think that this requires some proper massaging of how pidfs works. But in principle it might be possible. Again, I'm not saying it's a great idea and we definitely shouldn't do it now. But it's an interesting thought experiment at least.
On Tue, 2024-11-12 at 14:10 +0100, Christian Brauner wrote: > On Fri, Nov 01, 2024 at 01:54:48PM +0000, Erin Shepherd wrote: > > Since the introduction of pidfs, we have had 64-bit process identifiers > > that will not be reused for the entire uptime of the system. This greatly > > facilitates process tracking in userspace. > > > > There are two limitations at present: > > > > * These identifiers are currently only exposed to processes on 64-bit > > systems. On 32-bit systems, inode space is also limited to 32 bits and > > therefore is subject to the same reuse issues. We should really just move to storing 64-bit inode numbers internally on 32-bit machines. That would at least make statx() give you all 64 bits on 32-bit host. > > * There is no way to go from one of these unique identifiers to a pid or > > pidfd. > > > > Patch 1 & 2 in this stack implements fh_export for pidfs. This means > > userspace can retrieve a unique process identifier even on 32-bit systems > > via name_to_handle_at. > > > > Patch 3 & 4 in this stack implement fh_to_dentry for pidfs. This means > > userspace can convert back from a file handle to the corresponding pidfd. > > To support us going from a file handle to a pidfd, we have to store a pid > > inside the file handle. To ensure file handles are invariant and can move > > between pid namespaces, we stash a pid from the initial namespace inside > > the file handle. > > > > I'm not quite sure if stashing an initial-namespace pid inside the file > > handle is the right approach here; if not, I think that patch 1 & 2 are > > useful on their own. Hmm... I guess pid namespaces don't have a convenient 64-bit ID like mount namespaces do? In that case, stashing the pid from init_ns is probably the next best thing. > > Sorry for the delayed reply (I'm recovering from a lengthy illness.). > > I like the idea in general. I think this is really useful. A few of my > thoughts but I need input from Amir and Jeff: > > * In the last patch of the series you already implement decoding of > pidfd file handles by adding a .fh_to_dentry export_operations method. > > There are a few things to consider because of how open_by_handle_at() > works. > > - open_by_handle_at() needs to be restricted so it only creates pidfds > from pidfs file handles that resolve to a struct pid that is > reachable in the caller's pid namespace. In other words, it should > mirror pidfd_open(). > > Put another way, open_by_handle_at() must not be usable to open > arbitrary pids to prevent a container from constructing a pidfd file > handle for a process that lives outside it's pid namespace > hierarchy. > > With this restriction in place open_by_handle_at() can be available > to let unprivileged processes open pidfd file handles. > > Related to that, I don't think we need to make open_by_handle_at() > open arbitrary pidfd file handles via CAP_DAC_READ_SEARCH. Simply > because any process in the initial pid namespace can open any other > process via pidfd_open() anyway because pid namespaces are > hierarchical. > > IOW, CAP_DAC_READ_SEARCH must not override the restriction that the > provided pidfs file handle must be reachable from the caller's pid > namespace. > > - open_by_handle_at() uses may_decode_fh() to determine whether it's > possible to decode a file handle as an unprivileged user. The > current checks don't make sense for pidfs. Conceptually, I think > there don't need to place any restrictions based on global > CAP_DAC_READ_SEARCH, owning user namespace of the superblock or > mount on pidfs file handles. > > The only restriction that matters is that the requested pidfs file > handle is reachable from the caller's pid namespace. > > - A pidfd always has exactly a single inode and a single dentry. > There's no aliases. > > - Generally, in my naive opinion, I think that decoding pidfs file > handles should be a lot simpler than decoding regular path based > file handles. Because there should be no need to verify any > ancestors, or reconnect paths. Pidfs also doesn't have directory > inodes, only regular inodes. In other words, any dentry is > acceptable. > > Essentially, the only thing we need is for exportfs_decode_fh_raw() > to verify that the provided pidfs file handle is resolvable in the > caller's pid namespace. If so we're done. The challenge is how to > nicely plumb this into the code without it sticking out like a sore > thumb. > > - Pidfs should not be exportable via NFS. It doesn't make sense. I haven't looked over the patchset yet, but those restrictions all sound pretty reasonable to me. Special casing the may_decode_fh permission checks may be the tricky bit. I'm not sure what that should look like, tbqh. -- Jeff Layton <jlayton@kernel.org>
On 12/11/2024 14:57, Jeff Layton wrote: > On Tue, 2024-11-12 at 14:10 +0100, Christian Brauner wrote: > We should really just move to storing 64-bit inode numbers internally > on 32-bit machines. That would at least make statx() give you all 64 > bits on 32-bit host. I think that would be ideal from the perspective of exposing it to userspace. It does leave the question of going back from inode to pidfd unsolved though.I like the name_to_handle_at/open_by_handle_at approach because it neatly solves both sides of the problem with APIs we already have and understand > Hmm... I guess pid namespaces don't have a convenient 64-bit ID like > mount namespaces do? In that case, stashing the pid from init_ns is > probably the next best thing. Not that I could identify, no; so stashing the PID seemed like the most pragmatic approach. I'm not 100% sure it should be a documented property of the file handle format; I somewhat think that everything after the PID inode should be opaque to userspace and subject to change in the future (to the point I considered xoring it with a magic constant to make it less obvious to userspace/make it more obvious that its not to be relied upon; but that to my knowledge is not something that the kernel has done elsewhere). - Erin
Since the introduction of pidfs, we have had 64-bit process identifiers that will not be reused for the entire uptime of the system. This greatly facilitates process tracking in userspace. There are two limitations at present: * These identifiers are currently only exposed to processes on 64-bit systems. On 32-bit systems, inode space is also limited to 32 bits and therefore is subject to the same reuse issues. * There is no way to go from one of these unique identifiers to a pid or pidfd. This patch implements fh_export and fh_to_dentry which enables userspace to convert PIDs to and from PID file handles. A process can convert a pidfd into a file handle using name_to_handle_at, store it (in memory, on disk, or elsewhere) and then convert it back into a pidfd suing open_by_handle_at. To support us going from a file handle to a pidfd, we have to store a pid inside the file handle. To ensure file handles are invariant and can move between pid namespaces, we stash a pid from the initial namespace inside the file handle. (There has been some discussion as to whether or not it is OK to include the PID in the initial pid namespace, but so far there hasn't been any conclusive reason given as to why this would be a bad idea) Signed-off-by: Erin Shepherd <erin.shepherd@e43.eu> --- Changes in v2: - Permit filesystems to opt out of CAP_DAC_READ_SEARCH - Inline find_pid_ns/get_pid logic; remove unnecessary put_pid - Squash fh_export & fh_to_dentry into one commit - Link to v1: https://lore.kernel.org/r/2aa94713-c12a-4344-a45c-a01f26e16a0d@e43.eu Remaining: To address the PIDFD_THREAD question - Do we want to stash it in file handles (IMO no but there may be merits to doing so) - If not do we want PIDFD_THREAD/O_EXCL to open_by_handle_at to work or do we do something else? (Perhaps we could just add an ioctl which lets an app change the PIDFD_THREAD flag?) If PIDFD_SELF lands <https://lore.kernel.org/r/cover.1727644404.git.lorenzo.stoakes@oracle.com>: - Support for PIDFD_SELF(_THREAD) as reference fd in open_by_handle_at? (We can even detect that special case early there and bypass most of the file handle logic) --- Erin Shepherd (3): pseudofs: add support for export_ops exportfs: allow fs to disable CAP_DAC_READ_SEARCH check pidfs: implement file handle support fs/fhandle.c | 36 +++++++++++++++------------ fs/libfs.c | 1 + fs/pidfs.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++- include/linux/exportfs.h | 3 +++ include/linux/pseudo_fs.h | 1 + 5 files changed, 87 insertions(+), 16 deletions(-) --- base-commit: 14b6320953a3f856a3f93bf9a0e423395baa593d change-id: 20241112-pidfs_fh-fbb04b108a2b Best regards, -- Erin Shepherd <erin.shepherd@e43.eu>
On Wed, Nov 13, 2024 at 7:01 PM Erin Shepherd <erin.shepherd@e43.eu> wrote: > > Since the introduction of pidfs, we have had 64-bit process identifiers > that will not be reused for the entire uptime of the system. This greatly > facilitates process tracking in userspace. > > There are two limitations at present: > > * These identifiers are currently only exposed to processes on 64-bit > systems. On 32-bit systems, inode space is also limited to 32 bits and > therefore is subject to the same reuse issues. > * There is no way to go from one of these unique identifiers to a pid or > pidfd. > > This patch implements fh_export and fh_to_dentry which enables userspace to > convert PIDs to and from PID file handles. A process can convert a pidfd into > a file handle using name_to_handle_at, store it (in memory, on disk, or > elsewhere) and then convert it back into a pidfd suing open_by_handle_at. > > To support us going from a file handle to a pidfd, we have to store a pid > inside the file handle. To ensure file handles are invariant and can move > between pid namespaces, we stash a pid from the initial namespace inside > the file handle. > > (There has been some discussion as to whether or not it is OK to include > the PID in the initial pid namespace, but so far there hasn't been any > conclusive reason given as to why this would be a bad idea) IIUC, this is already exposed as st_ino on a 64bit arch? If that is the case, then there is certainly no new info leak in this patch. > > Signed-off-by: Erin Shepherd <erin.shepherd@e43.eu> > --- > Changes in v2: > - Permit filesystems to opt out of CAP_DAC_READ_SEARCH > - Inline find_pid_ns/get_pid logic; remove unnecessary put_pid > - Squash fh_export & fh_to_dentry into one commit Not sure why you did that. It was pretty nice as separate commits if you ask me. Whatever. Thanks, Amir.
On 14/11/2024 08:02, Amir Goldstein wrote: > On Wed, Nov 13, 2024 at 7:01 PM Erin Shepherd <erin.shepherd@e43.eu> wrote: >> Since the introduction of pidfs, we have had 64-bit process identifiers >> that will not be reused for the entire uptime of the system. This greatly >> facilitates process tracking in userspace. >> >> There are two limitations at present: >> >> * These identifiers are currently only exposed to processes on 64-bit >> systems. On 32-bit systems, inode space is also limited to 32 bits and >> therefore is subject to the same reuse issues. >> * There is no way to go from one of these unique identifiers to a pid or >> pidfd. >> >> This patch implements fh_export and fh_to_dentry which enables userspace to >> convert PIDs to and from PID file handles. A process can convert a pidfd into >> a file handle using name_to_handle_at, store it (in memory, on disk, or >> elsewhere) and then convert it back into a pidfd suing open_by_handle_at. >> >> To support us going from a file handle to a pidfd, we have to store a pid >> inside the file handle. To ensure file handles are invariant and can move >> between pid namespaces, we stash a pid from the initial namespace inside >> the file handle. >> >> (There has been some discussion as to whether or not it is OK to include >> the PID in the initial pid namespace, but so far there hasn't been any >> conclusive reason given as to why this would be a bad idea) > IIUC, this is already exposed as st_ino on a 64bit arch? > If that is the case, then there is certainly no new info leak in this patch. pid.ino is exposed, but the init-ns pid isn't exposed anywhere to my knowledge. >> Signed-off-by: Erin Shepherd <erin.shepherd@e43.eu> >> --- >> Changes in v2: >> - Permit filesystems to opt out of CAP_DAC_READ_SEARCH >> - Inline find_pid_ns/get_pid logic; remove unnecessary put_pid >> - Squash fh_export & fh_to_dentry into one commit > Not sure why you did that. > It was pretty nice as separate commits if you ask me. Whatever. I can revert that if you prefer. I squashed them because there was some churn when adding the init-ns-pid necessary to restore them, but I am happy to do things in two steps. Do you prefer having the final handle format in the first step, or letting it evolve into final form over the series?
On Thu, Nov 14, 2024 at 4:51 PM Erin Shepherd <erin.shepherd@e43.eu> wrote: > > > > On 14/11/2024 08:02, Amir Goldstein wrote: > > On Wed, Nov 13, 2024 at 7:01 PM Erin Shepherd <erin.shepherd@e43.eu> wrote: > >> Since the introduction of pidfs, we have had 64-bit process identifiers > >> that will not be reused for the entire uptime of the system. This greatly > >> facilitates process tracking in userspace. > >> > >> There are two limitations at present: > >> > >> * These identifiers are currently only exposed to processes on 64-bit > >> systems. On 32-bit systems, inode space is also limited to 32 bits and > >> therefore is subject to the same reuse issues. > >> * There is no way to go from one of these unique identifiers to a pid or > >> pidfd. > >> > >> This patch implements fh_export and fh_to_dentry which enables userspace to > >> convert PIDs to and from PID file handles. A process can convert a pidfd into > >> a file handle using name_to_handle_at, store it (in memory, on disk, or > >> elsewhere) and then convert it back into a pidfd suing open_by_handle_at. > >> > >> To support us going from a file handle to a pidfd, we have to store a pid > >> inside the file handle. To ensure file handles are invariant and can move > >> between pid namespaces, we stash a pid from the initial namespace inside > >> the file handle. > >> > >> (There has been some discussion as to whether or not it is OK to include > >> the PID in the initial pid namespace, but so far there hasn't been any > >> conclusive reason given as to why this would be a bad idea) > > IIUC, this is already exposed as st_ino on a 64bit arch? > > If that is the case, then there is certainly no new info leak in this patch. > > pid.ino is exposed, but the init-ns pid isn't exposed anywhere to my knowledge. > > >> Signed-off-by: Erin Shepherd <erin.shepherd@e43.eu> > >> --- > >> Changes in v2: > >> - Permit filesystems to opt out of CAP_DAC_READ_SEARCH > >> - Inline find_pid_ns/get_pid logic; remove unnecessary put_pid > >> - Squash fh_export & fh_to_dentry into one commit > > Not sure why you did that. > > It was pretty nice as separate commits if you ask me. Whatever. > > I can revert that if you prefer. I squashed them because there was some churn > when adding the init-ns-pid necessary to restore them, but I am happy to do > things in two steps. > > Do you prefer having the final handle format in the first step, or letting it > evolve into final form over the series? > I don't really mind. Was just wondering. Either way is fine. Thanks, Amir.
On Thu, Nov 14, 2024 at 01:48:02PM +0100, Erin Shepherd wrote: > > > On 14/11/2024 08:02, Amir Goldstein wrote: > > On Wed, Nov 13, 2024 at 7:01 PM Erin Shepherd <erin.shepherd@e43.eu> wrote: > >> Since the introduction of pidfs, we have had 64-bit process identifiers > >> that will not be reused for the entire uptime of the system. This greatly > >> facilitates process tracking in userspace. > >> > >> There are two limitations at present: > >> > >> * These identifiers are currently only exposed to processes on 64-bit > >> systems. On 32-bit systems, inode space is also limited to 32 bits and > >> therefore is subject to the same reuse issues. > >> * There is no way to go from one of these unique identifiers to a pid or > >> pidfd. > >> > >> This patch implements fh_export and fh_to_dentry which enables userspace to > >> convert PIDs to and from PID file handles. A process can convert a pidfd into > >> a file handle using name_to_handle_at, store it (in memory, on disk, or > >> elsewhere) and then convert it back into a pidfd suing open_by_handle_at. > >> > >> To support us going from a file handle to a pidfd, we have to store a pid > >> inside the file handle. To ensure file handles are invariant and can move > >> between pid namespaces, we stash a pid from the initial namespace inside > >> the file handle. > >> > >> (There has been some discussion as to whether or not it is OK to include > >> the PID in the initial pid namespace, but so far there hasn't been any > >> conclusive reason given as to why this would be a bad idea) > > IIUC, this is already exposed as st_ino on a 64bit arch? > > If that is the case, then there is certainly no new info leak in this patch. > > pid.ino is exposed, but the init-ns pid isn't exposed anywhere to my knowledge. I see what you mean. That might be an information leak. Not a very interesting one, I think but I need to think about it. > > >> Signed-off-by: Erin Shepherd <erin.shepherd@e43.eu> > >> --- > >> Changes in v2: > >> - Permit filesystems to opt out of CAP_DAC_READ_SEARCH > >> - Inline find_pid_ns/get_pid logic; remove unnecessary put_pid > >> - Squash fh_export & fh_to_dentry into one commit > > Not sure why you did that. > > It was pretty nice as separate commits if you ask me. Whatever. > > I can revert that if you prefer. I squashed them because there was some churn > when adding the init-ns-pid necessary to restore them, but I am happy to do > things in two steps. > > Do you prefer having the final handle format in the first step, or letting it > evolve into final form over the series? >
Pseudo-filesystems might reasonably wish to implement the export ops
(particularly for name_to_handle_at/open_by_handle_at); plumb this
through pseudo_fs_context
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Erin Shepherd <erin.shepherd@e43.eu>
---
fs/libfs.c | 1 +
include/linux/pseudo_fs.h | 1 +
2 files changed, 2 insertions(+)
diff --git a/fs/libfs.c b/fs/libfs.c
index 46966fd8bcf9f042e85d0b66134e59fbef83abfd..698a2ddfd0cb94a8927d1d8a3bb3b3226d6d5476 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -669,6 +669,7 @@ static int pseudo_fs_fill_super(struct super_block *s, struct fs_context *fc)
s->s_blocksize_bits = PAGE_SHIFT;
s->s_magic = ctx->magic;
s->s_op = ctx->ops ?: &simple_super_operations;
+ s->s_export_op = ctx->eops;
s->s_xattr = ctx->xattr;
s->s_time_gran = 1;
root = new_inode(s);
diff --git a/include/linux/pseudo_fs.h b/include/linux/pseudo_fs.h
index 730f77381d55f1816ef14adf7dd2cf1d62bb912c..2503f7625d65e7b1fbe9e64d5abf06cd8f017b5f 100644
--- a/include/linux/pseudo_fs.h
+++ b/include/linux/pseudo_fs.h
@@ -5,6 +5,7 @@
struct pseudo_fs_context {
const struct super_operations *ops;
+ const struct export_operations *eops;
const struct xattr_handler * const *xattr;
const struct dentry_operations *dops;
unsigned long magic;
--
2.46.1
For pidfs, there is no reason to restrict file handle decoding by
CAP_DAC_READ_SEARCH. Introduce an export_ops flag that can indicate
this
Signed-off-by: Erin Shepherd <erin.shepherd@e43.eu>
---
fs/fhandle.c | 36 +++++++++++++++++++++---------------
include/linux/exportfs.h | 3 +++
2 files changed, 24 insertions(+), 15 deletions(-)
diff --git a/fs/fhandle.c b/fs/fhandle.c
index 82df28d45cd70a7df525f50bbb398d646110cd99..056116e58f43983bc7bb86da170fb554c7a2fac7 100644
--- a/fs/fhandle.c
+++ b/fs/fhandle.c
@@ -235,26 +235,32 @@ static int do_handle_to_path(struct file_handle *handle, struct path *path,
return 0;
}
-/*
- * Allow relaxed permissions of file handles if the caller has the
- * ability to mount the filesystem or create a bind-mount of the
- * provided @mountdirfd.
- *
- * In both cases the caller may be able to get an unobstructed way to
- * the encoded file handle. If the caller is only able to create a
- * bind-mount we need to verify that there are no locked mounts on top
- * of it that could prevent us from getting to the encoded file.
- *
- * In principle, locked mounts can prevent the caller from mounting the
- * filesystem but that only applies to procfs and sysfs neither of which
- * support decoding file handles.
- */
static inline bool may_decode_fh(struct handle_to_path_ctx *ctx,
unsigned int o_flags)
{
struct path *root = &ctx->root;
+ struct export_operations *nop = root->mnt->mnt_sb->s_export_op;
+
+ if (nop && nop->flags & EXPORT_OP_UNRESTRICTED_OPEN)
+ return true;
+
+ if (capable(CAP_DAC_READ_SEARCH))
+ return true;
/*
+ * Allow relaxed permissions of file handles if the caller has the
+ * ability to mount the filesystem or create a bind-mount of the
+ * provided @mountdirfd.
+ *
+ * In both cases the caller may be able to get an unobstructed way to
+ * the encoded file handle. If the caller is only able to create a
+ * bind-mount we need to verify that there are no locked mounts on top
+ * of it that could prevent us from getting to the encoded file.
+ *
+ * In principle, locked mounts can prevent the caller from mounting the
+ * filesystem but that only applies to procfs and sysfs neither of which
+ * support decoding file handles.
+ *
* Restrict to O_DIRECTORY to provide a deterministic API that avoids a
* confusing api in the face of disconnected non-dir dentries.
*
@@ -293,7 +299,7 @@ static int handle_to_path(int mountdirfd, struct file_handle __user *ufh,
if (retval)
goto out_err;
- if (!capable(CAP_DAC_READ_SEARCH) && !may_decode_fh(&ctx, o_flags)) {
+ if (!may_decode_fh(&ctx, o_flags)) {
retval = -EPERM;
goto out_path;
}
diff --git a/include/linux/exportfs.h b/include/linux/exportfs.h
index 893a1d21dc1c4abc7e52325d7a4cf0adb407f039..459508b53e77ed0597cee217ffe3d82cc7cc11a4 100644
--- a/include/linux/exportfs.h
+++ b/include/linux/exportfs.h
@@ -247,6 +247,9 @@ struct export_operations {
*/
#define EXPORT_OP_FLUSH_ON_CLOSE (0x20) /* fs flushes file data on close */
#define EXPORT_OP_ASYNC_LOCK (0x40) /* fs can do async lock request */
+#define EXPORT_OP_UNRESTRICTED_OPEN (0x80) /* FS allows open_by_handle_at
+ without CAP_DAC_READ_SEARCH
+ */
unsigned long flags;
};
--
2.46.1
On Wed, Nov 13, 2024 at 8:11 PM Erin Shepherd <erin.shepherd@e43.eu> wrote: > > For pidfs, there is no reason to restrict file handle decoding by > CAP_DAC_READ_SEARCH. Introduce an export_ops flag that can indicate > this > > Signed-off-by: Erin Shepherd <erin.shepherd@e43.eu> > --- > fs/fhandle.c | 36 +++++++++++++++++++++--------------- > include/linux/exportfs.h | 3 +++ > 2 files changed, 24 insertions(+), 15 deletions(-) > > diff --git a/fs/fhandle.c b/fs/fhandle.c > index 82df28d45cd70a7df525f50bbb398d646110cd99..056116e58f43983bc7bb86da170fb554c7a2fac7 100644 > --- a/fs/fhandle.c > +++ b/fs/fhandle.c > @@ -235,26 +235,32 @@ static int do_handle_to_path(struct file_handle *handle, struct path *path, > return 0; > } > > -/* > - * Allow relaxed permissions of file handles if the caller has the > - * ability to mount the filesystem or create a bind-mount of the > - * provided @mountdirfd. > - * > - * In both cases the caller may be able to get an unobstructed way to > - * the encoded file handle. If the caller is only able to create a > - * bind-mount we need to verify that there are no locked mounts on top > - * of it that could prevent us from getting to the encoded file. > - * > - * In principle, locked mounts can prevent the caller from mounting the > - * filesystem but that only applies to procfs and sysfs neither of which > - * support decoding file handles. > - */ > static inline bool may_decode_fh(struct handle_to_path_ctx *ctx, > unsigned int o_flags) > { > struct path *root = &ctx->root; > + struct export_operations *nop = root->mnt->mnt_sb->s_export_op; > + > + if (nop && nop->flags & EXPORT_OP_UNRESTRICTED_OPEN) > + return true; > + > + if (capable(CAP_DAC_READ_SEARCH)) > + return true; > > /* > + * Allow relaxed permissions of file handles if the caller has the > + * ability to mount the filesystem or create a bind-mount of the > + * provided @mountdirfd. > + * > + * In both cases the caller may be able to get an unobstructed way to > + * the encoded file handle. If the caller is only able to create a > + * bind-mount we need to verify that there are no locked mounts on top > + * of it that could prevent us from getting to the encoded file. > + * > + * In principle, locked mounts can prevent the caller from mounting the > + * filesystem but that only applies to procfs and sysfs neither of which > + * support decoding file handles. > + * > * Restrict to O_DIRECTORY to provide a deterministic API that avoids a > * confusing api in the face of disconnected non-dir dentries. > * > @@ -293,7 +299,7 @@ static int handle_to_path(int mountdirfd, struct file_handle __user *ufh, > if (retval) > goto out_err; > > - if (!capable(CAP_DAC_READ_SEARCH) && !may_decode_fh(&ctx, o_flags)) { > + if (!may_decode_fh(&ctx, o_flags)) { > retval = -EPERM; > goto out_path; > } > diff --git a/include/linux/exportfs.h b/include/linux/exportfs.h > index 893a1d21dc1c4abc7e52325d7a4cf0adb407f039..459508b53e77ed0597cee217ffe3d82cc7cc11a4 100644 > --- a/include/linux/exportfs.h > +++ b/include/linux/exportfs.h > @@ -247,6 +247,9 @@ struct export_operations { > */ > #define EXPORT_OP_FLUSH_ON_CLOSE (0x20) /* fs flushes file data on close */ > #define EXPORT_OP_ASYNC_LOCK (0x40) /* fs can do async lock request */ > +#define EXPORT_OP_UNRESTRICTED_OPEN (0x80) /* FS allows open_by_handle_at > + without CAP_DAC_READ_SEARCH > + */ Don't love the name, but I wonder, isn't SB_NOUSER already a good enough indication that CAP_DAC_READ_SEARCH is irrelevant? Essentially, mnt_fd is the user's proof that they can access the mount and CAP_DAC_READ_SEARCH is the legacy "proof" that the user can reach from mount the inode by path lookup. Which reminds me, what is the mnt_fd expected for opening a pidfd file by handle? Thanks, Amir.
On Thu, Nov 14, 2024 at 07:37:19AM +0100, Amir Goldstein wrote: > On Wed, Nov 13, 2024 at 8:11 PM Erin Shepherd <erin.shepherd@e43.eu> wrote: > > > > For pidfs, there is no reason to restrict file handle decoding by > > CAP_DAC_READ_SEARCH. Introduce an export_ops flag that can indicate > > this > > > > Signed-off-by: Erin Shepherd <erin.shepherd@e43.eu> > > --- > > fs/fhandle.c | 36 +++++++++++++++++++++--------------- > > include/linux/exportfs.h | 3 +++ > > 2 files changed, 24 insertions(+), 15 deletions(-) > > > > diff --git a/fs/fhandle.c b/fs/fhandle.c > > index 82df28d45cd70a7df525f50bbb398d646110cd99..056116e58f43983bc7bb86da170fb554c7a2fac7 100644 > > --- a/fs/fhandle.c > > +++ b/fs/fhandle.c > > @@ -235,26 +235,32 @@ static int do_handle_to_path(struct file_handle *handle, struct path *path, > > return 0; > > } > > > > -/* > > - * Allow relaxed permissions of file handles if the caller has the > > - * ability to mount the filesystem or create a bind-mount of the > > - * provided @mountdirfd. > > - * > > - * In both cases the caller may be able to get an unobstructed way to > > - * the encoded file handle. If the caller is only able to create a > > - * bind-mount we need to verify that there are no locked mounts on top > > - * of it that could prevent us from getting to the encoded file. > > - * > > - * In principle, locked mounts can prevent the caller from mounting the > > - * filesystem but that only applies to procfs and sysfs neither of which > > - * support decoding file handles. > > - */ > > static inline bool may_decode_fh(struct handle_to_path_ctx *ctx, > > unsigned int o_flags) > > { > > struct path *root = &ctx->root; > > + struct export_operations *nop = root->mnt->mnt_sb->s_export_op; > > + > > + if (nop && nop->flags & EXPORT_OP_UNRESTRICTED_OPEN) > > + return true; > > + > > + if (capable(CAP_DAC_READ_SEARCH)) > > + return true; > > > > /* > > + * Allow relaxed permissions of file handles if the caller has the > > + * ability to mount the filesystem or create a bind-mount of the > > + * provided @mountdirfd. > > + * > > + * In both cases the caller may be able to get an unobstructed way to > > + * the encoded file handle. If the caller is only able to create a > > + * bind-mount we need to verify that there are no locked mounts on top > > + * of it that could prevent us from getting to the encoded file. > > + * > > + * In principle, locked mounts can prevent the caller from mounting the > > + * filesystem but that only applies to procfs and sysfs neither of which > > + * support decoding file handles. > > + * > > * Restrict to O_DIRECTORY to provide a deterministic API that avoids a > > * confusing api in the face of disconnected non-dir dentries. > > * > > @@ -293,7 +299,7 @@ static int handle_to_path(int mountdirfd, struct file_handle __user *ufh, > > if (retval) > > goto out_err; > > > > - if (!capable(CAP_DAC_READ_SEARCH) && !may_decode_fh(&ctx, o_flags)) { > > + if (!may_decode_fh(&ctx, o_flags)) { > > retval = -EPERM; > > goto out_path; > > } > > diff --git a/include/linux/exportfs.h b/include/linux/exportfs.h > > index 893a1d21dc1c4abc7e52325d7a4cf0adb407f039..459508b53e77ed0597cee217ffe3d82cc7cc11a4 100644 > > --- a/include/linux/exportfs.h > > +++ b/include/linux/exportfs.h > > @@ -247,6 +247,9 @@ struct export_operations { > > */ > > #define EXPORT_OP_FLUSH_ON_CLOSE (0x20) /* fs flushes file data on close */ > > #define EXPORT_OP_ASYNC_LOCK (0x40) /* fs can do async lock request */ > > +#define EXPORT_OP_UNRESTRICTED_OPEN (0x80) /* FS allows open_by_handle_at > > + without CAP_DAC_READ_SEARCH > > + */ > > Don't love the name, but I wonder, isn't SB_NOUSER already a good > enough indication that CAP_DAC_READ_SEARCH is irrelevant? > > Essentially, mnt_fd is the user's proof that they can access the mount > and CAP_DAC_READ_SEARCH is the legacy "proof" that the user can > reach from mount the inode by path lookup. > > Which reminds me, what is the mnt_fd expected for opening a pidfd > file by handle? int pidfd_self = pidfd_open(getpid(), 0); open_by_handle_at(pidfd_self, ...); is sufficient.
On Wed, Nov 13, 2024 at 05:55:24PM +0000, Erin Shepherd wrote: > For pidfs, there is no reason to restrict file handle decoding by > CAP_DAC_READ_SEARCH. Why is there no reason, i.e. why do you think it is safe. >Introduce an export_ops flag that can indicate > this Also why is is desirable? To be this looks more than sketchy with the actual exporting hat on, but I guess that's now how the cool kids use open by handle these days.
On 14/11/2024 05:37, Christoph Hellwig wrote: > On Wed, Nov 13, 2024 at 05:55:24PM +0000, Erin Shepherd wrote: >> For pidfs, there is no reason to restrict file handle decoding by >> CAP_DAC_READ_SEARCH. > Why is there no reason, i.e. why do you think it is safe. A process can use both open_by_handle_at to open the exact same set of pidfds as they can by pidfd_open. i.e. there is no reason to additionally restrict access to the former API. >> Introduce an export_ops flag that can indicate >> this > Also why is is desirable? > > To be this looks more than sketchy with the actual exporting hat on, > but I guess that's now how the cool kids use open by handle these days. Right - we have a bunch of API file systems where userspace wants stable non-reused file references for the same reasons network filesystems do. The first example of this was cgroupfs, but the same rationale exists for pidfs and process tracking.
Hi Erin, kernel test robot noticed the following build warnings: [auto build test WARNING on 14b6320953a3f856a3f93bf9a0e423395baa593d] url: https://github.com/intel-lab-lkp/linux/commits/Erin-Shepherd/pseudofs-add-support-for-export_ops/20241114-020539 base: 14b6320953a3f856a3f93bf9a0e423395baa593d patch link: https://lore.kernel.org/r/20241113-pidfs_fh-v2-2-9a4d28155a37%40e43.eu patch subject: [PATCH v2 2/3] exportfs: allow fs to disable CAP_DAC_READ_SEARCH check config: openrisc-defconfig (https://download.01.org/0day-ci/archive/20241114/202411140905.a0ntnQQG-lkp@intel.com/config) compiler: or1k-linux-gcc (GCC) 14.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241114/202411140905.a0ntnQQG-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202411140905.a0ntnQQG-lkp@intel.com/ All warnings (new ones prefixed by >>): fs/fhandle.c: In function 'may_decode_fh': >> fs/fhandle.c:242:41: warning: initialization discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers] 242 | struct export_operations *nop = root->mnt->mnt_sb->s_export_op; | ^~~~ vim +/const +242 fs/fhandle.c 237 238 static inline bool may_decode_fh(struct handle_to_path_ctx *ctx, 239 unsigned int o_flags) 240 { 241 struct path *root = &ctx->root; > 242 struct export_operations *nop = root->mnt->mnt_sb->s_export_op; 243 244 if (nop && nop->flags & EXPORT_OP_UNRESTRICTED_OPEN) 245 return true; 246 247 if (capable(CAP_DAC_READ_SEARCH)) 248 return true; 249 250 /* 251 * Allow relaxed permissions of file handles if the caller has the 252 * ability to mount the filesystem or create a bind-mount of the 253 * provided @mountdirfd. 254 * 255 * In both cases the caller may be able to get an unobstructed way to 256 * the encoded file handle. If the caller is only able to create a 257 * bind-mount we need to verify that there are no locked mounts on top 258 * of it that could prevent us from getting to the encoded file. 259 * 260 * In principle, locked mounts can prevent the caller from mounting the 261 * filesystem but that only applies to procfs and sysfs neither of which 262 * support decoding file handles. 263 * 264 * Restrict to O_DIRECTORY to provide a deterministic API that avoids a 265 * confusing api in the face of disconnected non-dir dentries. 266 * 267 * There's only one dentry for each directory inode (VFS rule)... 268 */ 269 if (!(o_flags & O_DIRECTORY)) 270 return false; 271 272 if (ns_capable(root->mnt->mnt_sb->s_user_ns, CAP_SYS_ADMIN)) 273 ctx->flags = HANDLE_CHECK_PERMS; 274 else if (is_mounted(root->mnt) && 275 ns_capable(real_mount(root->mnt)->mnt_ns->user_ns, 276 CAP_SYS_ADMIN) && 277 !has_locked_children(real_mount(root->mnt), root->dentry)) 278 ctx->flags = HANDLE_CHECK_PERMS | HANDLE_CHECK_SUBTREE; 279 else 280 return false; 281 282 /* Are we able to override DAC permissions? */ 283 if (!ns_capable(current_user_ns(), CAP_DAC_READ_SEARCH)) 284 return false; 285 286 ctx->fh_flags = EXPORT_FH_DIR_ONLY; 287 return true; 288 } 289 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
Hi Erin, kernel test robot noticed the following build errors: [auto build test ERROR on 14b6320953a3f856a3f93bf9a0e423395baa593d] url: https://github.com/intel-lab-lkp/linux/commits/Erin-Shepherd/pseudofs-add-support-for-export_ops/20241114-020539 base: 14b6320953a3f856a3f93bf9a0e423395baa593d patch link: https://lore.kernel.org/r/20241113-pidfs_fh-v2-2-9a4d28155a37%40e43.eu patch subject: [PATCH v2 2/3] exportfs: allow fs to disable CAP_DAC_READ_SEARCH check config: x86_64-allnoconfig (https://download.01.org/0day-ci/archive/20241114/202411140603.E03vXsdz-lkp@intel.com/config) compiler: clang version 19.1.3 (https://github.com/llvm/llvm-project ab51eccf88f5321e7c60591c5546b254b6afab99) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241114/202411140603.E03vXsdz-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202411140603.E03vXsdz-lkp@intel.com/ All errors (new ones prefixed by >>): In file included from fs/fhandle.c:2: In file included from include/linux/syscalls.h:93: In file included from include/trace/syscall.h:7: In file included from include/linux/trace_events.h:6: In file included from include/linux/ring_buffer.h:5: In file included from include/linux/mm.h:2213: include/linux/vmstat.h:518:36: warning: arithmetic between different enumeration types ('enum node_stat_item' and 'enum lru_list') [-Wenum-enum-conversion] 518 | return node_stat_name(NR_LRU_BASE + lru) + 3; // skip "nr_" | ~~~~~~~~~~~ ^ ~~~ >> fs/fhandle.c:242:28: error: initializing 'struct export_operations *' with an expression of type 'const struct export_operations *' discards qualifiers [-Werror,-Wincompatible-pointer-types-discards-qualifiers] 242 | struct export_operations *nop = root->mnt->mnt_sb->s_export_op; | ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 warning and 1 error generated. vim +242 fs/fhandle.c 237 238 static inline bool may_decode_fh(struct handle_to_path_ctx *ctx, 239 unsigned int o_flags) 240 { 241 struct path *root = &ctx->root; > 242 struct export_operations *nop = root->mnt->mnt_sb->s_export_op; 243 244 if (nop && nop->flags & EXPORT_OP_UNRESTRICTED_OPEN) 245 return true; 246 247 if (capable(CAP_DAC_READ_SEARCH)) 248 return true; 249 250 /* 251 * Allow relaxed permissions of file handles if the caller has the 252 * ability to mount the filesystem or create a bind-mount of the 253 * provided @mountdirfd. 254 * 255 * In both cases the caller may be able to get an unobstructed way to 256 * the encoded file handle. If the caller is only able to create a 257 * bind-mount we need to verify that there are no locked mounts on top 258 * of it that could prevent us from getting to the encoded file. 259 * 260 * In principle, locked mounts can prevent the caller from mounting the 261 * filesystem but that only applies to procfs and sysfs neither of which 262 * support decoding file handles. 263 * 264 * Restrict to O_DIRECTORY to provide a deterministic API that avoids a 265 * confusing api in the face of disconnected non-dir dentries. 266 * 267 * There's only one dentry for each directory inode (VFS rule)... 268 */ 269 if (!(o_flags & O_DIRECTORY)) 270 return false; 271 272 if (ns_capable(root->mnt->mnt_sb->s_user_ns, CAP_SYS_ADMIN)) 273 ctx->flags = HANDLE_CHECK_PERMS; 274 else if (is_mounted(root->mnt) && 275 ns_capable(real_mount(root->mnt)->mnt_ns->user_ns, 276 CAP_SYS_ADMIN) && 277 !has_locked_children(real_mount(root->mnt), root->dentry)) 278 ctx->flags = HANDLE_CHECK_PERMS | HANDLE_CHECK_SUBTREE; 279 else 280 return false; 281 282 /* Are we able to override DAC permissions? */ 283 if (!ns_capable(current_user_ns(), CAP_DAC_READ_SEARCH)) 284 return false; 285 286 ctx->fh_flags = EXPORT_FH_DIR_ONLY; 287 return true; 288 } 289 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
On 64-bit platforms, userspace can read the pidfd's inode in order to
get a never-repeated PID identifier. On 32-bit platforms this identifier
is not exposed, as inodes are limited to 32 bits. Instead expose the
identifier via export_fh, which makes it available to userspace via
name_to_handle_at
In addition we implement fh_to_dentry, which allows userspace to
recover a pidfd from a PID file handle.
We stash the process' PID in the root pid namespace inside the handle,
and use that to recover the pid (validating that pid->ino matches the
value in the handle, i.e. that the pid has not been reused).
We use the root namespace in order to ensure that file handles can be
moved across namespaces; however, we validate that the PID exists in
the current namespace before returning the inode.
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Erin Shepherd <erin.shepherd@e43.eu>
---
fs/pidfs.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 61 insertions(+), 1 deletion(-)
diff --git a/fs/pidfs.c b/fs/pidfs.c
index 80675b6bf88459c22787edaa68db360bdc0d0782..0684a9b8fe71c5205fb153b2714bc9c672045fd5 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/anon_inodes.h>
+#include <linux/exportfs.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/magic.h>
@@ -347,11 +348,69 @@ static const struct dentry_operations pidfs_dentry_operations = {
.d_prune = stashed_dentry_prune,
};
+#define PIDFD_FID_LEN 3
+
+struct pidfd_fid {
+ u64 ino;
+ s32 pid;
+} __packed;
+
+static int pidfs_encode_fh(struct inode *inode, u32 *fh, int *max_len,
+ struct inode *parent)
+{
+ struct pid *pid = inode->i_private;
+ struct pidfd_fid *fid = (struct pidfd_fid *)fh;
+
+ if (*max_len < PIDFD_FID_LEN) {
+ *max_len = PIDFD_FID_LEN;
+ return FILEID_INVALID;
+ }
+
+ fid->ino = pid->ino;
+ fid->pid = pid_nr(pid);
+ *max_len = PIDFD_FID_LEN;
+ return FILEID_INO64_GEN;
+}
+
+static struct dentry *pidfs_fh_to_dentry(struct super_block *sb,
+ struct fid *gen_fid,
+ int fh_len, int fh_type)
+{
+ int ret;
+ struct path path;
+ struct pidfd_fid *fid = (struct pidfd_fid *)gen_fid;
+ struct pid *pid;
+
+ if (fh_type != FILEID_INO64_GEN || fh_len < PIDFD_FID_LEN)
+ return NULL;
+
+ scoped_guard(rcu) {
+ pid = find_pid_ns(fid->pid, &init_pid_ns);
+ if (!pid || pid->ino != fid->ino || pid_vnr(pid) == 0)
+ return NULL;
+
+ pid = get_pid(pid);
+ }
+
+ ret = path_from_stashed(&pid->stashed, pidfs_mnt, pid, &path);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ mntput(path.mnt);
+ return path.dentry;
+}
+
+static const struct export_operations pidfs_export_operations = {
+ .encode_fh = pidfs_encode_fh,
+ .fh_to_dentry = pidfs_fh_to_dentry,
+ .flags = EXPORT_OP_UNRESTRICTED_OPEN,
+};
+
static int pidfs_init_inode(struct inode *inode, void *data)
{
inode->i_private = data;
inode->i_flags |= S_PRIVATE;
- inode->i_mode |= S_IRWXU;
+ inode->i_mode |= S_IRWXU | S_IRWXG | S_IRWXO;
inode->i_op = &pidfs_inode_operations;
inode->i_fop = &pidfs_file_operations;
/*
@@ -382,6 +441,7 @@ static int pidfs_init_fs_context(struct fs_context *fc)
return -ENOMEM;
ctx->ops = &pidfs_sops;
+ ctx->eops = &pidfs_export_operations;
ctx->dops = &pidfs_dentry_operations;
fc->s_fs_info = (void *)&pidfs_stashed_ops;
return 0;
--
2.46.1
On Wed, Nov 13, 2024 at 05:55:25PM +0000, Erin Shepherd wrote: > On 64-bit platforms, userspace can read the pidfd's inode in order to > get a never-repeated PID identifier. On 32-bit platforms this identifier > is not exposed, as inodes are limited to 32 bits. Instead expose the > identifier via export_fh, which makes it available to userspace via > name_to_handle_at > > In addition we implement fh_to_dentry, which allows userspace to > recover a pidfd from a PID file handle. > > We stash the process' PID in the root pid namespace inside the handle, > and use that to recover the pid (validating that pid->ino matches the > value in the handle, i.e. that the pid has not been reused). > > We use the root namespace in order to ensure that file handles can be > moved across namespaces; however, we validate that the PID exists in > the current namespace before returning the inode. > > Reviewed-by: Amir Goldstein <amir73il@gmail.com> > Signed-off-by: Erin Shepherd <erin.shepherd@e43.eu> > --- I think you need at least something like the following completely untested draft on top: - the pidfs_finish_open_by_handle_at() is somewhat of a clutch to handle thread vs thread-group pidfds but it works. - In contrast to pidfd_open() that uses dentry_open() to create a pidfd open_by_handle_at() uses file_open_root(). That's overall fine but makes pidfds subject to security hooks which they aren't via pidfd_open(). It also necessitats pidfs_finish_open_by_handle_at(). There's probably other solutions I'm not currently seeing. - The exportfs_decode_fh_raw() call that's used to decode the pidfd is passed vfs_dentry_acceptable() as acceptability callback. For pidfds we don't need any of that functionality and we don't need any of the disconnected dentry handling logic. So the easiest way to fix that is to rely on EXPORT_OP_UNRESTRICTED_OPEN to skip everything. That in turns means the only acceptability we have is the nop->fh_to_dentry() callback for pidfs. - This all really needs rigorous selftests before we can even think of merging any of this. Anway, here's the _completely untested, quickly drafted_ diff on top: diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c index 4f2dd4ab4486..65c93f7132d4 100644 --- a/fs/exportfs/expfs.c +++ b/fs/exportfs/expfs.c @@ -450,6 +450,13 @@ exportfs_decode_fh_raw(struct vfsmount *mnt, struct fid *fid, int fh_len, goto err_result; } + /* + * The filesystem has no acceptance criteria other than those in + * nop->fh_to_dentry(). + */ + if (nop->flags & EXPORT_OP_UNRESTRICTED_OPEN) + return result; + /* * If no acceptance criteria was specified by caller, a disconnected * dentry is also accepatable. Callers may use this mode to query if diff --git a/fs/fhandle.c b/fs/fhandle.c index 056116e58f43..89c2efacc0c3 100644 --- a/fs/fhandle.c +++ b/fs/fhandle.c @@ -11,6 +11,7 @@ #include <linux/personality.h> #include <linux/uaccess.h> #include <linux/compat.h> +#include <linux/pidfs.h> #include "internal.h" #include "mount.h" @@ -218,20 +219,21 @@ static int do_handle_to_path(struct file_handle *handle, struct path *path, { int handle_dwords; struct vfsmount *mnt = ctx->root.mnt; + struct dentry *dentry; /* change the handle size to multiple of sizeof(u32) */ handle_dwords = handle->handle_bytes >> 2; - path->dentry = exportfs_decode_fh_raw(mnt, - (struct fid *)handle->f_handle, - handle_dwords, handle->handle_type, - ctx->fh_flags, - vfs_dentry_acceptable, ctx); - if (IS_ERR_OR_NULL(path->dentry)) { - if (path->dentry == ERR_PTR(-ENOMEM)) + dentry = exportfs_decode_fh_raw(mnt, (struct fid *)handle->f_handle, + handle_dwords, handle->handle_type, + ctx->fh_flags, vfs_dentry_acceptable, + ctx); + if (IS_ERR_OR_NULL(dentry)) { + if (dentry == ERR_PTR(-ENOMEM)) return -ENOMEM; return -ESTALE; } path->mnt = mntget(mnt); + path->dentry = dentry; return 0; } @@ -239,7 +241,7 @@ static inline bool may_decode_fh(struct handle_to_path_ctx *ctx, unsigned int o_flags) { struct path *root = &ctx->root; - struct export_operations *nop = root->mnt->mnt_sb->s_export_op; + const struct export_operations *nop = root->mnt->mnt_sb->s_export_op; if (nop && nop->flags & EXPORT_OP_UNRESTRICTED_OPEN) return true; @@ -342,7 +344,7 @@ static long do_handle_open(int mountdirfd, struct file_handle __user *ufh, int open_flag) { long retval = 0; - struct path path; + struct path path __free(path_put) = {}; struct file *file; int fd; @@ -351,19 +353,24 @@ static long do_handle_open(int mountdirfd, struct file_handle __user *ufh, return retval; fd = get_unused_fd_flags(open_flag); - if (fd < 0) { - path_put(&path); + if (fd < 0) return fd; - } + file = file_open_root(&path, "", open_flag, 0); if (IS_ERR(file)) { put_unused_fd(fd); - retval = PTR_ERR(file); - } else { - retval = fd; - fd_install(fd, file); + return PTR_ERR(file); } - path_put(&path); + + retval = pidfs_finish_open_by_handle_at(file, open_flag); + if (retval) { + put_unused_fd(fd); + fput(file); + return retval; + } + + retval = fd; + fd_install(fd, file); return retval; } diff --git a/fs/pidfs.c b/fs/pidfs.c index 0684a9b8fe71..19948002f395 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -237,6 +237,24 @@ struct pid *pidfd_pid(const struct file *file) return file_inode(file)->i_private; } +int pidfs_finish_open_by_handle_at(struct file *file, unsigned int oflags) +{ + struct pid *pid; + bool thread = oflags & PIDFD_THREAD; + + pid = pidfd_pid(file); + if (IS_ERR(pid)) + return 0; + + if (!pid_has_task(pid, thread ? PIDTYPE_PID : PIDTYPE_TGID)) + return -EINVAL; + + if (thread) + file->f_flags |= PIDFD_THREAD; + + return 0; +} + static struct vfsmount *pidfs_mnt __ro_after_init; #if BITS_PER_LONG == 32 @@ -377,7 +395,7 @@ static struct dentry *pidfs_fh_to_dentry(struct super_block *sb, int fh_len, int fh_type) { int ret; - struct path path; + struct path path __free(path_put) = {}; struct pidfd_fid *fid = (struct pidfd_fid *)gen_fid; struct pid *pid; @@ -393,11 +411,12 @@ static struct dentry *pidfs_fh_to_dentry(struct super_block *sb, } ret = path_from_stashed(&pid->stashed, pidfs_mnt, pid, &path); - if (ret < 0) + if (ret < 0) { + put_pid(pid); return ERR_PTR(ret); + } - mntput(path.mnt); - return path.dentry; + return dget(path.dentry); } static const struct export_operations pidfs_export_operations = { diff --git a/include/linux/pidfs.h b/include/linux/pidfs.h index 75bdf9807802..9a4130056e7d 100644 --- a/include/linux/pidfs.h +++ b/include/linux/pidfs.h @@ -3,6 +3,7 @@ #define _LINUX_PID_FS_H struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags); +int pidfs_finish_open_by_handle_at(struct file *file, unsigned int oflags); void __init pidfs_init(void); #endif /* _LINUX_PID_FS_H */ > fs/pidfs.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- > 1 file changed, 61 insertions(+), 1 deletion(-) > > diff --git a/fs/pidfs.c b/fs/pidfs.c > index 80675b6bf88459c22787edaa68db360bdc0d0782..0684a9b8fe71c5205fb153b2714bc9c672045fd5 100644 > --- a/fs/pidfs.c > +++ b/fs/pidfs.c > @@ -1,5 +1,6 @@ > // SPDX-License-Identifier: GPL-2.0 > #include <linux/anon_inodes.h> > +#include <linux/exportfs.h> > #include <linux/file.h> > #include <linux/fs.h> > #include <linux/magic.h> > @@ -347,11 +348,69 @@ static const struct dentry_operations pidfs_dentry_operations = { > .d_prune = stashed_dentry_prune, > }; > > +#define PIDFD_FID_LEN 3 > + > +struct pidfd_fid { > + u64 ino; > + s32 pid; > +} __packed; > + > +static int pidfs_encode_fh(struct inode *inode, u32 *fh, int *max_len, > + struct inode *parent) > +{ > + struct pid *pid = inode->i_private; > + struct pidfd_fid *fid = (struct pidfd_fid *)fh; > + > + if (*max_len < PIDFD_FID_LEN) { > + *max_len = PIDFD_FID_LEN; > + return FILEID_INVALID; > + } > + > + fid->ino = pid->ino; > + fid->pid = pid_nr(pid); > + *max_len = PIDFD_FID_LEN; > + return FILEID_INO64_GEN; > +} > + > +static struct dentry *pidfs_fh_to_dentry(struct super_block *sb, > + struct fid *gen_fid, > + int fh_len, int fh_type) > +{ > + int ret; > + struct path path; > + struct pidfd_fid *fid = (struct pidfd_fid *)gen_fid; > + struct pid *pid; > + > + if (fh_type != FILEID_INO64_GEN || fh_len < PIDFD_FID_LEN) > + return NULL; > + > + scoped_guard(rcu) { > + pid = find_pid_ns(fid->pid, &init_pid_ns); > + if (!pid || pid->ino != fid->ino || pid_vnr(pid) == 0) > + return NULL; > + > + pid = get_pid(pid); > + } > + > + ret = path_from_stashed(&pid->stashed, pidfs_mnt, pid, &path); > + if (ret < 0) > + return ERR_PTR(ret); > + > + mntput(path.mnt); > + return path.dentry; > +} > + > +static const struct export_operations pidfs_export_operations = { > + .encode_fh = pidfs_encode_fh, > + .fh_to_dentry = pidfs_fh_to_dentry, > + .flags = EXPORT_OP_UNRESTRICTED_OPEN, > +}; > + > static int pidfs_init_inode(struct inode *inode, void *data) > { > inode->i_private = data; > inode->i_flags |= S_PRIVATE; > - inode->i_mode |= S_IRWXU; > + inode->i_mode |= S_IRWXU | S_IRWXG | S_IRWXO; > inode->i_op = &pidfs_inode_operations; > inode->i_fop = &pidfs_file_operations; > /* > @@ -382,6 +441,7 @@ static int pidfs_init_fs_context(struct fs_context *fc) > return -ENOMEM; > > ctx->ops = &pidfs_sops; > + ctx->eops = &pidfs_export_operations; > ctx->dops = &pidfs_dentry_operations; > fc->s_fs_info = (void *)&pidfs_stashed_ops; > return 0; > > -- > 2.46.1 >
On 14/11/2024 13:52, Christian Brauner wrote: > I think you need at least something like the following completely > untested draft on top: > > - the pidfs_finish_open_by_handle_at() is somewhat of a clutch to handle > thread vs thread-group pidfds but it works. > > - In contrast to pidfd_open() that uses dentry_open() to create a pidfd > open_by_handle_at() uses file_open_root(). That's overall fine but > makes pidfds subject to security hooks which they aren't via > pidfd_open(). It also necessitats pidfs_finish_open_by_handle_at(). > There's probably other solutions I'm not currently seeing. These two concerns combined with the special flag make me wonder if pidfs is so much of a special snowflake we should just special case it up front and skip all of the shared handle decode logic? > - The exportfs_decode_fh_raw() call that's used to decode the pidfd is > passed vfs_dentry_acceptable() as acceptability callback. For pidfds > we don't need any of that functionality and we don't need any of the > disconnected dentry handling logic. So the easiest way to fix that is > to rely on EXPORT_OP_UNRESTRICTED_OPEN to skip everything. That in > turns means the only acceptability we have is the nop->fh_to_dentry() > callback for pidfs. With the current logic we go exportfs_decode_fh_raw(...) -> find_acceptable_alias(result) -> vfs_dentry_acceptable(context, result). vfs_dentry_acceptable immediately returns 1 if ctx->flags is 0, which will always be the case if EXPORT_OP_UNRESTRICTED_OPEN was set, so we immediately fall back out of the call tree and return result. So I'm not 100% sure we actually need this special case but I'm not opposed. > - This all really needs rigorous selftests before we can even think of > merging any of this.
On Thu, Nov 14, 2024 at 02:13:06PM +0100, Erin Shepherd wrote: > On 14/11/2024 13:52, Christian Brauner wrote: > > > I think you need at least something like the following completely > > untested draft on top: > > > > - the pidfs_finish_open_by_handle_at() is somewhat of a clutch to handle > > thread vs thread-group pidfds but it works. > > > > - In contrast to pidfd_open() that uses dentry_open() to create a pidfd > > open_by_handle_at() uses file_open_root(). That's overall fine but > > makes pidfds subject to security hooks which they aren't via > > pidfd_open(). It also necessitats pidfs_finish_open_by_handle_at(). > > There's probably other solutions I'm not currently seeing. > > These two concerns combined with the special flag make me wonder if pidfs > is so much of a special snowflake we should just special case it up front > and skip all of the shared handle decode logic? Care to try a patch and see what it looks like? > > > - The exportfs_decode_fh_raw() call that's used to decode the pidfd is > > passed vfs_dentry_acceptable() as acceptability callback. For pidfds > > we don't need any of that functionality and we don't need any of the > > disconnected dentry handling logic. So the easiest way to fix that is > > to rely on EXPORT_OP_UNRESTRICTED_OPEN to skip everything. That in > > turns means the only acceptability we have is the nop->fh_to_dentry() > > callback for pidfs. > > With the current logic we go exportfs_decode_fh_raw(...) -> > find_acceptable_alias(result) -> vfs_dentry_acceptable(context, result). > vfs_dentry_acceptable immediately returns 1 if ctx->flags is 0, which will > always be the case if EXPORT_OP_UNRESTRICTED_OPEN was set, so we immediately > fall back out of the call tree and return result. > > So I'm not 100% sure we actually need this special case but I'm not opposed. Oh right, I completely forgot that find_acceptable_alias() is a no-op for pidfs.
On 14/11/2024 15:13, Christian Brauner wrote: > On Thu, Nov 14, 2024 at 02:13:06PM +0100, Erin Shepherd wrote: >> These two concerns combined with the special flag make me wonder if pidfs >> is so much of a special snowflake we should just special case it up front >> and skip all of the shared handle decode logic? > Care to try a patch and see what it looks like? The following is a completely untested sketch on top of the existing patch series. Some notes: - I made heavy use of the cleanup macros. I'm happy to convert things back to goto out_xx style if preferred - writing things this way just made bashing out the code without dropping resources on the floor easier - If you don't implement fh_to_dentry then name_to_handle_at will just return an error unless called with AT_HANDLE_FID. We need to decide what to do about that - The GET_PATH_FD_IS_NORMAL/etc constants don't match (what I see as) usual kernel style but I'm not sure how to conventionally express something like that Otherwise, I'm fairly happy with how it came out in the end. Maybe handle_to_path and do_handle_to_path could be collapsed into each other at this point. diff --git a/fs/fhandle.c b/fs/fhandle.c index 056116e58f43..697246085b69 100644 --- a/fs/fhandle.c +++ b/fs/fhandle.c @@ -11,6 +11,7 @@ #include <linux/personality.h> #include <linux/uaccess.h> #include <linux/compat.h> +#include <linux/pidfs.h> #include "internal.h" #include "mount.h" @@ -130,6 +131,11 @@ SYSCALL_DEFINE5(name_to_handle_at, int, dfd, const char __user *, name, return err; } +enum { + GET_PATH_FD_IS_NORMAL = 0, + GET_PATH_FD_IS_PIDFD = 1, +}; + static int get_path_from_fd(int fd, struct path *root) { if (fd == AT_FDCWD) { @@ -139,15 +145,16 @@ static int get_path_from_fd(int fd, struct path *root) path_get(root); spin_unlock(&fs->lock); } else { - struct fd f = fdget(fd); + CLASS(fd, f)(fd); if (!fd_file(f)) return -EBADF; + if (pidfd_pid(fd_file(f))) + return GET_PATH_FD_IS_PIDFD; *root = fd_file(f)->f_path; path_get(root); - fdput(f); } - return 0; + return GET_PATH_FD_IS_NORMAL; } enum handle_to_path_flags { @@ -287,84 +294,94 @@ static inline bool may_decode_fh(struct handle_to_path_ctx *ctx, return true; } -static int handle_to_path(int mountdirfd, struct file_handle __user *ufh, - struct path *path, unsigned int o_flags) +static int copy_handle_from_user(struct file_handle __user *ufh, struct file_handle **ret) { - int retval = 0; - struct file_handle f_handle; - struct file_handle *handle = NULL; - struct handle_to_path_ctx ctx = {}; - - retval = get_path_from_fd(mountdirfd, &ctx.root); - if (retval) - goto out_err; - - if (!may_decode_fh(&ctx, o_flags)) { - retval = -EPERM; - goto out_path; - } - + struct file_handle f_handle, *handle; if (copy_from_user(&f_handle, ufh, sizeof(struct file_handle))) { - retval = -EFAULT; - goto out_path; + return -EFAULT; } if ((f_handle.handle_bytes > MAX_HANDLE_SZ) || (f_handle.handle_bytes == 0)) { - retval = -EINVAL; - goto out_path; + return -EINVAL; } handle = kmalloc(struct_size(handle, f_handle, f_handle.handle_bytes), GFP_KERNEL); if (!handle) { - retval = -ENOMEM; - goto out_path; + return -ENOMEM; } + /* copy the full handle */ *handle = f_handle; if (copy_from_user(&handle->f_handle, &ufh->f_handle, f_handle.handle_bytes)) { - retval = -EFAULT; - goto out_handle; + kfree(handle); + return -EFAULT; } - retval = do_handle_to_path(handle, path, &ctx); + *ret = handle; + return 0; +} -out_handle: - kfree(handle); -out_path: - path_put(&ctx.root); -out_err: - return retval; +static int handle_to_path(struct path root, struct file_handle __user *ufh, + struct path *path, unsigned int o_flags) +{ + struct file_handle *handle = NULL; + struct handle_to_path_ctx ctx = { + .root = root, + }; + + if (!may_decode_fh(&ctx, o_flags)) { + return -EPERM; + } + + return do_handle_to_path(handle, path, &ctx); } static long do_handle_open(int mountdirfd, struct file_handle __user *ufh, int open_flag) { long retval = 0; - struct path path; - struct file *file; - int fd; + struct file_handle *handle __free(kfree) = NULL; + struct path __free(path_put) root = {}; + struct path __free(path_put) path = {}; + struct file *file = NULL; + int root_type; - retval = handle_to_path(mountdirfd, ufh, &path, open_flag); + root_type = get_path_from_fd(mountdirfd, &root); + if (root_type < 0) + return root_type; + + retval = copy_handle_from_user(ufh, &handle); if (retval) return retval; - fd = get_unused_fd_flags(open_flag); - if (fd < 0) { - path_put(&path); + CLASS(get_unused_fd, fd)(open_flag); + if (fd < 0) return fd; + + switch (root_type) { + case GET_PATH_FD_IS_NORMAL: + retval = handle_to_path(root, handle, &path, open_flag); + if (retval) + return retval; + file = file_open_root(&path, "", open_flag, 0); + if (IS_ERR(file)) { + return PTR_ERR(file); + } + break; + + case GET_PATH_FD_IS_PIDFD: + file = pidfd_open_by_handle( + handle->f_handle, handle->handle_bytes >> 2, handle->handle_type, + open_flag); + if (IS_ERR(file)) + return retval; + break; } - file = file_open_root(&path, "", open_flag, 0); - if (IS_ERR(file)) { - put_unused_fd(fd); - retval = PTR_ERR(file); - } else { - retval = fd; - fd_install(fd, file); - } - path_put(&path); - return retval; + + fd_install(fd, file); + return take_fd(fd); } /** diff --git a/fs/pidfs.c b/fs/pidfs.c index 0684a9b8fe71..65b72dc05380 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -453,22 +453,53 @@ static struct file_system_type pidfs_type = { .kill_sb = kill_anon_super, }; -struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags) +static struct file *__pidfs_alloc_file(struct pid *pid, unsigned int flags) { - struct file *pidfd_file; struct path path; int ret; - ret = path_from_stashed(&pid->stashed, pidfs_mnt, get_pid(pid), &path); + ret = path_from_stashed(&pid->stashed, pidfs_mnt, pid, &path); if (ret < 0) return ERR_PTR(ret); pidfd_file = dentry_open(&path, flags, current_cred()); + if (!IS_ERR(pidfd_file)) + pidfd_file->f_flags |= (flags & PIDFD_THREAD); path_put(&path); return pidfd_file; } +struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags) +{ + return __pidfs_alloc_file(get_pid(pid), flags); +} + +struct file *pidfd_open_by_handle(void *gen_fid, int fh_len, int fh_type, + unsigned int flags) +{ + bool thread = flags & PIDFD_THREAD; + struct pidfd_fid *fid = (struct pidfd_fid *)gen_fid; + struct pid *pid; + + if (fh_type != FILEID_INO64_GEN || fh_len < PIDFD_FID_LEN) + return ERR_PTR(-ESTALE); + + scoped_guard(rcu) { + pid = find_pid_ns(fid->pid, &init_pid_ns); + if (!pid || pid->ino != fid->ino || pid_vnr(pid) == 0) + return ERR_PTR(-ESTALE); + if(!pid_has_task(pid, thread ? PIDTYPE_PID : PIDTYPE_TGID)) + return ERR_PTR(-ESTALE); + /* Something better? EINVAL like pidfd_open wouldn't be + very obvious... */ + + pid = get_pid(pid); + } + + return __pidfs_alloc_file(pid, flags); +} + void __init pidfs_init(void) { pidfs_mnt = kern_mount(&pidfs_type); diff --git a/include/linux/pidfs.h b/include/linux/pidfs.h index 75bdf9807802..fba2654ae60f 100644 --- a/include/linux/pidfs.h +++ b/include/linux/pidfs.h @@ -3,6 +3,8 @@ #define _LINUX_PID_FS_H struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags); +struct file *pidfd_open_by_handle(void *gen_fid, int fh_len, int fh_type, + unsigned int flags); void __init pidfs_init(void); #endif /* _LINUX_PID_FS_H */ diff --git a/kernel/fork.c b/kernel/fork.c index 22f43721d031..fc47c76e4ff4 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -2015,11 +2015,6 @@ static int __pidfd_prepare(struct pid *pid, unsigned int flags, struct file **re put_unused_fd(pidfd); return PTR_ERR(pidfd_file); } - /* - * anon_inode_getfile() ignores everything outside of the - * O_ACCMODE | O_NONBLOCK mask, set PIDFD_THREAD manually. - */ - pidfd_file->f_flags |= (flags & PIDFD_THREAD); *ret = pidfd_file; return pidfd; }
On Thu, Nov 14, 2024 at 11:51 PM Erin Shepherd <erin.shepherd@e43.eu> wrote: > > On 14/11/2024 15:13, Christian Brauner wrote: > > > On Thu, Nov 14, 2024 at 02:13:06PM +0100, Erin Shepherd wrote: > >> These two concerns combined with the special flag make me wonder if pidfs > >> is so much of a special snowflake we should just special case it up front > >> and skip all of the shared handle decode logic? > > Care to try a patch and see what it looks like? > > The following is a completely untested sketch on top of the existing patch series. > Some notes: > > - I made heavy use of the cleanup macros. I'm happy to convert things back to > goto out_xx style if preferred - writing things this way just made bashing out > the code without dropping resources on the floor easier Your cleanup is very welcome, just please! not in the same patch as refactoring and logic changes. Please do these 3 different things in different commits. This patch is unreviewable as far as I am concerned. > - If you don't implement fh_to_dentry then name_to_handle_at will just return an error > unless called with AT_HANDLE_FID. We need to decide what to do about that What's to decide? I did not understand the problem. > - The GET_PATH_FD_IS_NORMAL/etc constants don't match (what I see as) usual kernel style > but I'm not sure how to conventionally express something like that I believe the conventional way to express a custom operation is an optional method. For example: static int exportfs_get_name(struct vfsmount *mnt, struct dentry *dir, char *name, struct dentry *child) { const struct export_operations *nop = dir->d_sb->s_export_op; struct path path = {.mnt = mnt, .dentry = dir}; if (nop->get_name) return nop->get_name(dir, name, child); else return get_name(&path, name, child); } There are plenty of optional custom inode, file, sb, dentry operations with default fallback. some examples: if (dir_inode->i_op->atomic_open) { dentry = atomic_open(nd, dentry, file, open_flag, mode); if (!splice && file_out->f_op->copy_file_range) { ret = file_out->f_op->copy_file_range(file_in, pos_in, file_out, pos_out, len, flags); } else if (!splice && file_in->f_op->remap_file_range && samesb) { ret = file_in->f_op->remap_file_range(file_in, pos_in, So I think the right model for you to follow is a custom optional s_export_op->open_by_handle() operation. Thanks, Amir.
On Wed, Nov 13, 2024 at 7:11 PM Erin Shepherd <erin.shepherd@e43.eu> wrote: > > On 64-bit platforms, userspace can read the pidfd's inode in order to > get a never-repeated PID identifier. On 32-bit platforms this identifier > is not exposed, as inodes are limited to 32 bits. Instead expose the > identifier via export_fh, which makes it available to userspace via > name_to_handle_at > > In addition we implement fh_to_dentry, which allows userspace to > recover a pidfd from a PID file handle. "In addition" is a good indication that a separate patch was a good idea.. > > We stash the process' PID in the root pid namespace inside the handle, > and use that to recover the pid (validating that pid->ino matches the > value in the handle, i.e. that the pid has not been reused). > > We use the root namespace in order to ensure that file handles can be > moved across namespaces; however, we validate that the PID exists in > the current namespace before returning the inode. > > Reviewed-by: Amir Goldstein <amir73il@gmail.com> This patch has changed enough that you should not have kept my RVB. BTW, please refrain from using the same subject for the cover letter and a single patch. They are not the same thing, so if they get the same name, one of them has an inaccurate description. > Signed-off-by: Erin Shepherd <erin.shepherd@e43.eu> > --- > fs/pidfs.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- > 1 file changed, 61 insertions(+), 1 deletion(-) > > diff --git a/fs/pidfs.c b/fs/pidfs.c > index 80675b6bf88459c22787edaa68db360bdc0d0782..0684a9b8fe71c5205fb153b2714bc9c672045fd5 100644 > --- a/fs/pidfs.c > +++ b/fs/pidfs.c > @@ -1,5 +1,6 @@ > // SPDX-License-Identifier: GPL-2.0 > #include <linux/anon_inodes.h> > +#include <linux/exportfs.h> > #include <linux/file.h> > #include <linux/fs.h> > #include <linux/magic.h> > @@ -347,11 +348,69 @@ static const struct dentry_operations pidfs_dentry_operations = { > .d_prune = stashed_dentry_prune, > }; > > +#define PIDFD_FID_LEN 3 > + > +struct pidfd_fid { > + u64 ino; > + s32 pid; > +} __packed; > + > +static int pidfs_encode_fh(struct inode *inode, u32 *fh, int *max_len, > + struct inode *parent) > +{ > + struct pid *pid = inode->i_private; > + struct pidfd_fid *fid = (struct pidfd_fid *)fh; > + > + if (*max_len < PIDFD_FID_LEN) { > + *max_len = PIDFD_FID_LEN; > + return FILEID_INVALID; > + } > + > + fid->ino = pid->ino; > + fid->pid = pid_nr(pid); > + *max_len = PIDFD_FID_LEN; > + return FILEID_INO64_GEN; > +} > + > +static struct dentry *pidfs_fh_to_dentry(struct super_block *sb, > + struct fid *gen_fid, > + int fh_len, int fh_type) > +{ > + int ret; > + struct path path; > + struct pidfd_fid *fid = (struct pidfd_fid *)gen_fid; > + struct pid *pid; > + > + if (fh_type != FILEID_INO64_GEN || fh_len < PIDFD_FID_LEN) > + return NULL; > + > + scoped_guard(rcu) { > + pid = find_pid_ns(fid->pid, &init_pid_ns); > + if (!pid || pid->ino != fid->ino || pid_vnr(pid) == 0) > + return NULL; > + > + pid = get_pid(pid); > + } > + > + ret = path_from_stashed(&pid->stashed, pidfs_mnt, pid, &path); > + if (ret < 0) > + return ERR_PTR(ret); How come no need to put_pid() in case of error? > + > + mntput(path.mnt); > + return path.dentry; > +} > + > +static const struct export_operations pidfs_export_operations = { > + .encode_fh = pidfs_encode_fh, > + .fh_to_dentry = pidfs_fh_to_dentry, > + .flags = EXPORT_OP_UNRESTRICTED_OPEN, > +}; > + > static int pidfs_init_inode(struct inode *inode, void *data) > { > inode->i_private = data; > inode->i_flags |= S_PRIVATE; > - inode->i_mode |= S_IRWXU; > + inode->i_mode |= S_IRWXU | S_IRWXG | S_IRWXO; This change is not explained. Why is it here? Thanks, Amir.
On 14/11/2024 08:07, Amir Goldstein wrote: > On Wed, Nov 13, 2024 at 7:11 PM Erin Shepherd <erin.shepherd@e43.eu> wrote: >> On 64-bit platforms, userspace can read the pidfd's inode in order to >> get a never-repeated PID identifier. On 32-bit platforms this identifier >> is not exposed, as inodes are limited to 32 bits. Instead expose the >> identifier via export_fh, which makes it available to userspace via >> name_to_handle_at >> >> In addition we implement fh_to_dentry, which allows userspace to >> recover a pidfd from a PID file handle. > "In addition" is a good indication that a separate patch was a good idea.. > >> We stash the process' PID in the root pid namespace inside the handle, >> and use that to recover the pid (validating that pid->ino matches the >> value in the handle, i.e. that the pid has not been reused). >> >> We use the root namespace in order to ensure that file handles can be >> moved across namespaces; however, we validate that the PID exists in >> the current namespace before returning the inode. >> >> Reviewed-by: Amir Goldstein <amir73il@gmail.com> > This patch has changed enough that you should not have kept my RVB. > > BTW, please refrain from using the same subject for the cover letter and > a single patch. They are not the same thing, so if they get the same > name, one of them has an inaccurate description. > ACK to all three. >> Signed-off-by: Erin Shepherd <erin.shepherd@e43.eu> >> --- >> fs/pidfs.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- >> 1 file changed, 61 insertions(+), 1 deletion(-) >> >> diff --git a/fs/pidfs.c b/fs/pidfs.c >> index 80675b6bf88459c22787edaa68db360bdc0d0782..0684a9b8fe71c5205fb153b2714bc9c672045fd5 100644 >> --- a/fs/pidfs.c >> +++ b/fs/pidfs.c >> @@ -1,5 +1,6 @@ >> // SPDX-License-Identifier: GPL-2.0 >> #include <linux/anon_inodes.h> >> +#include <linux/exportfs.h> >> #include <linux/file.h> >> #include <linux/fs.h> >> #include <linux/magic.h> >> @@ -347,11 +348,69 @@ static const struct dentry_operations pidfs_dentry_operations = { >> .d_prune = stashed_dentry_prune, >> }; >> >> +#define PIDFD_FID_LEN 3 >> + >> +struct pidfd_fid { >> + u64 ino; >> + s32 pid; >> +} __packed; >> + >> +static int pidfs_encode_fh(struct inode *inode, u32 *fh, int *max_len, >> + struct inode *parent) >> +{ >> + struct pid *pid = inode->i_private; >> + struct pidfd_fid *fid = (struct pidfd_fid *)fh; >> + >> + if (*max_len < PIDFD_FID_LEN) { >> + *max_len = PIDFD_FID_LEN; >> + return FILEID_INVALID; >> + } >> + >> + fid->ino = pid->ino; >> + fid->pid = pid_nr(pid); >> + *max_len = PIDFD_FID_LEN; >> + return FILEID_INO64_GEN; >> +} >> + >> +static struct dentry *pidfs_fh_to_dentry(struct super_block *sb, >> + struct fid *gen_fid, >> + int fh_len, int fh_type) >> +{ >> + int ret; >> + struct path path; >> + struct pidfd_fid *fid = (struct pidfd_fid *)gen_fid; >> + struct pid *pid; >> + >> + if (fh_type != FILEID_INO64_GEN || fh_len < PIDFD_FID_LEN) >> + return NULL; >> + >> + scoped_guard(rcu) { >> + pid = find_pid_ns(fid->pid, &init_pid_ns); >> + if (!pid || pid->ino != fid->ino || pid_vnr(pid) == 0) >> + return NULL; >> + >> + pid = get_pid(pid); >> + } >> + >> + ret = path_from_stashed(&pid->stashed, pidfs_mnt, pid, &path); >> + if (ret < 0) >> + return ERR_PTR(ret); > How come no need to put_pid() in case of error? This one confused me at first too, but path_from_stashed frees it (via stashed_ops.put_data) on error. You can see the same pattern in pidfs_alloc_file. (It already needs to know how to free it for the case where a stashed dentry already exists) >> + >> + mntput(path.mnt); >> + return path.dentry; >> +} >> + >> +static const struct export_operations pidfs_export_operations = { >> + .encode_fh = pidfs_encode_fh, >> + .fh_to_dentry = pidfs_fh_to_dentry, >> + .flags = EXPORT_OP_UNRESTRICTED_OPEN, >> +}; >> + >> static int pidfs_init_inode(struct inode *inode, void *data) >> { >> inode->i_private = data; >> inode->i_flags |= S_PRIVATE; >> - inode->i_mode |= S_IRWXU; >> + inode->i_mode |= S_IRWXU | S_IRWXG | S_IRWXO; > This change is not explained. > Why is it here? open_by_handle_at eventually passes through the may_open permission check. The existing permissions only permits root to open them (since the owning uid & gid is 0). So, it was necessary to widen them to align with how pidfd_open works. If I stick with this approach (see [1]) I'll ensure to document this change better in the commit message. [1] https://lore.kernel.org/all/6a3ed633-311d-47ff-8a7e-5121d6186139@e43.eu/
On Tue, Nov 12, 2024 at 11:43:13PM +0100, Erin Shepherd wrote: > On 12/11/2024 14:57, Jeff Layton wrote: > > On Tue, 2024-11-12 at 14:10 +0100, Christian Brauner wrote: > > We should really just move to storing 64-bit inode numbers internally > > on 32-bit machines. That would at least make statx() give you all 64 > > bits on 32-bit host. > > I think that would be ideal from the perspective of exposing it to > userspace. > It does leave the question of going back from inode to pidfd unsolved > though.I like the name_to_handle_at/open_by_handle_at approach because Indeed it doesn't solve it because it's possible that a given struct pid never had a pidfd created for it and thus no inode actually does exist. So when you're decoding a pidfs file handle you need to go to a struct pid based on some property. The pid is fine for that and it is equivalen to how pidfd_open() works. > it neatly solves both sides of the problem with APIs we already have and > understand > > > Hmm... I guess pid namespaces don't have a convenient 64-bit ID like > > mount namespaces do? In that case, stashing the pid from init_ns is > > probably the next best thing. > > Not that I could identify, no; so stashing the PID seemed like the most > pragmatic > approach. > > I'm not 100% sure it should be a documented property of the file handle > format; I > somewhat think that everything after the PID inode should be opaque to > userspace > and subject to change in the future (to the point I considered xoring it > with a > magic constant to make it less obvious to userspace/make it more obvious > that its > not to be relied upon; but that to my knowledge is not something that > the kernel > has done elsewhere). > > - Erin >
On Tue, Nov 12, 2024 at 11:43:13PM +0100, Erin Shepherd wrote: > On 12/11/2024 14:57, Jeff Layton wrote: > > On Tue, 2024-11-12 at 14:10 +0100, Christian Brauner wrote: > > We should really just move to storing 64-bit inode numbers internally > > on 32-bit machines. That would at least make statx() give you all 64 > > bits on 32-bit host. > > I think that would be ideal from the perspective of exposing it to > userspace. > It does leave the question of going back from inode to pidfd unsolved > though.I like the name_to_handle_at/open_by_handle_at approach because > it neatly solves both sides of the problem with APIs we already have and > understand > > > Hmm... I guess pid namespaces don't have a convenient 64-bit ID like > > mount namespaces do? In that case, stashing the pid from init_ns is > > probably the next best thing. > > Not that I could identify, no; so stashing the PID seemed like the most > pragmatic > approach. > > I'm not 100% sure it should be a documented property of the file > handle format; I somewhat think that everything after the PID inode > should be opaque to userspace and subject to change in the future (to > the point I considered xoring it with a magic constant to make it less > obvious to userspace/make it more obvious that its not to be relied > upon; but that to my knowledge is not something that the kernel has > done elsewhere). It's a handle, the internal details of its layout of it is supposed to be opaque to userspace. I wonder how well userspace deals with weirdly sized handles though... --D > - Erin > >
© 2016 - 2024 Red Hat, Inc.