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)

overlayfs基本结构

其中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)
/* audit system wants to get cap info from files as well */
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 /* !_LINUX_CAPABILITY_H */
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)