Message ID | 20240517-b4-sio-read_write-v3-1-f180df0a19e6@google.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | [v3] fs: fix unintentional arithmetic wraparound in offset calculation | expand |
On Fri, May 17, 2024 at 12:29:06AM +0000, Justin Stitt wrote: > When running syzkaller with the newly reintroduced signed integer > overflow sanitizer we encounter this report: why do you keep saying it's unintentional? it's clearly intended.
On Fri, May 17, 2024 at 02:13:22AM +0100, Matthew Wilcox wrote: > On Fri, May 17, 2024 at 12:29:06AM +0000, Justin Stitt wrote: > > When running syzkaller with the newly reintroduced signed integer > > overflow sanitizer we encounter this report: > > why do you keep saying it's unintentional? it's clearly intended. Because they are short on actual bugs to be found by their tooling and attempt to inflate the sound/noise rate; therefore, every time when overflow _IS_ handled correctly, it must have been an accident - we couldn't have possibly done the analysis correctly. And if somebody insists that they _are_ capable of basic math, they must be dishonest. So... "unintentional" it's going to be. <southpark> Math is hard, mmkay? </southpark> Al, more than slightly annoyed by that aspect of the entire thing...
On Fri, May 17, 2024 at 02:26:47AM +0100, Al Viro wrote: > On Fri, May 17, 2024 at 02:13:22AM +0100, Matthew Wilcox wrote: > > On Fri, May 17, 2024 at 12:29:06AM +0000, Justin Stitt wrote: > > > When running syzkaller with the newly reintroduced signed integer > > > overflow sanitizer we encounter this report: > > > > why do you keep saying it's unintentional? it's clearly intended. > > Because they are short on actual bugs to be found by their tooling > and attempt to inflate the sound/noise rate; therefore, every time > when overflow _IS_ handled correctly, it must have been an accident - > we couldn't have possibly done the analysis correctly. And if somebody > insists that they _are_ capable of basic math, they must be dishonest. > So... "unintentional" it's going to be. > > <southpark> Math is hard, mmkay? </southpark> > > Al, more than slightly annoyed by that aspect of the entire thing... Yes, some of the patches I've seen floating past actually seem nice, but the vast majority just seem like make-work. And the tone is definitely inappropriate.
Hi, On Thu, May 16, 2024 at 6:13 PM Matthew Wilcox <willy@infradead.org> wrote: > > On Fri, May 17, 2024 at 12:29:06AM +0000, Justin Stitt wrote: > > When running syzkaller with the newly reintroduced signed integer > > overflow sanitizer we encounter this report: > > why do you keep saying it's unintentional? it's clearly intended. Right, "unintentional" is a poor choice of phrasing. I actually mean: "overflow-checking arithmetic was done in a way that intrinsically causes an overflow (wraparound)". I can clearly see the intent of the code; there's even comments saying exactly what it does: "/* Ensure offsets don't wrap. */"... So the thinking is: let's use the overflow-checking helpers so we can get a good signal through the sanitizers on _real_ bugs, especially in spots with no bounds handling. Thanks Justin
On Fri, May 17, 2024 at 02:26:47AM +0100, Al Viro wrote: > On Fri, May 17, 2024 at 02:13:22AM +0100, Matthew Wilcox wrote: > > On Fri, May 17, 2024 at 12:29:06AM +0000, Justin Stitt wrote: > > > When running syzkaller with the newly reintroduced signed integer > > > overflow sanitizer we encounter this report: > > > > why do you keep saying it's unintentional? it's clearly intended. > > Because they are short on actual bugs to be found by their tooling > and attempt to inflate the sound/noise rate; therefore, every time "short on bugs"? We're trying to drive it to zero. I would *love* to be short on bugs. See my reply[1] to Ted. > when overflow _IS_ handled correctly, it must have been an accident - > we couldn't have possibly done the analysis correctly. And if somebody > insists that they _are_ capable of basic math, they must be dishonest. > So... "unintentional" it's going to be. As Justin said, this is a poor choice in wording. In other cases I've tried to describe this as making changes so that intent is unambiguous (to both a human and a compiler). > <southpark> Math is hard, mmkay? </southpark> > > Al, more than slightly annoyed by that aspect of the entire thing... I'm sorry about that. None of this is a commentary on code correctness; we're just trying to refactor things so that the compiler can help us catch the _unintended_ overflows. This one is _intended_, so here we are to find a palatable way to leave the behavior unchanged while gaining compiler coverage. -Kees [1] https://lore.kernel.org/linux-hardening/202405171329.019F2F566C@keescook/
On Fri 17-05-24 00:29:06, Justin Stitt wrote: > When running syzkaller with the newly reintroduced signed integer > overflow sanitizer we encounter this report: > > UBSAN: signed-integer-overflow in ../fs/read_write.c:91:10 > 9223372036854775807 + 4096 cannot be represented in type 'loff_t' (aka 'long long') > Call Trace: > <TASK> > dump_stack_lvl+0x93/0xd0 > handle_overflow+0x171/0x1b0 > generic_file_llseek_size+0x35b/0x380 > > ... amongst others: > UBSAN: signed-integer-overflow in ../fs/read_write.c:1657:12 > 142606336 - -9223372036854775807 cannot be represented in type 'loff_t' (aka 'long long') > ... > UBSAN: signed-integer-overflow in ../fs/read_write.c:1666:11 > 9223372036854775807 - -9223231299366420479 cannot be represented in type 'loff_t' (aka 'long long') > > Fix the accidental overflow in these position and offset calculations > by checking for negative position values, using check_add_overflow() > helpers and clamping values to expected ranges. > > Link: https://github.com/llvm/llvm-project/pull/82432 [1] > Closes: https://github.com/KSPP/linux/issues/358 > Cc: linux-hardening@vger.kernel.org > Signed-off-by: Justin Stitt <justinstitt@google.com> Except for the unfortunate wording in the changelog, the code actually looks easier to grasp to me and if it helps the compiler as well, I'm in favor of this change (but I definitely don't want to overrule Al if he hates it ;)). Regarding the code: > @@ -1467,8 +1470,8 @@ static int generic_copy_file_checks(struct file *file_in, loff_t pos_in, > > /* Don't allow overlapped copying within the same file. */ > if (inode_in == inode_out && > - pos_out + count > pos_in && > - pos_out < pos_in + count) > + out_sum > pos_in && > + pos_out < in_sum) > return -EINVAL; This is actually subtly wrong becaue 'count' could have been updated (shrinked) between the check_add_overflow() and this place. So please keep the old checks here. > @@ -1649,6 +1652,9 @@ int generic_write_check_limits(struct file *file, loff_t pos, loff_t *count) > loff_t max_size = inode->i_sb->s_maxbytes; > loff_t limit = rlimit(RLIMIT_FSIZE); > > + if (pos < 0) > + return -EINVAL; > + > if (limit != RLIM_INFINITY) { > if (pos >= limit) { > send_sig(SIGXFSZ, current, 0); Here I'm a bit confused. How is this related to the signed overflow handling? Honza
diff --git a/fs/read_write.c b/fs/read_write.c index d4c036e82b6c..8be30c8829a9 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -88,7 +88,8 @@ generic_file_llseek_size(struct file *file, loff_t offset, int whence, { switch (whence) { case SEEK_END: - offset += eof; + if (check_add_overflow(offset, eof, &offset)) + return -EINVAL; break; case SEEK_CUR: /* @@ -105,7 +106,9 @@ generic_file_llseek_size(struct file *file, loff_t offset, int whence, * like SEEK_SET. */ spin_lock(&file->f_lock); - offset = vfs_setpos(file, file->f_pos + offset, maxsize); + if (check_add_overflow(offset, file->f_pos, &offset)) + return -EINVAL; + offset = vfs_setpos(file, offset, maxsize); spin_unlock(&file->f_lock); return offset; case SEEK_DATA: @@ -1416,7 +1419,7 @@ static int generic_copy_file_checks(struct file *file_in, loff_t pos_in, struct inode *inode_in = file_inode(file_in); struct inode *inode_out = file_inode(file_out); uint64_t count = *req_count; - loff_t size_in; + loff_t size_in, in_sum, out_sum; int ret; ret = generic_file_rw_checks(file_in, file_out); @@ -1450,8 +1453,8 @@ static int generic_copy_file_checks(struct file *file_in, loff_t pos_in, if (IS_SWAPFILE(inode_in) || IS_SWAPFILE(inode_out)) return -ETXTBSY; - /* Ensure offsets don't wrap. */ - if (pos_in + count < pos_in || pos_out + count < pos_out) + if (check_add_overflow(pos_in, count, &in_sum) || + check_add_overflow(pos_out, count, &out_sum)) return -EOVERFLOW; /* Shorten the copy to EOF */ @@ -1467,8 +1470,8 @@ static int generic_copy_file_checks(struct file *file_in, loff_t pos_in, /* Don't allow overlapped copying within the same file. */ if (inode_in == inode_out && - pos_out + count > pos_in && - pos_out < pos_in + count) + out_sum > pos_in && + pos_out < in_sum) return -EINVAL; *req_count = count; @@ -1649,6 +1652,9 @@ int generic_write_check_limits(struct file *file, loff_t pos, loff_t *count) loff_t max_size = inode->i_sb->s_maxbytes; loff_t limit = rlimit(RLIMIT_FSIZE); + if (pos < 0) + return -EINVAL; + if (limit != RLIM_INFINITY) { if (pos >= limit) { send_sig(SIGXFSZ, current, 0); diff --git a/fs/remap_range.c b/fs/remap_range.c index de07f978ce3e..4570be4ef463 100644 --- a/fs/remap_range.c +++ b/fs/remap_range.c @@ -36,7 +36,7 @@ static int generic_remap_checks(struct file *file_in, loff_t pos_in, struct inode *inode_out = file_out->f_mapping->host; uint64_t count = *req_count; uint64_t bcount; - loff_t size_in, size_out; + loff_t size_in, size_out, in_sum, out_sum; loff_t bs = inode_out->i_sb->s_blocksize; int ret; @@ -44,17 +44,17 @@ static int generic_remap_checks(struct file *file_in, loff_t pos_in, if (!IS_ALIGNED(pos_in, bs) || !IS_ALIGNED(pos_out, bs)) return -EINVAL; - /* Ensure offsets don't wrap. */ - if (pos_in + count < pos_in || pos_out + count < pos_out) - return -EINVAL; + if (check_add_overflow(pos_in, count, &in_sum) || + check_add_overflow(pos_out, count, &out_sum)) + return -EOVERFLOW; size_in = i_size_read(inode_in); size_out = i_size_read(inode_out); /* Dedupe requires both ranges to be within EOF. */ if ((remap_flags & REMAP_FILE_DEDUP) && - (pos_in >= size_in || pos_in + count > size_in || - pos_out >= size_out || pos_out + count > size_out)) + (pos_in >= size_in || in_sum > size_in || + pos_out >= size_out || out_sum > size_out)) return -EINVAL; /* Ensure the infile range is within the infile. */
When running syzkaller with the newly reintroduced signed integer overflow sanitizer we encounter this report: UBSAN: signed-integer-overflow in ../fs/read_write.c:91:10 9223372036854775807 + 4096 cannot be represented in type 'loff_t' (aka 'long long') Call Trace: <TASK> dump_stack_lvl+0x93/0xd0 handle_overflow+0x171/0x1b0 generic_file_llseek_size+0x35b/0x380 ... amongst others: UBSAN: signed-integer-overflow in ../fs/read_write.c:1657:12 142606336 - -9223372036854775807 cannot be represented in type 'loff_t' (aka 'long long') ... UBSAN: signed-integer-overflow in ../fs/read_write.c:1666:11 9223372036854775807 - -9223231299366420479 cannot be represented in type 'loff_t' (aka 'long long') Fix the accidental overflow in these position and offset calculations by checking for negative position values, using check_add_overflow() helpers and clamping values to expected ranges. Link: https://github.com/llvm/llvm-project/pull/82432 [1] Closes: https://github.com/KSPP/linux/issues/358 Cc: linux-hardening@vger.kernel.org Signed-off-by: Justin Stitt <justinstitt@google.com> --- Changes in v3: - use check_add_overflow() instead of min() to keep old -EINVAL behavior (thanks Jan) - shorten UBSAN splat in commit log, reword commit log - Link to v2: https://lore.kernel.org/r/20240509-b4-sio-read_write-v2-1-018fc1e63392@google.com Changes in v2: - fix some more cases syzkaller found in read_write.c - use min over min_t as the types are the same - Link to v1: https://lore.kernel.org/r/20240509-b4-sio-read_write-v1-1-06bec2022697@google.com --- Historically, the signed integer overflow sanitizer did not work in the kernel due to its interaction with `-fwrapv` but this has since been changed [1] in the newest version of Clang. It was re-enabled in the kernel with Commit 557f8c582a9ba8ab ("ubsan: Reintroduce signed overflow sanitizer"). Here's the syzkaller reproducer: | # {Threaded:false Repeat:false RepeatTimes:0 Procs:1 Slowdown:1 Sandbox: | # SandboxArg:0 Leak:false NetInjection:false NetDevices:false | # NetReset:false Cgroups:false BinfmtMisc:false CloseFDs:false KCSAN:false | # DevlinkPCI:false NicVF:false USB:false VhciInjection:false Wifi:false | # IEEE802154:false Sysctl:false Swap:false UseTmpDir:false | # HandleSegv:false Repro:false Trace:false LegacyOptions:{Collide:false | # Fault:false FaultCall:0 FaultNth:0}} | r0 = openat$sysfs(0xffffffffffffff9c, &(0x7f0000000000)='/sys/kernel/address_bits', 0x0, 0x98) | lseek(r0, 0x7fffffffffffffff, 0x2) ... which was used against Kees' tree here (v6.8rc2): https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git/log/?h=wip/v6.9-rc2/unsigned-overflow-sanitizer ... with this config: https://gist.github.com/JustinStitt/824976568b0f228ccbcbe49f3dee9bf4 --- fs/read_write.c | 20 +++++++++++++------- fs/remap_range.c | 12 ++++++------ 2 files changed, 19 insertions(+), 13 deletions(-) --- base-commit: 0106679839f7c69632b3b9833c3268c316c0a9fc change-id: 20240509-b4-sio-read_write-04a17d40620e Best regards, -- Justin Stitt <justinstitt@google.com>