aboutsummaryrefslogtreecommitdiffstats
path: root/fs/xfs
diff options
context:
space:
mode:
authorBrian Foster <bfoster@redhat.com>2016-01-03 23:55:10 -0500
committerDave Chinner <david@fromorbit.com>2016-01-03 23:55:10 -0500
commita70f9fe52daa839d3925ac7e2dbd0ca758434493 (patch)
tree014ce5e9c688c974b92a740e9f49784091c26eb5 /fs/xfs
parent168309855a7d1e16db751e9c647119fe2d2dc878 (diff)
xfs: detect and handle invalid iclog size set by mkfs
XFS log records have separate fields for the record size and the iclog size used to write the record. mkfs.xfs zeroes the log and writes an unmount record to generate a clean log for the subsequent mount. The userspace record logging code has a bug where the iclog size (h_size) field of the log record is hardcoded to 32k, even if a log stripe unit is specified. The log record length is correctly extended to the stripe unit. Since the kernel log recovery code uses the h_size field to determine the log buffer size, this means that the kernel can attempt to read/process records larger than the buffer size and overrun the buffer. This has historically not been a problem because the kernel doesn't actually run through log recovery in the clean unmount case. Instead, the kernel detects that a single unmount record exists between the head and tail and pushes the tail forward such that the log is viewed as clean (head == tail). Once CRC verification is enabled, however, all records at the head of the log are verified for CRC errors and thus we are susceptible to overrun problems if the iclog field is not correct. While the core problem must be fixed in userspace, this is historical behavior that must be detected in the kernel to avoid severe side effects such as memory corruption and crashes. Update the log buffer size calculation code to detect this condition, warn the user and resize the log buffer based on the log stripe unit. Return a corruption error in cases where this does not look like a clean filesystem (i.e., the log record header indicates more than one operation). Signed-off-by: Brian Foster <bfoster@redhat.com> Reviewed-by: Dave Chinner <dchinner@redhat.com> Signed-off-by: Dave Chinner <david@fromorbit.com>
Diffstat (limited to 'fs/xfs')
-rw-r--r--fs/xfs/xfs_log_recover.c26
1 files changed, 25 insertions, 1 deletions
diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c
index c5ecaacdd218..4f880d6cfb93 100644
--- a/fs/xfs/xfs_log_recover.c
+++ b/fs/xfs/xfs_log_recover.c
@@ -4245,7 +4245,7 @@ xlog_do_recovery_pass(
4245 xfs_daddr_t blk_no; 4245 xfs_daddr_t blk_no;
4246 char *offset; 4246 char *offset;
4247 xfs_buf_t *hbp, *dbp; 4247 xfs_buf_t *hbp, *dbp;
4248 int error = 0, h_size; 4248 int error = 0, h_size, h_len;
4249 int bblks, split_bblks; 4249 int bblks, split_bblks;
4250 int hblks, split_hblks, wrapped_hblks; 4250 int hblks, split_hblks, wrapped_hblks;
4251 struct hlist_head rhash[XLOG_RHASH_SIZE]; 4251 struct hlist_head rhash[XLOG_RHASH_SIZE];
@@ -4274,7 +4274,31 @@ xlog_do_recovery_pass(
4274 error = xlog_valid_rec_header(log, rhead, tail_blk); 4274 error = xlog_valid_rec_header(log, rhead, tail_blk);
4275 if (error) 4275 if (error)
4276 goto bread_err1; 4276 goto bread_err1;
4277
4278 /*
4279 * xfsprogs has a bug where record length is based on lsunit but
4280 * h_size (iclog size) is hardcoded to 32k. Now that we
4281 * unconditionally CRC verify the unmount record, this means the
4282 * log buffer can be too small for the record and cause an
4283 * overrun.
4284 *
4285 * Detect this condition here. Use lsunit for the buffer size as
4286 * long as this looks like the mkfs case. Otherwise, return an
4287 * error to avoid a buffer overrun.
4288 */
4277 h_size = be32_to_cpu(rhead->h_size); 4289 h_size = be32_to_cpu(rhead->h_size);
4290 h_len = be32_to_cpu(rhead->h_len);
4291 if (h_len > h_size) {
4292 if (h_len <= log->l_mp->m_logbsize &&
4293 be32_to_cpu(rhead->h_num_logops) == 1) {
4294 xfs_warn(log->l_mp,
4295 "invalid iclog size (%d bytes), using lsunit (%d bytes)",
4296 h_size, log->l_mp->m_logbsize);
4297 h_size = log->l_mp->m_logbsize;
4298 } else
4299 return -EFSCORRUPTED;
4300 }
4301
4278 if ((be32_to_cpu(rhead->h_version) & XLOG_VERSION_2) && 4302 if ((be32_to_cpu(rhead->h_version) & XLOG_VERSION_2) &&
4279 (h_size > XLOG_HEADER_CYCLE_SIZE)) { 4303 (h_size > XLOG_HEADER_CYCLE_SIZE)) {
4280 hblks = h_size / XLOG_HEADER_CYCLE_SIZE; 4304 hblks = h_size / XLOG_HEADER_CYCLE_SIZE;