| From e531d0bceb402e643a4499de40dd3fa39d8d2e43 Mon Sep 17 00:00:00 2001 |
| From: "Darrick J. Wong" <darrick.wong@oracle.com> |
| Date: Thu, 14 May 2015 19:11:50 -0400 |
| Subject: jbd2: fix r_count overflows leading to buffer overflow in journal recovery |
| |
| From: "Darrick J. Wong" <darrick.wong@oracle.com> |
| |
| commit e531d0bceb402e643a4499de40dd3fa39d8d2e43 upstream. |
| |
| The journal revoke block recovery code does not check r_count for |
| sanity, which means that an evil value of r_count could result in |
| the kernel reading off the end of the revoke table and into whatever |
| garbage lies beyond. This could crash the kernel, so fix that. |
| |
| However, in testing this fix, I discovered that the code to write |
| out the revoke tables also was not correctly checking to see if the |
| block was full -- the current offset check is fine so long as the |
| revoke table space size is a multiple of the record size, but this |
| is not true when either journal_csum_v[23] are set. |
| |
| Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com> |
| Signed-off-by: Theodore Ts'o <tytso@mit.edu> |
| Reviewed-by: Jan Kara <jack@suse.cz> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/jbd2/recovery.c | 10 +++++++++- |
| fs/jbd2/revoke.c | 18 ++++++++++-------- |
| 2 files changed, 19 insertions(+), 9 deletions(-) |
| |
| --- a/fs/jbd2/recovery.c |
| +++ b/fs/jbd2/recovery.c |
| @@ -839,15 +839,23 @@ static int scan_revoke_records(journal_t |
| { |
| jbd2_journal_revoke_header_t *header; |
| int offset, max; |
| + int csum_size = 0; |
| + __u32 rcount; |
| int record_len = 4; |
| |
| header = (jbd2_journal_revoke_header_t *) bh->b_data; |
| offset = sizeof(jbd2_journal_revoke_header_t); |
| - max = be32_to_cpu(header->r_count); |
| + rcount = be32_to_cpu(header->r_count); |
| |
| if (!jbd2_revoke_block_csum_verify(journal, header)) |
| return -EINVAL; |
| |
| + if (jbd2_journal_has_csum_v2or3(journal)) |
| + csum_size = sizeof(struct jbd2_journal_revoke_tail); |
| + if (rcount > journal->j_blocksize - csum_size) |
| + return -EINVAL; |
| + max = rcount; |
| + |
| if (JBD2_HAS_INCOMPAT_FEATURE(journal, JBD2_FEATURE_INCOMPAT_64BIT)) |
| record_len = 8; |
| |
| --- a/fs/jbd2/revoke.c |
| +++ b/fs/jbd2/revoke.c |
| @@ -583,7 +583,7 @@ static void write_one_revoke_record(jour |
| { |
| int csum_size = 0; |
| struct buffer_head *descriptor; |
| - int offset; |
| + int sz, offset; |
| journal_header_t *header; |
| |
| /* If we are already aborting, this all becomes a noop. We |
| @@ -600,9 +600,14 @@ static void write_one_revoke_record(jour |
| if (jbd2_journal_has_csum_v2or3(journal)) |
| csum_size = sizeof(struct jbd2_journal_revoke_tail); |
| |
| + if (JBD2_HAS_INCOMPAT_FEATURE(journal, JBD2_FEATURE_INCOMPAT_64BIT)) |
| + sz = 8; |
| + else |
| + sz = 4; |
| + |
| /* Make sure we have a descriptor with space left for the record */ |
| if (descriptor) { |
| - if (offset >= journal->j_blocksize - csum_size) { |
| + if (offset + sz > journal->j_blocksize - csum_size) { |
| flush_descriptor(journal, descriptor, offset, write_op); |
| descriptor = NULL; |
| } |
| @@ -625,16 +630,13 @@ static void write_one_revoke_record(jour |
| *descriptorp = descriptor; |
| } |
| |
| - if (JBD2_HAS_INCOMPAT_FEATURE(journal, JBD2_FEATURE_INCOMPAT_64BIT)) { |
| + if (JBD2_HAS_INCOMPAT_FEATURE(journal, JBD2_FEATURE_INCOMPAT_64BIT)) |
| * ((__be64 *)(&descriptor->b_data[offset])) = |
| cpu_to_be64(record->blocknr); |
| - offset += 8; |
| - |
| - } else { |
| + else |
| * ((__be32 *)(&descriptor->b_data[offset])) = |
| cpu_to_be32(record->blocknr); |
| - offset += 4; |
| - } |
| + offset += sz; |
| |
| *offsetp = offset; |
| } |