History

jbd2: fix r_count overflows leading to buffer overflow in journal recovery

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.

Darrick J. Wong committed 11 years ago

Commit e531d0b1 parent 2f974862 files · +19 −9Browse files

Unified · Split

2 files
  1. fs/jbd2/recovery.c +9 −1
  2. fs/jbd2/revoke.c +10 −8
fs/jbd2/recovery.c+9−1
@@ -842,15 +842,23 @@ static int scan_revoke_records(journal_t *journal, struct buffer_head *bh, · 841 unmodified lines
842842 {
843843 jbd2_journal_revoke_header_t *header;
844844 int offset, max;
845+ int csum_size = 0;
846+ __u32 rcount;
845847 int record_len = 4;
846848
847849 header = (jbd2_journal_revoke_header_t *) bh->b_data;
848850 offset = sizeof(jbd2_journal_revoke_header_t);
849− max = be32_to_cpu(header->r_count);
851+ rcount = be32_to_cpu(header->r_count);
850852
851853 if (!jbd2_revoke_block_csum_verify(journal, header))
852854 return -EINVAL;
853855
856+ if (jbd2_journal_has_csum_v2or3(journal))
857+ csum_size = sizeof(struct jbd2_journal_revoke_tail);
858+ if (rcount > journal->j_blocksize - csum_size)
859+ return -EINVAL;
860+ max = rcount;
861+
854862 if (JBD2_HAS_INCOMPAT_FEATURE(journal, JBD2_FEATURE_INCOMPAT_64BIT))
855863 record_len = 8;
856864
fs/jbd2/revoke.c+10−8
@@ -577,7 +577,7 @@ static void write_one_revoke_record(journal_t *journal, · 576 unmodified lines
577577 {
578578 int csum_size = 0;
579579 struct buffer_head *descriptor;
580− int offset;
580+ int sz, offset;
581581 journal_header_t *header;
582582
583583 /* If we are already aborting, this all becomes a noop. We
@@ -594,9 +594,14 @@ static void write_one_revoke_record(journal_t *journal, · 10 unmodified lines
594594 if (jbd2_journal_has_csum_v2or3(journal))
595595 csum_size = sizeof(struct jbd2_journal_revoke_tail);
596596
597+ if (JBD2_HAS_INCOMPAT_FEATURE(journal, JBD2_FEATURE_INCOMPAT_64BIT))
598+ sz = 8;
599+ else
600+ sz = 4;
601+
597602 /* Make sure we have a descriptor with space left for the record */
598603 if (descriptor) {
599− if (offset >= journal->j_blocksize - csum_size) {
604+ if (offset + sz > journal->j_blocksize - csum_size) {
600605 flush_descriptor(journal, descriptor, offset, write_op);
601606 descriptor = NULL;
602607 }
@@ -619,16 +624,13 @@ static void write_one_revoke_record(journal_t *journal, · 16 unmodified lines
619624 *descriptorp = descriptor;
620625 }
621626
622− if (JBD2_HAS_INCOMPAT_FEATURE(journal, JBD2_FEATURE_INCOMPAT_64BIT)) {
627+ if (JBD2_HAS_INCOMPAT_FEATURE(journal, JBD2_FEATURE_INCOMPAT_64BIT))
623628 * ((__be64 *)(&descriptor->b_data[offset])) =
624629 cpu_to_be64(record->blocknr);
625− offset += 8;
626−
627− } else {
630+ else
628631 * ((__be32 *)(&descriptor->b_data[offset])) =
629632 cpu_to_be32(record->blocknr);
630− offset += 4;
631− }
633+ offset += sz;
632634
633635 *offsetp = offset;
634636 }