Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

btrfs: properly set the termination value of ctx->pos in readdir

The value of ctx->pos in the last readdir call is supposed to be set to
INT_MAX due to 32bit compatibility, unless 'pos' is intentially set to a
larger value, then it's LLONG_MAX.

There's a report from PaX SIZE_OVERFLOW plugin that "ctx->pos++"
overflows (https://forums.grsecurity.net/viewtopic.php?f=1&t=4284), on a
64bit arch, where the value is 0x7fffffffffffffff ie. LLONG_MAX before
the increment.

We can get to that situation like that:

* emit all regular readdir entries
* still in the same call to readdir, bump the last pos to INT_MAX
* next call to readdir will not emit any entries, but will reach the
bump code again, finds pos to be INT_MAX and sets it to LLONG_MAX

Normally this is not a problem, but if we call readdir again, we'll find
'pos' set to LLONG_MAX and the unconditional increment will overflow.

The report from Victor at
(http://thread.gmane.org/gmane.comp.file-systems.btrfs/49500) with debugging
print shows that pattern:

Overflow: e
Overflow: 7fffffff
Overflow: 7fffffffffffffff
PAX: size overflow detected in function btrfs_real_readdir
fs/btrfs/inode.c:5760 cicus.935_282 max, count: 9, decl: pos; num: 0;
context: dir_context;
CPU: 0 PID: 2630 Comm: polkitd Not tainted 4.2.3-grsec #1
Hardware name: Gigabyte Technology Co., Ltd. H81ND2H/H81ND2H, BIOS F3 08/11/2015
ffffffff81901608 0000000000000000 ffffffff819015e6 ffffc90004973d48
ffffffff81742f0f 0000000000000007 ffffffff81901608 ffffc90004973d78
ffffffff811cb706 0000000000000000 ffff8800d47359e0 ffffc90004973ed8
Call Trace:
[<ffffffff81742f0f>] dump_stack+0x4c/0x7f
[<ffffffff811cb706>] report_size_overflow+0x36/0x40
[<ffffffff812ef0bc>] btrfs_real_readdir+0x69c/0x6d0
[<ffffffff811dafc8>] iterate_dir+0xa8/0x150
[<ffffffff811e6d8d>] ? __fget_light+0x2d/0x70
[<ffffffff811dba3a>] SyS_getdents+0xba/0x1c0
Overflow: 1a
[<ffffffff811db070>] ? iterate_dir+0x150/0x150
[<ffffffff81749b69>] entry_SYSCALL_64_fastpath+0x12/0x83

The jump from 7fffffff to 7fffffffffffffff happens when new dir entries
are not yet synced and are processed from the delayed list. Then the code
could go to the bump section again even though it might not emit any new
dir entries from the delayed list.

The fix avoids entering the "bump" section again once we've finished
emitting the entries, both for synced and delayed entries.

References: https://forums.grsecurity.net/viewtopic.php?f=1&t=4284
Reported-by: Victor <services@swwu.com>
CC: stable@vger.kernel.org
Signed-off-by: David Sterba <dsterba@suse.com>
Tested-by: Holger Hoffstätte <holger.hoffstaette@googlemail.com>
Signed-off-by: Chris Mason <clm@fb.com>

authored by

David Sterba and committed by
Chris Mason
bc4ef759 43d871f0

+16 -3
+2 -1
fs/btrfs/delayed-inode.c
··· 1689 1689 * 1690 1690 */ 1691 1691 int btrfs_readdir_delayed_dir_index(struct dir_context *ctx, 1692 - struct list_head *ins_list) 1692 + struct list_head *ins_list, bool *emitted) 1693 1693 { 1694 1694 struct btrfs_dir_item *di; 1695 1695 struct btrfs_delayed_item *curr, *next; ··· 1733 1733 1734 1734 if (over) 1735 1735 return 1; 1736 + *emitted = true; 1736 1737 } 1737 1738 return 0; 1738 1739 }
+1 -1
fs/btrfs/delayed-inode.h
··· 144 144 int btrfs_should_delete_dir_index(struct list_head *del_list, 145 145 u64 index); 146 146 int btrfs_readdir_delayed_dir_index(struct dir_context *ctx, 147 - struct list_head *ins_list); 147 + struct list_head *ins_list, bool *emitted); 148 148 149 149 /* for init */ 150 150 int __init btrfs_delayed_inode_init(void);
+13 -1
fs/btrfs/inode.c
··· 5716 5716 char *name_ptr; 5717 5717 int name_len; 5718 5718 int is_curr = 0; /* ctx->pos points to the current index? */ 5719 + bool emitted; 5719 5720 5720 5721 /* FIXME, use a real flag for deciding about the key type */ 5721 5722 if (root->fs_info->tree_root == root) ··· 5745 5744 if (ret < 0) 5746 5745 goto err; 5747 5746 5747 + emitted = false; 5748 5748 while (1) { 5749 5749 leaf = path->nodes[0]; 5750 5750 slot = path->slots[0]; ··· 5825 5823 5826 5824 if (over) 5827 5825 goto nopos; 5826 + emitted = true; 5828 5827 di_len = btrfs_dir_name_len(leaf, di) + 5829 5828 btrfs_dir_data_len(leaf, di) + sizeof(*di); 5830 5829 di_cur += di_len; ··· 5838 5835 if (key_type == BTRFS_DIR_INDEX_KEY) { 5839 5836 if (is_curr) 5840 5837 ctx->pos++; 5841 - ret = btrfs_readdir_delayed_dir_index(ctx, &ins_list); 5838 + ret = btrfs_readdir_delayed_dir_index(ctx, &ins_list, &emitted); 5842 5839 if (ret) 5843 5840 goto nopos; 5844 5841 } 5842 + 5843 + /* 5844 + * If we haven't emitted any dir entry, we must not touch ctx->pos as 5845 + * it was was set to the termination value in previous call. We assume 5846 + * that "." and ".." were emitted if we reach this point and set the 5847 + * termination value as well for an empty directory. 5848 + */ 5849 + if (ctx->pos > 2 && !emitted) 5850 + goto nopos; 5845 5851 5846 5852 /* Reached end of directory/root. Bump pos past the last item. */ 5847 5853 ctx->pos++;