It turns out overlayfs is actually okay wrt. mutliple conversions, because
it uses the right context for lower operations. I.e. before calling
vfs_{set,get}xattr() on underlying fs, it overrides creds with that of the
mounter, so the current user ns will now match that of
overlay_sb->s_user_ns, meaning that the caps will be converted to just the
right format for the next layer
OTOH ecryptfs, which is the only other one affected by commit 7c03e2cda4a5
("vfs: move cap_convert_nscap() call into vfs_setxattr()") needs to be
fixed up, since it doesn't do the cap override thing that overlayfs does.
I don't have an ecryptfs setup, so untested, but it's a fairly trivial
change.
My other observation was that cap_inode_getsecurity() messes up conversion
of caps in more than one case. This is independent of the overlayfs user
ns enablement but affects it as well.
Maybe we can revisit the infrastructure improvements we discussed, but I
think these fixes are more appropriate for the current cycle.
Thanks,
Miklos
Miklos Szeredi (2):
ecryptfs: fix uid translation for setxattr on security.capability
security.capability: fix conversions on getxattr
fs/ecryptfs/inode.c | 10 +++++--
security/commoncap.c | 67 ++++++++++++++++++++++++++++----------------
2 files changed, 50 insertions(+), 27 deletions(-)
--
2.26.2
Prior to commit 7c03e2cda4a5 ("vfs: move cap_convert_nscap() call into
vfs_setxattr()") the translation of nscap->rootid did not take stacked
filesystems (overlayfs and ecryptfs) into account.
That patch fixed the overlay case, but made the ecryptfs case worse.
Restore old the behavior for ecryptfs that existed before the overlayfs
fix. This does not fix ecryptfs's handling of complex user namespace
setups, but it does make sure existing setups don't regress.
Reported-by: Eric W. Biederman <redacted>
Cc: Tyler Hicks <code@tyhicks.com>
Fixes: 7c03e2cda4a5 ("vfs: move cap_convert_nscap() call into vfs_setxattr()")
Signed-off-by: Miklos Szeredi <redacted>
---
fs/ecryptfs/inode.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
If a capability is stored on disk in v2 format cap_inode_getsecurity() will
currently return in v2 format unconditionally.
This is wrong: v2 cap should be equivalent to a v3 cap with zero rootid,
and so the same conversions performed on it.
If the rootid cannot be mapped v3 is returned unconverted. Fix this so
that both v2 and v3 return -EOVERFLOW if the rootid (or the owner of the fs
user namespace in case of v2) cannot be mapped in the current user
namespace.
Signed-off-by: Miklos Szeredi <redacted>
---
security/commoncap.c | 67 ++++++++++++++++++++++++++++----------------
1 file changed, 43 insertions(+), 24 deletions(-)
@@ -396,46 +397,61 @@ int cap_inode_getsecurity(struct inode *inode, const char *name, void **buffer,fs_ns=inode->i_sb->s_user_ns;cap=(structvfs_cap_data*)tmpbuf;if(is_v2header((size_t)ret,cap)){-/* If this is sizeof(vfs_cap_data) then we're ok with the-*on-diskvalue,soreturnthat.*/-if(alloc)-*buffer=tmpbuf;-else-kfree(tmpbuf);-returnret;-}elseif(!is_v3header((size_t)ret,cap)){-kfree(tmpbuf);-return-EINVAL;+root=0;+}elseif(is_v3header((size_t)ret,cap)){+nscap=(structvfs_ns_cap_data*)tmpbuf;+root=le32_to_cpu(nscap->rootid);+}else{+size=-EINVAL;+gotoout_free;}-nscap=(structvfs_ns_cap_data*)tmpbuf;-root=le32_to_cpu(nscap->rootid);kroot=make_kuid(fs_ns,root);/* If the root kuid maps to a valid uid in current ns, then return*thisasanscap.*/mappedroot=from_kuid(current_user_ns(),kroot);if(mappedroot!=(uid_t)-1&&mappedroot!=(uid_t)0){+size=sizeof(structvfs_ns_cap_data);if(alloc){-*buffer=tmpbuf;+if(!nscap){+/* v2 -> v3 conversion */+nscap=kzalloc(size,GFP_ATOMIC);+if(!nscap){+size=-ENOMEM;+gotoout_free;+}+nsmagic=VFS_CAP_REVISION_3;+magic=le32_to_cpu(cap->magic_etc);+if(magic&VFS_CAP_FLAGS_EFFECTIVE)+nsmagic|=VFS_CAP_FLAGS_EFFECTIVE;+memcpy(&nscap->data,&cap->data,sizeof(__le32)*2*VFS_CAP_U32);+nscap->magic_etc=cpu_to_le32(nsmagic);+}else{+/* use allocated v3 buffer */+tmpbuf=NULL;+}nscap->rootid=cpu_to_le32(mappedroot);-}else-kfree(tmpbuf);-returnsize;+*buffer=nscap;+}+gotoout_free;}if(!rootid_owns_currentns(kroot)){-kfree(tmpbuf);-return-EOPNOTSUPP;+size=-EOVERFLOW;+gotoout_free;}/* This comes from a parent namespace. Return as a v2 capability */size=sizeof(structvfs_cap_data);if(alloc){-*buffer=kmalloc(size,GFP_ATOMIC);-if(*buffer){-structvfs_cap_data*cap=*buffer;-__le32nsmagic,magic;+if(nscap){+/* v3 -> v2 conversion */+cap=kzalloc(size,GFP_ATOMIC);+if(!cap){+size=-ENOMEM;+gotoout_free;+}magic=VFS_CAP_REVISION_2;nsmagic=le32_to_cpu(nscap->magic_etc);if(nsmagic&VFS_CAP_FLAGS_EFFECTIVE)
@@ -443,9 +459,12 @@ int cap_inode_getsecurity(struct inode *inode, const char *name, void **buffer,memcpy(&cap->data,&nscap->data,sizeof(__le32)*2*VFS_CAP_U32);cap->magic_etc=cpu_to_le32(magic);}else{-size=-ENOMEM;+/* use unconverted v2 */+tmpbuf=NULL;}+*buffer=cap;}+out_free:kfree(tmpbuf);returnsize;}
From: kernel test robot <hidden> Date: 2021-01-20 19:42:05
Hi Miklos,
I love your patch! Perhaps something to improve:
[auto build test WARNING on security/next-testing]
[also build test WARNING on linux/master linus/master v5.11-rc4 next-20210120]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]
url: https://github.com/0day-ci/linux/commits/Miklos-Szeredi/capability-conversion-fixes/20210120-152933
base: https://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security.git next-testing
config: xtensa-randconfig-s032-20210120 (attached as .config)
compiler: xtensa-linux-gcc (GCC) 9.3.0
reproduce:
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# apt-get install sparse
# sparse version: v0.6.3-208-g46a52ca4-dirty
# https://github.com/0day-ci/linux/commit/bcf70adf8bcc3e52cb1b262ae2e1d9154da75097
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Miklos-Szeredi/capability-conversion-fixes/20210120-152933
git checkout bcf70adf8bcc3e52cb1b262ae2e1d9154da75097
# save the attached .config to linux build tree
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' ARCH=xtensa
If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <redacted>
"sparse warnings: (new ones prefixed by >>)"
security/commoncap.c:424:41: sparse: sparse: incorrect type in assignment (different base types) @@ expected restricted __le32 [usertype] nsmagic @@ got int @@
security/commoncap.c:424:41: sparse: expected restricted __le32 [usertype] nsmagic
security/commoncap.c:424:41: sparse: got int
quoted
security/commoncap.c:425:39: sparse: sparse: incorrect type in assignment (different base types) @@ expected restricted __le32 [usertype] magic @@ got unsigned int @@
security/commoncap.c:425:39: sparse: expected restricted __le32 [usertype] magic
security/commoncap.c:425:39: sparse: got unsigned int
security/commoncap.c:426:37: sparse: sparse: restricted __le32 degrades to integer
security/commoncap.c:427:49: sparse: sparse: invalid assignment: |=
security/commoncap.c:427:49: sparse: left side has type restricted __le32
security/commoncap.c:427:49: sparse: right side has type int
security/commoncap.c:429:52: sparse: sparse: cast from restricted __le32
quoted
security/commoncap.c:429:52: sparse: sparse: incorrect type in argument 1 (different base types) @@ expected unsigned int [usertype] val @@ got restricted __le32 [usertype] nsmagic @@
security/commoncap.c:429:52: sparse: expected unsigned int [usertype] val
security/commoncap.c:429:52: sparse: got restricted __le32 [usertype] nsmagic
security/commoncap.c:429:52: sparse: sparse: cast from restricted __le32
security/commoncap.c:429:52: sparse: sparse: cast from restricted __le32
security/commoncap.c:429:52: sparse: sparse: cast from restricted __le32
security/commoncap.c:429:52: sparse: sparse: cast from restricted __le32
security/commoncap.c:455:31: sparse: sparse: incorrect type in assignment (different base types) @@ expected restricted __le32 [usertype] magic @@ got int @@
security/commoncap.c:455:31: sparse: expected restricted __le32 [usertype] magic
security/commoncap.c:455:31: sparse: got int
security/commoncap.c:456:33: sparse: sparse: incorrect type in assignment (different base types) @@ expected restricted __le32 [usertype] nsmagic @@ got unsigned int @@
security/commoncap.c:456:33: sparse: expected restricted __le32 [usertype] nsmagic
security/commoncap.c:456:33: sparse: got unsigned int
security/commoncap.c:457:29: sparse: sparse: restricted __le32 degrades to integer
security/commoncap.c:458:39: sparse: sparse: invalid assignment: |=
security/commoncap.c:458:39: sparse: left side has type restricted __le32
security/commoncap.c:458:39: sparse: right side has type int
security/commoncap.c:460:42: sparse: sparse: cast from restricted __le32
security/commoncap.c:460:42: sparse: sparse: incorrect type in argument 1 (different base types) @@ expected unsigned int [usertype] val @@ got restricted __le32 [usertype] magic @@
security/commoncap.c:460:42: sparse: expected unsigned int [usertype] val
security/commoncap.c:460:42: sparse: got restricted __le32 [usertype] magic
security/commoncap.c:460:42: sparse: sparse: cast from restricted __le32
security/commoncap.c:460:42: sparse: sparse: cast from restricted __le32
security/commoncap.c:460:42: sparse: sparse: cast from restricted __le32
security/commoncap.c:460:42: sparse: sparse: cast from restricted __le32
security/commoncap.c:1281:41: sparse: sparse: dubious: !x | y
vim +425 security/commoncap.c
357
358 /*
359 * getsecurity: We are called for security.* before any attempt to read the
360 * xattr from the inode itself.
361 *
362 * This gives us a chance to read the on-disk value and convert it. If we
363 * return -EOPNOTSUPP, then vfs_getxattr() will call the i_op handler.
364 *
365 * Note we are not called by vfs_getxattr_alloc(), but that is only called
366 * by the integrity subsystem, which really wants the unconverted values -
367 * so that's good.
368 */
369 int cap_inode_getsecurity(struct inode *inode, const char *name, void **buffer,
370 bool alloc)
371 {
372 int size, ret;
373 kuid_t kroot;
374 __le32 nsmagic, magic;
375 uid_t root, mappedroot;
376 char *tmpbuf = NULL;
377 struct vfs_cap_data *cap;
378 struct vfs_ns_cap_data *nscap = NULL;
379 struct dentry *dentry;
380 struct user_namespace *fs_ns;
381
382 if (strcmp(name, "capability") != 0)
383 return -EOPNOTSUPP;
384
385 dentry = d_find_any_alias(inode);
386 if (!dentry)
387 return -EINVAL;
388
389 size = sizeof(struct vfs_ns_cap_data);
390 ret = (int) vfs_getxattr_alloc(dentry, XATTR_NAME_CAPS,
391 &tmpbuf, size, GFP_NOFS);
392 dput(dentry);
393
394 if (ret < 0)
395 return ret;
396
397 fs_ns = inode->i_sb->s_user_ns;
398 cap = (struct vfs_cap_data *) tmpbuf;
399 if (is_v2header((size_t) ret, cap)) {
400 root = 0;
401 } else if (is_v3header((size_t) ret, cap)) {
402 nscap = (struct vfs_ns_cap_data *) tmpbuf;
403 root = le32_to_cpu(nscap->rootid);
404 } else {
405 size = -EINVAL;
406 goto out_free;
407 }
408
409 kroot = make_kuid(fs_ns, root);
410
411 /* If the root kuid maps to a valid uid in current ns, then return
412 * this as a nscap. */
413 mappedroot = from_kuid(current_user_ns(), kroot);
414 if (mappedroot != (uid_t)-1 && mappedroot != (uid_t)0) {
415 size = sizeof(struct vfs_ns_cap_data);
416 if (alloc) {
417 if (!nscap) {
418 /* v2 -> v3 conversion */
419 nscap = kzalloc(size, GFP_ATOMIC);
420 if (!nscap) {
421 size = -ENOMEM;
422 goto out_free;
423 }
424 nsmagic = VFS_CAP_REVISION_3;
> 425 magic = le32_to_cpu(cap->magic_etc);
426 if (magic & VFS_CAP_FLAGS_EFFECTIVE)
427 nsmagic |= VFS_CAP_FLAGS_EFFECTIVE;
428 memcpy(&nscap->data, &cap->data, sizeof(__le32) * 2 * VFS_CAP_U32);
> 429 nscap->magic_etc = cpu_to_le32(nsmagic);
430 } else {
431 /* use allocated v3 buffer */
432 tmpbuf = NULL;
433 }
434 nscap->rootid = cpu_to_le32(mappedroot);
435 *buffer = nscap;
436 }
437 goto out_free;
438 }
439
440 if (!rootid_owns_currentns(kroot)) {
441 size = -EOVERFLOW;
442 goto out_free;
443 }
444
445 /* This comes from a parent namespace. Return as a v2 capability */
446 size = sizeof(struct vfs_cap_data);
447 if (alloc) {
448 if (nscap) {
449 /* v3 -> v2 conversion */
450 cap = kzalloc(size, GFP_ATOMIC);
451 if (!cap) {
452 size = -ENOMEM;
453 goto out_free;
454 }
455 magic = VFS_CAP_REVISION_2;
456 nsmagic = le32_to_cpu(nscap->magic_etc);
457 if (nsmagic & VFS_CAP_FLAGS_EFFECTIVE)
458 magic |= VFS_CAP_FLAGS_EFFECTIVE;
459 memcpy(&cap->data, &nscap->data, sizeof(__le32) * 2 * VFS_CAP_U32);
460 cap->magic_etc = cpu_to_le32(magic);
461 } else {
462 /* use unconverted v2 */
463 tmpbuf = NULL;
464 }
465 *buffer = cap;
466 }
467 out_free:
468 kfree(tmpbuf);
469 return size;
470 }
471
---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
security/commoncap.c:424:41: sparse: sparse: incorrect type in assignment (different base types) @@ expected restricted __le32 [usertype] nsmagic @@ got int @@
Prior to commit 7c03e2cda4a5 ("vfs: move cap_convert_nscap() call into
vfs_setxattr()") the translation of nscap->rootid did not take stacked
filesystems (overlayfs and ecryptfs) into account.
That patch fixed the overlay case, but made the ecryptfs case worse.
Thanks for sending a fix!
I know that you don't have an eCryptfs setup to test with but I'm at a
loss about how to test this from the userns/fscaps side of things. Do
you have a sequence of unshare/setcap/getcap commands that I can run on
a file inside of an eCryptfs mount to verify that the bug exists after
7c03e2cda4a5 and then again to verify that this patch fixes the bug?
Tyler
quoted hunk
Restore old the behavior for ecryptfs that existed before the overlayfs
fix. This does not fix ecryptfs's handling of complex user namespace
setups, but it does make sure existing setups don't regress.
Reported-by: Eric W. Biederman <redacted>
Cc: Tyler Hicks <code@tyhicks.com>
Fixes: 7c03e2cda4a5 ("vfs: move cap_convert_nscap() call into vfs_setxattr()")
Signed-off-by: Miklos Szeredi <redacted>
---
fs/ecryptfs/inode.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
On Fri, Jan 22, 2021 at 7:31 PM Tyler Hicks [off-list ref] wrote:
On 2021-01-19 17:22:03, Miklos Szeredi wrote:
quoted
Prior to commit 7c03e2cda4a5 ("vfs: move cap_convert_nscap() call into
vfs_setxattr()") the translation of nscap->rootid did not take stacked
filesystems (overlayfs and ecryptfs) into account.
That patch fixed the overlay case, but made the ecryptfs case worse.
Thanks for sending a fix!
I know that you don't have an eCryptfs setup to test with but I'm at a
loss about how to test this from the userns/fscaps side of things. Do
you have a sequence of unshare/setcap/getcap commands that I can run on
a file inside of an eCryptfs mount to verify that the bug exists after
7c03e2cda4a5 and then again to verify that this patch fixes the bug?
You need two terminals:
$ = <USER>
# = root
$ unshare -Um
$ echo $$
<PID>
# echo "0 1000 1" > uid_map
# cp uid_map gid_map
# echo 1000 2000 1 >> uid_map
# echo 2000 3000 1 >> uid_map
# cat uid_map > /proc/<PID>/uid_map
# cat gid_map > /proc/<PID>/gid_map
$ mkdir ~/tmp ~/mnt
$ mount -t tmpfs tmpfs ~/tmp
$ pwd
/home/<USER>
# nsenter -t <PID> -m
# [setup ecryptfs on /home/<USER>/mnt using /home/<USER>/tmp]
$ cd ~/mnt
$ touch test
$ /sbin/setcap -n 1000 cap_dac_override+eip test
$ /sbin/getcap -n test
test = cap_dac_override+eip [rootid=1000]
Without the patch, I'm thinking that it will do a double translate and
end up with rootid=2000 in the user namespace, but I might well have
messed it up...
Let me know how this goes.
Thanks,
Miklos
On Mon, Jan 25, 2021 at 2:25 PM Miklos Szeredi [off-list ref] wrote:
On Fri, Jan 22, 2021 at 7:31 PM Tyler Hicks [off-list ref] wrote:
quoted
On 2021-01-19 17:22:03, Miklos Szeredi wrote:
quoted
Prior to commit 7c03e2cda4a5 ("vfs: move cap_convert_nscap() call into
vfs_setxattr()") the translation of nscap->rootid did not take stacked
filesystems (overlayfs and ecryptfs) into account.
That patch fixed the overlay case, but made the ecryptfs case worse.
Thanks for sending a fix!
I know that you don't have an eCryptfs setup to test with but I'm at a
loss about how to test this from the userns/fscaps side of things. Do
you have a sequence of unshare/setcap/getcap commands that I can run on
a file inside of an eCryptfs mount to verify that the bug exists after
7c03e2cda4a5 and then again to verify that this patch fixes the bug?
You need two terminals:
$ = <USER>
# = root
$ unshare -Um
$ echo $$
<PID>
# echo "0 1000 1" > uid_map
NOTE: <USER> is assumed to have uid=1000, so this and following
"1000" values need to be fixed up if it's not the case.
Thanks,
Miklos
On Fri, Jan 22, 2021 at 7:31 PM Tyler Hicks [off-list ref] wrote:
quoted
On 2021-01-19 17:22:03, Miklos Szeredi wrote:
quoted
Prior to commit 7c03e2cda4a5 ("vfs: move cap_convert_nscap() call into
vfs_setxattr()") the translation of nscap->rootid did not take stacked
filesystems (overlayfs and ecryptfs) into account.
That patch fixed the overlay case, but made the ecryptfs case worse.
Thanks for sending a fix!
I know that you don't have an eCryptfs setup to test with but I'm at a
loss about how to test this from the userns/fscaps side of things. Do
you have a sequence of unshare/setcap/getcap commands that I can run on
a file inside of an eCryptfs mount to verify that the bug exists after
7c03e2cda4a5 and then again to verify that this patch fixes the bug?
You need two terminals:
$ = <USER>
# = root
$ unshare -Um
$ echo $$
<PID>
# echo "0 1000 1" > uid_map
# cp uid_map gid_map
# echo 1000 2000 1 >> uid_map
# echo 2000 3000 1 >> uid_map
# cat uid_map > /proc/<PID>/uid_map
# cat gid_map > /proc/<PID>/gid_map
$ mkdir ~/tmp ~/mnt
$ mount -t tmpfs tmpfs ~/tmp
$ pwd
/home/<USER>
# nsenter -t <PID> -m
# [setup ecryptfs on /home/<USER>/mnt using /home/<USER>/tmp]
$ cd ~/mnt
$ touch test
$ /sbin/setcap -n 1000 cap_dac_override+eip test
$ /sbin/getcap -n test
test = cap_dac_override+eip [rootid=1000]
Without the patch, I'm thinking that it will do a double translate and
end up with rootid=2000 in the user namespace, but I might well have
messed it up...
Let me know how this goes.
Spot-on instructions. Thank you for taking the time to provide the
steps.
I was able to repro the bug and verify the fix. The change visually
looks good to me and it passed through the eCryptfs regression tests.
I've pushed it to the eCryptfs next branch and I plan to submit it to
Linus on Thursday. Thanks again!
Tyler