Analysis:
---
Kernel BUG:
Apr 21 11:39:31 web-12667 kernel: BUG: unable to handle kernel NULL
pointer dereference at 00000000000002c0
Apr 21 11:39:31 web-12667 kernel: IP: [<ffffffff8132ae16>]
fuse_readdir+0x376/0x700
...
Apr 21 11:39:31 web-12667 kernel: Oops: 0000 [#5] SMP
...
Apr 21 11:39:31 web-12667 kernel: CPU: 1 PID: 12133 Comm: php-fpm
Tainted: G D 4.4.0-1138-aws #152-Ubuntu
(an internal kern.log shows 7 of these, with consistent function+offset
and faulting address.)
Faulting source code line:
$ eu-addr2line -ifae vmlinux-4.4.0-1138-aws fuse_readdir+0x376
0xffffffff8132ae16
constant_test_bit inlined at
/build/linux-aws-8TZ6DM/linux-aws-4.4.0/fs/fuse/fuse_i.h:699 in fuse_readdir
/build/linux-aws-8TZ6DM/linux-aws-4.4.0/arch/x86/include/asm/bitops.h:311
fuse_is_bad
/build/linux-aws-8TZ6DM/linux-aws-4.4.0/fs/fuse/fuse_i.h:699
fuse_direntplus_link
/build/linux-aws-8TZ6DM/linux-aws-4.4.0/fs/fuse/dir.c:1267
parse_dirplusfile
/build/linux-aws-8TZ6DM/linux-aws-4.4.0/fs/fuse/dir.c:1342
fuse_readdir
/build/linux-aws-8TZ6DM/linux-aws-4.4.0/fs/fuse/dir.c:1392
Source code:
Issue: 'if (!inode)' doesn't prevent 'if (fuse_is_bad(inode)' from
running with 'inode == NULL'.
1194 static int fuse_direntplus_link(struct file *file,
1195 struct fuse_direntplus *direntplus,
1196 u64 attr_version)
1197 {
...
1240 if (dentry) {
1241 inode = d_inode(dentry);
1242 if (!inode) {
1243 d_drop(dentry);
1244 } else if ...
1246 ...
1247 } else if (is_bad_inode(inode)) {
1248 err = -EIO;
1249 goto out;
1250 } else {
1251 ...
1266 }
1267 if (fuse_is_bad(inode)) {
...
1272 }
697 static inline bool fuse_is_bad(struct inode *inode)
698 {
699 return unlikely(test_bit(FUSE_I_BAD,
&get_fuse_inode(inode)->state));
700 }
681 static inline struct fuse_inode *get_fuse_inode(struct inode *inode)
682 {
683 return container_of(inode, struct fuse_inode, inode);
684 }
Struct field offset (matches faulting address, i.e., NULL + 0x2c0)
$ pahole --hex -C fuse_inode vmlinux-4.4.0-1138-aws | grep state
long unsigned int state; /* 0x2c0 0x8
*/
The origin of the issue is in the backport to linux-xenial from linux-stable
4.9,
which comes from linux mainline.
- mainline:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=5d069dbe8aaf2a197142558b6fb2978189ba3454
- stable49:
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/fs/fuse/dir.c?h=v4.9.311&id=3a2f8823aa565cc67bdd00c4cd5e1d8ad81e8436
- backport:
https://git.launchpad.net/~canonical-kernel-esm/canonical-kernel-esm/+git/linux-xenial/commit/?h=master-prep&id=8deb786162e1e4cf73ae4c56d62b86f0d7c8ade0
In mainline there's no fuse_direntplus_link()
In stable/4.9, there's an '!inode' check w/ 'goto retry' that skips
'fuse_is_bad()'
-
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/fs/fuse/dir.c?h=v4.9.311&id=3a2f8823aa565cc67bdd00c4cd5e1d8ad81e8436#n1268
~/git/linux-stable$ git log --oneline origin/linux-4.9.y --
fs/fuse/dir.c | head -n1
3a2f8823aa56 fuse: fix bad inode
~/git/linux-stable$ git show -U10 3a2f8823aa56 | grep -A15
fuse_direntplus_link
@@ -1244,21 +1265,21 @@ static int fuse_direntplus_link(struct file
*file,
if (!d_in_lookup(dentry)) {
struct fuse_inode *fi;
inode = d_inode(dentry);
if (!inode ||
get_node_id(inode) != o->nodeid ||
((o->attr.mode ^ inode->i_mode) & S_IFMT)) {
d_invalidate(dentry);
dput(dentry);
goto retry;
}
- if (is_bad_inode(inode)) {
+ if (fuse_is_bad(inode)) {
dput(dentry);
return -EIO;
}
In xenial, there's no such 'goto' or another mechanism to skip
'fuse_is_bad(inode)' if '!inode' (above).
--
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/1970482
Title:
Xenial: kernel BUG/Oops/crash in fuse_readdir() due to CVE-2020-36322
backport
To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1970482/+subscriptions
--
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs