CVE-2021-3493漏洞分析
漏洞描述
Linux内核中overlayfs文件系统中的Ubuntu特定问题,在该问题中,它未正确验证关于用户名称空间的文件系统功能的应用程序。由于Ubuntu附带了一个允许非特权的overlayfs挂载的补丁,因此本地攻击者可以使用它来获得更高的特权。
影响版本
- Ubuntu 20.10
- Ubuntu 20.04 LTS
- Ubuntu 18.04 LTS
- Ubuntu 16.04 LTS
- Ubuntu 14.04 ESM
前置知识
overlayfs
Overlayfs是一种类似aufs的一种堆叠文件系统,它依赖并建立在其它的文件系统上(如ext4fs和xfs等),并不直接参与磁盘空间结构的划分,仅仅将原来底层文件系统中不同的目录进行合并,然后向用户呈现,因此对于用户来说,它所见到的overlay文件系统根目录下的内容就来自挂载时所指定的不同目录的合集。(图源自参考文章2)
其中lower dirA / lower dirB目录和upper dir目录为来自底层文件系统的不同目录,用户可以自行指定,内部包含了用户想要合并的文件和目录,merge dir目录为挂载点。当文件系统挂载后,在merge目录下将会同时看到来自各lower和upper目录下的内容,并且用户也无法(无需)感知这些文件分别哪些来自lower dir,哪些来自upper dir,用户看见的只是一个普通的文件系统根目录而已。
但这几个不同的目录不完全等价,upper目录会覆盖掉lower目录的同名文件,而同一层的lower文件中,较上层的也会屏蔽较下层的同名文件。并且upper文件是可读写的,写入merge文件(来自upper挂载的文件)数据时会直接将其写入到对应文件;而lower文件是只读的,无论如何对merge文件(来自lower挂载的文件)进行修改,原文件都不会变(写操作时会将该文件拷贝到upper中)。另外,挂载后就不允许对原lower和upper进行操作。
总的来说是以下三点:
- 上下层同名目录合并
- 上下层同名文件覆盖
- lower dir文件写时拷贝
linux capability控制权
capability允许了普通用户拥有超级用户的权限。与sudo不同,capability可以分开赋予某种特定的权限,且不需要用到超级用户;而sudo则是所有权限都具有,并且需要用到超级用户。
user namespaces
用于对用户和用户组进行隔离。/proc/PID/uid_map
和/proc/PID/gid_map
文件中是关于映射设置的信息,其内容为三组数字:first-ns-id first-target-id count
。
- first-ns-id:是给定进程的namespace中第一个合法的ID,常被设置为 0 ,即就是root的ID,这个ID在user namespace中是合法的
- first-target-id:是父进程(宿主机)命令空间中的真实ID,first-ns-id会被映射到宿主机中的first-target-id
- count:表示映射的范围,为 1 表示只映射一个,大于1表示按顺序映射
linux文件系统的扩展属性
xattr 是文件扩展属性全称是一种以key-value 保存数据到文件系统中的技术。xattr从功能上分为四类user/trusted/system/security。 这四类中system用于保存acl,security 用于支持selinux,user/trusted 提供给用户保存进程的设置。
在os中,每一个文件系统都对应一组setxattr和getxattr命令用于读写xattr。
setxattr函数最终会通过vfs调用各个文件系统实现的setxattr来设置其扩展属性。
调用链
setxattr
vfs_setxattr
security_inode_setxattr
cap_inode_setxattr
ns_capable
__vfs_setxattr_noperm
__vfs_setxattr
ovl_xattr_set
cap_convert_nscap
Patch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| diff --git a/fs/xattr.c b/fs/xattr.c index cd7a563e8bcd4..fd57153b1f617 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -276,8 +276,16 @@ vfs_setxattr(struct dentry *dentry, const char *name, const void *value, { struct inode *inode = dentry->d_inode; struct inode *delegated_inode = NULL; + const void *orig_value = value; int error; + if (size && strcmp(name, XATTR_NAME_CAPS) == 0) { + error = cap_convert_nscap(dentry, &value, size); + if (error < 0) + return error; + size = error; + } + retry_deleg: inode_lock(inode); error = __vfs_setxattr_locked(dentry, name, value, size, flags, @@ -289,6 +297,9 @@ retry_deleg: if (!error) goto retry_deleg; } + if (value != orig_value) + kfree(value); + return error; } EXPORT_SYMBOL_GPL(vfs_setxattr); @@ -537,12 +548,6 @@ setxattr(struct dentry *d, const char __user *name, const void __user *value, if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) || (strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0)) posix_acl_fix_xattr_from_user(kvalue, size); - else if (strcmp(kname, XATTR_NAME_CAPS) == 0) { - error = cap_convert_nscap(d, &kvalue, size); - if (error < 0) - goto out; - size = error; - } } error = vfs_setxattr(d, kname, kvalue, size, flags); diff --git a/include/linux/capability.h b/include/linux/capability.h index 1e7fe311cabe3..b2f698915c0f3 100644 --- a/include/linux/capability.h +++ b/include/linux/capability.h @@ -270,6 +270,6 @@ static inline bool checkpoint_restore_ns_capable(struct user_namespace *ns) extern int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps); -extern int cap_convert_nscap(struct dentry *dentry, void **ivalue, size_t size); +extern int cap_convert_nscap(struct dentry *dentry, const void **ivalue, size_t size); #endif diff --git a/security/commoncap.c b/security/commoncap.c index 59bf3c1674c8b..bacc1111d871b 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -473,7 +473,7 @@ static bool validheader(size_t size, const struct vfs_cap_data *cap) * * If all is ok, we return the new size, on error return < 0. */ -int cap_convert_nscap(struct dentry *dentry, void **ivalue, size_t size) +int cap_convert_nscap(struct dentry *dentry, const void **ivalue, size_t size) { struct vfs_ns_cap_data *nscap; uid_t nsrootid; @@ -516,7 +516,6 @@ int cap_convert_nscap(struct dentry *dentry, void **ivalue, size_t size) nscap->magic_etc = cpu_to_le32(nsmagic); memcpy(&nscap->data, &cap->data, sizeof(__le32) * 2 * VFS_CAP_U32); - kvfree(*ivalue); *ivalue = nscap; return newsize; }
|
漏洞利用思路
总结下exp的利用思路为如下几点:
修改user namespaces,使其在另一命名空间下具有root权限,从而可以挂载overlay。
利用overlay拷贝出exp文件,并且因为当前命名空间下为超级权限,可以直接设置capability,从而赋予权限。
运行exp,提权成功。
参考文章
CVE-2021-3493 Linux kernel提权漏洞复现
深入理解overlayfs(一):初识
namespaces之 User Namespace机制
Linux自主访问控制机制模块详细分析之文件系统的扩展属性
linux 中文件系统的扩展属性
Ubuntu内核OverlayFS权限逃逸漏洞分析(CVE-2021-3493)