third_party_f2fs-tools/fsck/segment.c
Robin Hsu e66e4c1f3b fsck: prevent buffer overrun in quota code
A maliciously corrupted file systems can trigger buffer overruns in
the quota code used by fsck.

To fix it, quota file sizes are checked against real allocated
block index tables (inode, direct nodes, indirect nodes, double
indirect nodes).  If the size mismatches, the quota file is considered
corrupted and will be regenerated.

Signed-off-by: Robin Hsu <robinhsu@google.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
2020-06-23 16:58:44 -07:00

409 lines
9.9 KiB
C

/**
* segment.c
*
* Many parts of codes are copied from Linux kernel/fs/f2fs.
*
* Copyright (C) 2015 Huawei Ltd.
* Witten by:
* Hou Pengyang <houpengyang@huawei.com>
* Liu Shuoran <liushuoran@huawei.com>
* Jaegeuk Kim <jaegeuk@kernel.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "fsck.h"
#include "node.h"
#include "quotaio.h"
int reserve_new_block(struct f2fs_sb_info *sbi, block_t *to,
struct f2fs_summary *sum, int type, bool is_inode)
{
struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
struct seg_entry *se;
u64 blkaddr, offset;
u64 old_blkaddr = *to;
bool is_node = IS_NODESEG(type);
if (old_blkaddr == NULL_ADDR) {
if (c.func == FSCK) {
if (fsck->chk.valid_blk_cnt >= sbi->user_block_count) {
ERR_MSG("Not enough space\n");
return -ENOSPC;
}
if (is_node && fsck->chk.valid_node_cnt >=
sbi->total_valid_node_count) {
ERR_MSG("Not enough space for node block\n");
return -ENOSPC;
}
} else {
if (sbi->total_valid_block_count >=
sbi->user_block_count) {
ERR_MSG("Not enough space\n");
return -ENOSPC;
}
if (is_node && sbi->total_valid_node_count >=
sbi->total_node_count) {
ERR_MSG("Not enough space for node block\n");
return -ENOSPC;
}
}
}
blkaddr = SM_I(sbi)->main_blkaddr;
if (find_next_free_block(sbi, &blkaddr, 0, type, false)) {
ERR_MSG("Can't find free block");
ASSERT(0);
}
se = get_seg_entry(sbi, GET_SEGNO(sbi, blkaddr));
offset = OFFSET_IN_SEG(sbi, blkaddr);
se->type = type;
se->valid_blocks++;
f2fs_set_bit(offset, (char *)se->cur_valid_map);
if (need_fsync_data_record(sbi)) {
se->ckpt_type = type;
se->ckpt_valid_blocks++;
f2fs_set_bit(offset, (char *)se->ckpt_valid_map);
}
if (c.func == FSCK) {
f2fs_set_main_bitmap(sbi, blkaddr, type);
f2fs_set_sit_bitmap(sbi, blkaddr);
}
if (old_blkaddr == NULL_ADDR) {
sbi->total_valid_block_count++;
if (is_node) {
sbi->total_valid_node_count++;
if (is_inode)
sbi->total_valid_inode_count++;
}
if (c.func == FSCK) {
fsck->chk.valid_blk_cnt++;
if (is_node) {
fsck->chk.valid_node_cnt++;
if (is_inode)
fsck->chk.valid_inode_cnt++;
}
}
}
se->dirty = 1;
/* read/write SSA */
*to = (block_t)blkaddr;
update_sum_entry(sbi, *to, sum);
return 0;
}
int new_data_block(struct f2fs_sb_info *sbi, void *block,
struct dnode_of_data *dn, int type)
{
struct f2fs_summary sum;
struct node_info ni;
unsigned int blkaddr = datablock_addr(dn->node_blk, dn->ofs_in_node);
int ret;
ASSERT(dn->node_blk);
memset(block, 0, BLOCK_SZ);
get_node_info(sbi, dn->nid, &ni);
set_summary(&sum, dn->nid, dn->ofs_in_node, ni.version);
ret = reserve_new_block(sbi, &dn->data_blkaddr, &sum, type, 0);
if (ret) {
c.alloc_failed = 1;
return ret;
}
if (blkaddr == NULL_ADDR)
inc_inode_blocks(dn);
else if (blkaddr == NEW_ADDR)
dn->idirty = 1;
set_data_blkaddr(dn);
return 0;
}
u64 f2fs_quota_size(struct quota_file *qf)
{
struct node_info ni;
struct f2fs_node *inode;
u64 filesize;
inode = (struct f2fs_node *) calloc(BLOCK_SZ, 1);
ASSERT(inode);
/* Read inode */
get_node_info(qf->sbi, qf->ino, &ni);
ASSERT(dev_read_block(inode, ni.blk_addr) >= 0);
ASSERT(S_ISREG(le16_to_cpu(inode->i.i_mode)));
filesize = le64_to_cpu(inode->i.i_size);
free(inode);
return filesize;
}
u64 f2fs_read(struct f2fs_sb_info *sbi, nid_t ino, u8 *buffer,
u64 count, pgoff_t offset)
{
struct dnode_of_data dn;
struct node_info ni;
struct f2fs_node *inode;
char *blk_buffer;
u64 filesize;
u64 off_in_blk;
u64 len_in_blk;
u64 read_count;
u64 remained_blkentries;
block_t blkaddr;
void *index_node = NULL;
memset(&dn, 0, sizeof(dn));
/* Memory allocation for block buffer and inode. */
blk_buffer = calloc(BLOCK_SZ, 2);
ASSERT(blk_buffer);
inode = (struct f2fs_node*)(blk_buffer + BLOCK_SZ);
/* Read inode */
get_node_info(sbi, ino, &ni);
ASSERT(dev_read_block(inode, ni.blk_addr) >= 0);
ASSERT(!S_ISDIR(le16_to_cpu(inode->i.i_mode)));
ASSERT(!S_ISLNK(le16_to_cpu(inode->i.i_mode)));
/* Adjust count with file length. */
filesize = le64_to_cpu(inode->i.i_size);
if (offset > filesize)
count = 0;
else if (count + offset > filesize)
count = filesize - offset;
/* Main loop for file blocks */
read_count = remained_blkentries = 0;
while (count > 0) {
if (remained_blkentries == 0) {
set_new_dnode(&dn, inode, NULL, ino);
get_dnode_of_data(sbi, &dn, F2FS_BYTES_TO_BLK(offset),
LOOKUP_NODE);
if (index_node)
free(index_node);
index_node = (dn.node_blk == dn.inode_blk) ?
NULL : dn.node_blk;
remained_blkentries = ADDRS_PER_PAGE(sbi,
dn.node_blk, dn.inode_blk);
}
ASSERT(remained_blkentries > 0);
blkaddr = datablock_addr(dn.node_blk, dn.ofs_in_node);
if (blkaddr == NULL_ADDR || blkaddr == NEW_ADDR)
break;
off_in_blk = offset % BLOCK_SZ;
len_in_blk = BLOCK_SZ - off_in_blk;
if (len_in_blk > count)
len_in_blk = count;
/* Read data from single block. */
if (len_in_blk < BLOCK_SZ) {
ASSERT(dev_read_block(blk_buffer, blkaddr) >= 0);
memcpy(buffer, blk_buffer + off_in_blk, len_in_blk);
} else {
/* Direct read */
ASSERT(dev_read_block(buffer, blkaddr) >= 0);
}
offset += len_in_blk;
count -= len_in_blk;
buffer += len_in_blk;
read_count += len_in_blk;
dn.ofs_in_node++;
remained_blkentries--;
}
if (index_node)
free(index_node);
free(blk_buffer);
return read_count;
}
u64 f2fs_write(struct f2fs_sb_info *sbi, nid_t ino, u8 *buffer,
u64 count, pgoff_t offset)
{
struct dnode_of_data dn;
struct node_info ni;
struct f2fs_node *inode;
char *blk_buffer;
u64 off_in_blk;
u64 len_in_blk;
u64 written_count;
u64 remained_blkentries;
block_t blkaddr;
void* index_node = NULL;
int idirty = 0;
int err;
/* Memory allocation for block buffer and inode. */
blk_buffer = calloc(BLOCK_SZ, 2);
ASSERT(blk_buffer);
inode = (struct f2fs_node*)(blk_buffer + BLOCK_SZ);
/* Read inode */
get_node_info(sbi, ino, &ni);
ASSERT(dev_read_block(inode, ni.blk_addr) >= 0);
ASSERT(!S_ISDIR(le16_to_cpu(inode->i.i_mode)));
ASSERT(!S_ISLNK(le16_to_cpu(inode->i.i_mode)));
/* Main loop for file blocks */
written_count = remained_blkentries = 0;
while (count > 0) {
if (remained_blkentries == 0) {
set_new_dnode(&dn, inode, NULL, ino);
err = get_dnode_of_data(sbi, &dn,
F2FS_BYTES_TO_BLK(offset), ALLOC_NODE);
if (err)
break;
idirty |= dn.idirty;
if (index_node)
free(index_node);
index_node = (dn.node_blk == dn.inode_blk) ?
NULL : dn.node_blk;
remained_blkentries = ADDRS_PER_PAGE(sbi,
dn.node_blk, dn.inode_blk);
}
ASSERT(remained_blkentries > 0);
blkaddr = datablock_addr(dn.node_blk, dn.ofs_in_node);
if (blkaddr == NULL_ADDR || blkaddr == NEW_ADDR) {
err = new_data_block(sbi, blk_buffer,
&dn, CURSEG_WARM_DATA);
if (err)
break;
blkaddr = dn.data_blkaddr;
}
off_in_blk = offset % BLOCK_SZ;
len_in_blk = BLOCK_SZ - off_in_blk;
if (len_in_blk > count)
len_in_blk = count;
/* Write data to single block. */
if (len_in_blk < BLOCK_SZ) {
ASSERT(dev_read_block(blk_buffer, blkaddr) >= 0);
memcpy(blk_buffer + off_in_blk, buffer, len_in_blk);
ASSERT(dev_write_block(blk_buffer, blkaddr) >= 0);
} else {
/* Direct write */
ASSERT(dev_write_block(buffer, blkaddr) >= 0);
}
offset += len_in_blk;
count -= len_in_blk;
buffer += len_in_blk;
written_count += len_in_blk;
dn.ofs_in_node++;
if ((--remained_blkentries == 0 || count == 0) && (dn.ndirty))
ASSERT(dev_write_block(dn.node_blk, dn.node_blkaddr) >= 0);
}
if (offset > le64_to_cpu(inode->i.i_size)) {
inode->i.i_size = cpu_to_le64(offset);
idirty = 1;
}
if (idirty) {
ASSERT(inode == dn.inode_blk);
ASSERT(write_inode(inode, ni.blk_addr) >= 0);
}
if (index_node)
free(index_node);
free(blk_buffer);
return written_count;
}
/* This function updates only inode->i.i_size */
void f2fs_filesize_update(struct f2fs_sb_info *sbi, nid_t ino, u64 filesize)
{
struct node_info ni;
struct f2fs_node *inode;
inode = calloc(BLOCK_SZ, 1);
ASSERT(inode);
get_node_info(sbi, ino, &ni);
ASSERT(dev_read_block(inode, ni.blk_addr) >= 0);
ASSERT(!S_ISDIR(le16_to_cpu(inode->i.i_mode)));
ASSERT(!S_ISLNK(le16_to_cpu(inode->i.i_mode)));
inode->i.i_size = cpu_to_le64(filesize);
ASSERT(write_inode(inode, ni.blk_addr) >= 0);
free(inode);
}
int f2fs_build_file(struct f2fs_sb_info *sbi, struct dentry *de)
{
int fd, n;
pgoff_t off = 0;
u8 buffer[BLOCK_SZ];
if (de->ino == 0)
return -1;
fd = open(de->full_path, O_RDONLY);
if (fd < 0) {
MSG(0, "Skip: Fail to open %s\n", de->full_path);
return -1;
}
/* inline_data support */
if (de->size <= DEF_MAX_INLINE_DATA) {
struct node_info ni;
struct f2fs_node *node_blk;
int ret;
get_node_info(sbi, de->ino, &ni);
node_blk = calloc(BLOCK_SZ, 1);
ASSERT(node_blk);
ret = dev_read_block(node_blk, ni.blk_addr);
ASSERT(ret >= 0);
node_blk->i.i_inline |= F2FS_INLINE_DATA;
node_blk->i.i_inline |= F2FS_DATA_EXIST;
if (c.feature & cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR)) {
node_blk->i.i_inline |= F2FS_EXTRA_ATTR;
node_blk->i.i_extra_isize =
cpu_to_le16(calc_extra_isize());
}
n = read(fd, buffer, BLOCK_SZ);
ASSERT((unsigned long)n == de->size);
memcpy(inline_data_addr(node_blk), buffer, de->size);
node_blk->i.i_size = cpu_to_le64(de->size);
ASSERT(write_inode(node_blk, ni.blk_addr) >= 0);
free(node_blk);
} else {
while ((n = read(fd, buffer, BLOCK_SZ)) > 0) {
f2fs_write(sbi, de->ino, buffer, n, off);
off += n;
}
}
close(fd);
if (n < 0)
return -1;
update_free_segments(sbi);
MSG(1, "Info: Create %s -> %s\n"
" -- ino=%x, type=%x, mode=%x, uid=%x, "
"gid=%x, cap=%"PRIx64", size=%lu, pino=%x\n",
de->full_path, de->path,
de->ino, de->file_type, de->mode,
de->uid, de->gid, de->capabilities, de->size, de->pino);
return 0;
}