| From: Eric Biggers <ebiggers@google.com> |
| Date: Fri, 13 Jul 2018 16:59:27 -0700 |
| Subject: reiserfs: fix buffer overflow with long warning messages |
| |
| commit fe10e398e860955bac4d28ec031b701d358465e4 upstream. |
| |
| ReiserFS prepares log messages into a 1024-byte buffer with no bounds |
| checks. Long messages, such as the "unknown mount option" warning when |
| userspace passes a crafted mount options string, overflow this buffer. |
| This causes KASAN to report a global-out-of-bounds write. |
| |
| Fix it by truncating messages to the buffer size. |
| |
| Link: http://lkml.kernel.org/r/20180707203621.30922-1-ebiggers3@gmail.com |
| Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") |
| Reported-by: syzbot+b890b3335a4d8c608963@syzkaller.appspotmail.com |
| Signed-off-by: Eric Biggers <ebiggers@google.com> |
| Reviewed-by: Andrew Morton <akpm@linux-foundation.org> |
| Signed-off-by: Andrew Morton <akpm@linux-foundation.org> |
| Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> |
| [bwh: Backported to 3.16: adjust context] |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| --- |
| fs/reiserfs/prints.c | 141 +++++++++++++++++++++++++------------------ |
| 1 file changed, 81 insertions(+), 60 deletions(-) |
| |
| --- a/fs/reiserfs/prints.c |
| +++ b/fs/reiserfs/prints.c |
| @@ -76,85 +76,101 @@ static char *le_type(struct reiserfs_key |
| } |
| |
| /* %k */ |
| -static void sprintf_le_key(char *buf, struct reiserfs_key *key) |
| +static int scnprintf_le_key(char *buf, size_t size, struct reiserfs_key *key) |
| { |
| if (key) |
| - sprintf(buf, "[%d %d %s %s]", le32_to_cpu(key->k_dir_id), |
| - le32_to_cpu(key->k_objectid), le_offset(key), |
| - le_type(key)); |
| + return scnprintf(buf, size, "[%d %d %s %s]", |
| + le32_to_cpu(key->k_dir_id), |
| + le32_to_cpu(key->k_objectid), le_offset(key), |
| + le_type(key)); |
| else |
| - sprintf(buf, "[NULL]"); |
| + return scnprintf(buf, size, "[NULL]"); |
| } |
| |
| /* %K */ |
| -static void sprintf_cpu_key(char *buf, struct cpu_key *key) |
| +static int scnprintf_cpu_key(char *buf, size_t size, struct cpu_key *key) |
| { |
| if (key) |
| - sprintf(buf, "[%d %d %s %s]", key->on_disk_key.k_dir_id, |
| - key->on_disk_key.k_objectid, reiserfs_cpu_offset(key), |
| - cpu_type(key)); |
| + return scnprintf(buf, size, "[%d %d %s %s]", |
| + key->on_disk_key.k_dir_id, |
| + key->on_disk_key.k_objectid, |
| + reiserfs_cpu_offset(key), cpu_type(key)); |
| else |
| - sprintf(buf, "[NULL]"); |
| + return scnprintf(buf, size, "[NULL]"); |
| } |
| |
| -static void sprintf_de_head(char *buf, struct reiserfs_de_head *deh) |
| +static int scnprintf_de_head(char *buf, size_t size, |
| + struct reiserfs_de_head *deh) |
| { |
| if (deh) |
| - sprintf(buf, |
| - "[offset=%d dir_id=%d objectid=%d location=%d state=%04x]", |
| - deh_offset(deh), deh_dir_id(deh), deh_objectid(deh), |
| - deh_location(deh), deh_state(deh)); |
| + return scnprintf(buf, size, |
| + "[offset=%d dir_id=%d objectid=%d location=%d state=%04x]", |
| + deh_offset(deh), deh_dir_id(deh), |
| + deh_objectid(deh), deh_location(deh), |
| + deh_state(deh)); |
| else |
| - sprintf(buf, "[NULL]"); |
| + return scnprintf(buf, size, "[NULL]"); |
| |
| } |
| |
| -static void sprintf_item_head(char *buf, struct item_head *ih) |
| +static int scnprintf_item_head(char *buf, size_t size, struct item_head *ih) |
| { |
| if (ih) { |
| - strcpy(buf, |
| - (ih_version(ih) == KEY_FORMAT_3_6) ? "*3.6* " : "*3.5*"); |
| - sprintf_le_key(buf + strlen(buf), &(ih->ih_key)); |
| - sprintf(buf + strlen(buf), ", item_len %d, item_location %d, " |
| - "free_space(entry_count) %d", |
| - ih_item_len(ih), ih_location(ih), ih_free_space(ih)); |
| + char *p = buf; |
| + char * const end = buf + size; |
| + |
| + p += scnprintf(p, end - p, "%s", |
| + (ih_version(ih) == KEY_FORMAT_3_6) ? |
| + "*3.6* " : "*3.5*"); |
| + |
| + p += scnprintf_le_key(p, end - p, &ih->ih_key); |
| + |
| + p += scnprintf(p, end - p, |
| + ", item_len %d, item_location %d, free_space(entry_count) %d", |
| + ih_item_len(ih), ih_location(ih), |
| + ih_free_space(ih)); |
| + return p - buf; |
| } else |
| - sprintf(buf, "[NULL]"); |
| + return scnprintf(buf, size, "[NULL]"); |
| } |
| |
| -static void sprintf_direntry(char *buf, struct reiserfs_dir_entry *de) |
| +static int scnprintf_direntry(char *buf, size_t size, |
| + struct reiserfs_dir_entry *de) |
| { |
| char name[20]; |
| |
| memcpy(name, de->de_name, de->de_namelen > 19 ? 19 : de->de_namelen); |
| name[de->de_namelen > 19 ? 19 : de->de_namelen] = 0; |
| - sprintf(buf, "\"%s\"==>[%d %d]", name, de->de_dir_id, de->de_objectid); |
| + return scnprintf(buf, size, "\"%s\"==>[%d %d]", |
| + name, de->de_dir_id, de->de_objectid); |
| } |
| |
| -static void sprintf_block_head(char *buf, struct buffer_head *bh) |
| +static int scnprintf_block_head(char *buf, size_t size, struct buffer_head *bh) |
| { |
| - sprintf(buf, "level=%d, nr_items=%d, free_space=%d rdkey ", |
| - B_LEVEL(bh), B_NR_ITEMS(bh), B_FREE_SPACE(bh)); |
| + return scnprintf(buf, size, |
| + "level=%d, nr_items=%d, free_space=%d rdkey ", |
| + B_LEVEL(bh), B_NR_ITEMS(bh), B_FREE_SPACE(bh)); |
| } |
| |
| -static void sprintf_buffer_head(char *buf, struct buffer_head *bh) |
| +static int scnprintf_buffer_head(char *buf, size_t size, struct buffer_head *bh) |
| { |
| char b[BDEVNAME_SIZE]; |
| |
| - sprintf(buf, |
| - "dev %s, size %zd, blocknr %llu, count %d, state 0x%lx, page %p, (%s, %s, %s)", |
| - bdevname(bh->b_bdev, b), bh->b_size, |
| - (unsigned long long)bh->b_blocknr, atomic_read(&(bh->b_count)), |
| - bh->b_state, bh->b_page, |
| - buffer_uptodate(bh) ? "UPTODATE" : "!UPTODATE", |
| - buffer_dirty(bh) ? "DIRTY" : "CLEAN", |
| - buffer_locked(bh) ? "LOCKED" : "UNLOCKED"); |
| + return scnprintf(buf, size, |
| + "dev %s, size %zd, blocknr %llu, count %d, state 0x%lx, page %p, (%s, %s, %s)", |
| + bdevname(bh->b_bdev, b), bh->b_size, |
| + (unsigned long long)bh->b_blocknr, |
| + atomic_read(&(bh->b_count)), |
| + bh->b_state, bh->b_page, |
| + buffer_uptodate(bh) ? "UPTODATE" : "!UPTODATE", |
| + buffer_dirty(bh) ? "DIRTY" : "CLEAN", |
| + buffer_locked(bh) ? "LOCKED" : "UNLOCKED"); |
| } |
| |
| -static void sprintf_disk_child(char *buf, struct disk_child *dc) |
| +static int scnprintf_disk_child(char *buf, size_t size, struct disk_child *dc) |
| { |
| - sprintf(buf, "[dc_number=%d, dc_size=%u]", dc_block_number(dc), |
| - dc_size(dc)); |
| + return scnprintf(buf, size, "[dc_number=%d, dc_size=%u]", |
| + dc_block_number(dc), dc_size(dc)); |
| } |
| |
| static char *is_there_reiserfs_struct(char *fmt, int *what) |
| @@ -191,55 +207,60 @@ static void prepare_error_buf(const char |
| char *fmt1 = fmt_buf; |
| char *k; |
| char *p = error_buf; |
| + char * const end = &error_buf[sizeof(error_buf)]; |
| int what; |
| |
| spin_lock(&error_lock); |
| |
| - strcpy(fmt1, fmt); |
| + if (WARN_ON(strscpy(fmt_buf, fmt, sizeof(fmt_buf)) < 0)) { |
| + strscpy(error_buf, "format string too long", end - error_buf); |
| + goto out_unlock; |
| + } |
| |
| while ((k = is_there_reiserfs_struct(fmt1, &what)) != NULL) { |
| *k = 0; |
| |
| - p += vsprintf(p, fmt1, args); |
| + p += vscnprintf(p, end - p, fmt1, args); |
| |
| switch (what) { |
| case 'k': |
| - sprintf_le_key(p, va_arg(args, struct reiserfs_key *)); |
| + p += scnprintf_le_key(p, end - p, |
| + va_arg(args, struct reiserfs_key *)); |
| break; |
| case 'K': |
| - sprintf_cpu_key(p, va_arg(args, struct cpu_key *)); |
| + p += scnprintf_cpu_key(p, end - p, |
| + va_arg(args, struct cpu_key *)); |
| break; |
| case 'h': |
| - sprintf_item_head(p, va_arg(args, struct item_head *)); |
| + p += scnprintf_item_head(p, end - p, |
| + va_arg(args, struct item_head *)); |
| break; |
| case 't': |
| - sprintf_direntry(p, |
| - va_arg(args, |
| - struct reiserfs_dir_entry *)); |
| + p += scnprintf_direntry(p, end - p, |
| + va_arg(args, struct reiserfs_dir_entry *)); |
| break; |
| case 'y': |
| - sprintf_disk_child(p, |
| - va_arg(args, struct disk_child *)); |
| + p += scnprintf_disk_child(p, end - p, |
| + va_arg(args, struct disk_child *)); |
| break; |
| case 'z': |
| - sprintf_block_head(p, |
| - va_arg(args, struct buffer_head *)); |
| + p += scnprintf_block_head(p, end - p, |
| + va_arg(args, struct buffer_head *)); |
| break; |
| case 'b': |
| - sprintf_buffer_head(p, |
| - va_arg(args, struct buffer_head *)); |
| + p += scnprintf_buffer_head(p, end - p, |
| + va_arg(args, struct buffer_head *)); |
| break; |
| case 'a': |
| - sprintf_de_head(p, |
| - va_arg(args, |
| - struct reiserfs_de_head *)); |
| + p += scnprintf_de_head(p, end - p, |
| + va_arg(args, struct reiserfs_de_head *)); |
| break; |
| } |
| |
| - p += strlen(p); |
| fmt1 = k + 2; |
| } |
| - vsprintf(p, fmt1, args); |
| + p += vscnprintf(p, end - p, fmt1, args); |
| +out_unlock: |
| spin_unlock(&error_lock); |
| |
| } |