On Tue, Mar 29, 2022 at 8:51 AM Mickaël Salaün <mic@xxxxxxxxxxx> wrote: > > From: Mickaël Salaün <mic@xxxxxxxxxxxxxxxxxxx> > > Add a new LANDLOCK_ACCESS_FS_REFER access right to enable policy writers > to allow sandboxed processes to link and rename files from and to a > specific set of file hierarchies. This access right should be composed > with LANDLOCK_ACCESS_FS_MAKE_* for the destination of a link or rename, > and with LANDLOCK_ACCESS_FS_REMOVE_* for a source of a rename. This > lift a Landlock limitation that always denied changing the parent of an > inode. > > Renaming or linking to the same directory is still always allowed, > whatever LANDLOCK_ACCESS_FS_REFER is used or not, because it is not > considered a threat to user data. > > However, creating multiple links or renaming to a different parent > directory may lead to privilege escalations if not handled properly. > Indeed, we must be sure that the source doesn't gain more privileges by > being accessible from the destination. This is handled by making sure > that the source hierarchy (including the referenced file or directory > itself) restricts at least as much the destination hierarchy. If it is > not the case, an EXDEV error is returned, making it potentially possible > for user space to copy the file hierarchy instead of moving or linking > it. > > Instead of creating different access rights for the source and the > destination, we choose to make it simple and consistent for users. > Indeed, considering the previous constraint, it would be weird to > require such destination access right to be also granted to the source > (to make it a superset). Moreover, RENAME_EXCHANGE would also add to > the confusion because of paths being both a source and a destination. > > See the provided documentation for additional details. > > New tests are provided with a following commit. > > Cc: Paul Moore <paul@xxxxxxxxxxxxxx> > Signed-off-by: Mickaël Salaün <mic@xxxxxxxxxxxxxxxxxxx> > Link: https://lore.kernel.org/r/20220329125117.1393824-8-mic@xxxxxxxxxxx > --- > > Changes since v1: > * Update current_check_access_path() to efficiently handle > RENAME_EXCHANGE thanks to the updated LSM hook (see previous patch). > Only one path walk is performed per rename arguments until their > common mount point is reached. Superset of access rights is correctly > checked, including when exchanging a file with a directory. This > requires to store another matrix of layer masks. > * Reorder and rename check_access_path_dual() arguments in a more > generic way: switch from src/dst to 1/2. This makes it easier to > understand the RENAME_EXCHANGE cases alongs with the others. Update > and improve check_access_path_dual() documentation accordingly. > * Clean up the check_access_path_dual() loop: set both allowed_parent* > when reaching internal filesystems and remove a useless one. This > allows potential renames in internal filesystems (like for other > operations). > * Move the function arguments checks from BUILD_BUG_ON() to > WARN_ON_ONCE() to avoid clang build error. > * Rename is_superset() to no_more_access() and make it handle superset > checks of source and destination for simple and exchange cases. > * Move the layer_masks_child* creation from current_check_refer_path() > to check_access_path_dual(): this is simpler and less error-prone, > especially with RENAME_EXCHANGE. > * Remove one optimization in current_check_refer_path() to make the code > simpler, especially with the RENAME_EXCHANGE handling. > * Remove overzealous WARN_ON_ONCE() for !access_request check in > init_layer_masks(). > --- > include/uapi/linux/landlock.h | 27 +- > security/landlock/fs.c | 607 ++++++++++++++++--- > security/landlock/limits.h | 2 +- > security/landlock/syscalls.c | 2 +- > tools/testing/selftests/landlock/base_test.c | 2 +- > tools/testing/selftests/landlock/fs_test.c | 3 +- > 6 files changed, 566 insertions(+), 77 deletions(-) I'm still not going to claim that I'm a Landlock expert, but this looks sane to me. Reviewed-by: Paul Moore <paul@xxxxxxxxxxxxxx> > +static inline access_mask_t init_layer_masks( > + const struct landlock_ruleset *const domain, > + const access_mask_t access_request, > + layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) > +{ > + access_mask_t handled_accesses = 0; > + size_t layer_level; > + > + memset(layer_masks, 0, sizeof(*layer_masks)); > + /* An empty access request can happen because of O_WRONLY | O_RDWR. */ ;) -- paul-moore.com