ocfs2: add bounds checking to ocfs2_check_dir_entry()
This adds sanity checks for ocfs2_dir_entry to make sure all members of ocfs2_dir_entry don't stray beyond valid memory region. Link: https://lkml.kernel.org/r/20240626104433.163270-1-llfamsec@gmail.com Signed-off-by: lei lu <llfamsec@gmail.com> Reviewed-by: Heming Zhao <heming.zhao@suse.com> Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com> Cc: Mark Fasheh <mark@fasheh.com> Cc: Joel Becker <jlbec@evilplan.org> Cc: Junxiao Bi <junxiao.bi@oracle.com> Cc: Changwei Ge <gechangwei@live.cn> Cc: Gang He <ghe@suse.com> Cc: Jun Piao <piaojun@huawei.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
parent
937b2972ce
commit
255547c6bb
@ -294,13 +294,16 @@ out:
|
||||
* bh passed here can be an inode block or a dir data block, depending
|
||||
* on the inode inline data flag.
|
||||
*/
|
||||
static int ocfs2_check_dir_entry(struct inode * dir,
|
||||
struct ocfs2_dir_entry * de,
|
||||
struct buffer_head * bh,
|
||||
static int ocfs2_check_dir_entry(struct inode *dir,
|
||||
struct ocfs2_dir_entry *de,
|
||||
struct buffer_head *bh,
|
||||
char *buf,
|
||||
unsigned int size,
|
||||
unsigned long offset)
|
||||
{
|
||||
const char *error_msg = NULL;
|
||||
const int rlen = le16_to_cpu(de->rec_len);
|
||||
const unsigned long next_offset = ((char *) de - buf) + rlen;
|
||||
|
||||
if (unlikely(rlen < OCFS2_DIR_REC_LEN(1)))
|
||||
error_msg = "rec_len is smaller than minimal";
|
||||
@ -308,9 +311,11 @@ static int ocfs2_check_dir_entry(struct inode * dir,
|
||||
error_msg = "rec_len % 4 != 0";
|
||||
else if (unlikely(rlen < OCFS2_DIR_REC_LEN(de->name_len)))
|
||||
error_msg = "rec_len is too small for name_len";
|
||||
else if (unlikely(
|
||||
((char *) de - bh->b_data) + rlen > dir->i_sb->s_blocksize))
|
||||
error_msg = "directory entry across blocks";
|
||||
else if (unlikely(next_offset > size))
|
||||
error_msg = "directory entry overrun";
|
||||
else if (unlikely(next_offset > size - OCFS2_DIR_REC_LEN(1)) &&
|
||||
next_offset != size)
|
||||
error_msg = "directory entry too close to end";
|
||||
|
||||
if (unlikely(error_msg != NULL))
|
||||
mlog(ML_ERROR, "bad entry in directory #%llu: %s - "
|
||||
@ -352,16 +357,17 @@ static inline int ocfs2_search_dirblock(struct buffer_head *bh,
|
||||
de_buf = first_de;
|
||||
dlimit = de_buf + bytes;
|
||||
|
||||
while (de_buf < dlimit) {
|
||||
while (de_buf < dlimit - OCFS2_DIR_MEMBER_LEN) {
|
||||
/* this code is executed quadratically often */
|
||||
/* do minimal checking `by hand' */
|
||||
|
||||
de = (struct ocfs2_dir_entry *) de_buf;
|
||||
|
||||
if (de_buf + namelen <= dlimit &&
|
||||
if (de->name + namelen <= dlimit &&
|
||||
ocfs2_match(namelen, name, de)) {
|
||||
/* found a match - just to be sure, do a full check */
|
||||
if (!ocfs2_check_dir_entry(dir, de, bh, offset)) {
|
||||
if (!ocfs2_check_dir_entry(dir, de, bh, first_de,
|
||||
bytes, offset)) {
|
||||
ret = -1;
|
||||
goto bail;
|
||||
}
|
||||
@ -1138,7 +1144,7 @@ static int __ocfs2_delete_entry(handle_t *handle, struct inode *dir,
|
||||
pde = NULL;
|
||||
de = (struct ocfs2_dir_entry *) first_de;
|
||||
while (i < bytes) {
|
||||
if (!ocfs2_check_dir_entry(dir, de, bh, i)) {
|
||||
if (!ocfs2_check_dir_entry(dir, de, bh, first_de, bytes, i)) {
|
||||
status = -EIO;
|
||||
mlog_errno(status);
|
||||
goto bail;
|
||||
@ -1635,7 +1641,8 @@ int __ocfs2_add_entry(handle_t *handle,
|
||||
/* These checks should've already been passed by the
|
||||
* prepare function, but I guess we can leave them
|
||||
* here anyway. */
|
||||
if (!ocfs2_check_dir_entry(dir, de, insert_bh, offset)) {
|
||||
if (!ocfs2_check_dir_entry(dir, de, insert_bh, data_start,
|
||||
size, offset)) {
|
||||
retval = -ENOENT;
|
||||
goto bail;
|
||||
}
|
||||
@ -1774,7 +1781,8 @@ static int ocfs2_dir_foreach_blk_id(struct inode *inode,
|
||||
}
|
||||
|
||||
de = (struct ocfs2_dir_entry *) (data->id_data + ctx->pos);
|
||||
if (!ocfs2_check_dir_entry(inode, de, di_bh, ctx->pos)) {
|
||||
if (!ocfs2_check_dir_entry(inode, de, di_bh, (char *)data->id_data,
|
||||
i_size_read(inode), ctx->pos)) {
|
||||
/* On error, skip the f_pos to the end. */
|
||||
ctx->pos = i_size_read(inode);
|
||||
break;
|
||||
@ -1867,7 +1875,8 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode,
|
||||
while (ctx->pos < i_size_read(inode)
|
||||
&& offset < sb->s_blocksize) {
|
||||
de = (struct ocfs2_dir_entry *) (bh->b_data + offset);
|
||||
if (!ocfs2_check_dir_entry(inode, de, bh, offset)) {
|
||||
if (!ocfs2_check_dir_entry(inode, de, bh, bh->b_data,
|
||||
sb->s_blocksize, offset)) {
|
||||
/* On error, skip the f_pos to the
|
||||
next block. */
|
||||
ctx->pos = (ctx->pos | (sb->s_blocksize - 1)) + 1;
|
||||
@ -3339,7 +3348,7 @@ static int ocfs2_find_dir_space_id(struct inode *dir, struct buffer_head *di_bh,
|
||||
struct super_block *sb = dir->i_sb;
|
||||
struct ocfs2_dinode *di = (struct ocfs2_dinode *)di_bh->b_data;
|
||||
struct ocfs2_dir_entry *de, *last_de = NULL;
|
||||
char *de_buf, *limit;
|
||||
char *first_de, *de_buf, *limit;
|
||||
unsigned long offset = 0;
|
||||
unsigned int rec_len, new_rec_len, free_space;
|
||||
|
||||
@ -3352,14 +3361,16 @@ static int ocfs2_find_dir_space_id(struct inode *dir, struct buffer_head *di_bh,
|
||||
else
|
||||
free_space = dir->i_sb->s_blocksize - i_size_read(dir);
|
||||
|
||||
de_buf = di->id2.i_data.id_data;
|
||||
first_de = di->id2.i_data.id_data;
|
||||
de_buf = first_de;
|
||||
limit = de_buf + i_size_read(dir);
|
||||
rec_len = OCFS2_DIR_REC_LEN(namelen);
|
||||
|
||||
while (de_buf < limit) {
|
||||
de = (struct ocfs2_dir_entry *)de_buf;
|
||||
|
||||
if (!ocfs2_check_dir_entry(dir, de, di_bh, offset)) {
|
||||
if (!ocfs2_check_dir_entry(dir, de, di_bh, first_de,
|
||||
i_size_read(dir), offset)) {
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
@ -3441,7 +3452,8 @@ static int ocfs2_find_dir_space_el(struct inode *dir, const char *name,
|
||||
/* move to next block */
|
||||
de = (struct ocfs2_dir_entry *) bh->b_data;
|
||||
}
|
||||
if (!ocfs2_check_dir_entry(dir, de, bh, offset)) {
|
||||
if (!ocfs2_check_dir_entry(dir, de, bh, bh->b_data, blocksize,
|
||||
offset)) {
|
||||
status = -ENOENT;
|
||||
goto bail;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user