From 249636f3a78c38e45ee200034b1853cc6fa4db46 Mon Sep 17 00:00:00 2001 From: Paul Millar Date: Mon, 28 Mar 2022 14:14:40 +0200 Subject: [PATCH] pnfsmanager: avoid leaking whether or not file exists Motivation: The WebDAV door (among other doors) queries pnfsmanager to discover the PNFS-ID of a target. Within pnfsmanager, the NameSpaceProvider plugin is used to discover information about the target. When looking up the inode from a path, the Chimera plugin (ChimeraNameSpaceProvider) checks whether the user can 'cd' into the target's parent directory. To do this, it looks up the inode information for the target and all it's parents. Currently, if the target does not exist then this immediately throws FileNotFoundChimeraFsException. This is true even if the user does not have permission to 'cd' into a parent directory. Therefore, the user can discover whether a file exists within a directory that user cannot enter. Modification: Update ChimeraNameSpaceProvider so that, after discovering the target does not exist, it next checks whether the user can 'cd' into the deepest parent directory that exists. If the user does not have permission to 'cd' into this deepest parent directory then the FileNotFoundChimeraFsException is replaced with a PermissionDeniedCacheException. Result: Doors no longer allow a user to discover whether or not a file or directory exists within directories they cannot access. Target: master Request: 8.0 Request: 7.2 Request: 7.1 Request: 7.0 Request: 6.2 Requires-notes: yes Requires-book: no --- .../namespace/ChimeraNameSpaceProvider.java | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/modules/dcache-chimera/src/main/java/org/dcache/chimera/namespace/ChimeraNameSpaceProvider.java b/modules/dcache-chimera/src/main/java/org/dcache/chimera/namespace/ChimeraNameSpaceProvider.java index 90a77d5a0d7..937cc13bdd5 100644 --- a/modules/dcache-chimera/src/main/java/org/dcache/chimera/namespace/ChimeraNameSpaceProvider.java +++ b/modules/dcache-chimera/src/main/java/org/dcache/chimera/namespace/ChimeraNameSpaceProvider.java @@ -228,23 +228,51 @@ public void setUploadSubDirectory(String path) { _uploadSubDirectory = path; } + private void checkLookupPermissions(Subject subject, List inodes, String path) + throws ChimeraFsException, CacheException { + for (FsInode inode : inodes) { + if (inode.isDirectory()) { + FileAttributes attributes = getFileAttributesForPermissionHandler(inode); + if (_permissionHandler.canLookup(subject, attributes) != ACCESS_ALLOWED) { + throw new PermissionDeniedCacheException("Access denied: " + path); + } + } + } + } + + private List findDeepestParent(String path) throws ChimeraFsException { + FsPath target = FsPath.create(path); + + do { + target = target.parent(); + try { + return _fs.path2inodes(target.toString()); + } catch (FileNotFoundChimeraFsException ignored) { + // Continue onto next parent. + } + } while (target != FsPath.ROOT); + + throw new RuntimeException("Unable to find inode for ROOT"); + } + private ExtendedInode pathToInode(Subject subject, String path) throws ChimeraFsException, CacheException { if (Subjects.isExemptFromNamespaceChecks(subject)) { return new ExtendedInode(_fs, _fs.path2inode(path)); } - List inodes = _fs.path2inodes(path); + List inodes; + try { + inodes = _fs.path2inodes(path); + } catch (FileNotFoundChimeraFsException e) { + // Do not leak whether a file exists if user cannot 'cd' into the parent directory. + inodes = findDeepestParent(path); + checkLookupPermissions(subject, inodes, path); + throw e; + } + if (_verifyAllLookups) { - for (FsInode inode : inodes.subList(0, inodes.size() - 1)) { - if (inode.isDirectory()) { - FileAttributes attributes = - getFileAttributesForPermissionHandler(inode); - if (_permissionHandler.canLookup(subject, attributes) != ACCESS_ALLOWED) { - throw new PermissionDeniedCacheException("Access denied: " + path); - } - } - } + checkLookupPermissions(subject, inodes.subList(0, inodes.size() - 1), path); } else { for (FsInode inode : Iterables.skip(Lists.reverse(inodes), 1)) { if (inode.isDirectory()) {