diff mbox series

[v4] ocfs2: add bounds checking to ocfs2_check_dir_entry()

Message ID 20240617114027.139982-1-llfamsec@gmail.com (mailing list archive)
State New
Headers show
Series [v4] ocfs2: add bounds checking to ocfs2_check_dir_entry() | expand

Commit Message

lei lu June 17, 2024, 11:40 a.m. UTC
This adds sanity checks for ocfs2_dir_entry to make sure all members of
ocfs2_dir_entry don't stray beyond valid memory region.

Signed-off-by: lei lu <llfamsec@gmail.com>
Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/dir.c | 34 ++++++++++++++++++++++------------
 1 file changed, 22 insertions(+), 12 deletions(-)

Comments

heming.zhao@suse.com June 18, 2024, 3:29 a.m. UTC | #1
On 6/17/24 19:40, lei lu wrote:
> This adds sanity checks for ocfs2_dir_entry to make sure all members of
> ocfs2_dir_entry don't stray beyond valid memory region.
> 
> Signed-off-by: lei lu <llfamsec@gmail.com>
> Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
> ---
>   fs/ocfs2/dir.c | 34 ++++++++++++++++++++++------------
>   1 file changed, 22 insertions(+), 12 deletions(-)
> 
> diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
> index d620d4c53c6f..562f61daf77d 100644
> --- a/fs/ocfs2/dir.c
> +++ b/fs/ocfs2/dir.c
> @@ -294,13 +294,16 @@ static void ocfs2_dx_dir_name_hash(struct inode *dir, const char *name, int len,
>    * 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;

from this patch, there are two cases for the parameter 'buf':

1>
'de' is the same as 'bh', de & bh both are the latest dir_entry offset.
the sanity check always passes below cases. e.g:
- ocfs2_search_dirblock()
- ocfs2_find_dir_space_id()

2>
'de' is different with 'bh', bh is the first dir_entry offset, de is
the latest offset. these cases should be the expected code paths, e.g:
- __ocfs2_delete_entry()
- ocfs2_dir_foreach_blk_id()
- ocfs2_dir_foreach_blk_el()
- ocfs2_find_dir_space_el()

If my thinking is right, case <1> needs to be revised to follow the design.

-Heming
>   
>   	if (unlikely(rlen < OCFS2_DIR_REC_LEN(1)))
>   		error_msg = "rec_len is smaller than minimal";
> @@ -308,6 +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(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";
>   	else if (unlikely(
>   		 ((char *) de - bh->b_data) + rlen > dir->i_sb->s_blocksize))
>   		error_msg = "directory entry across blocks";
> @@ -352,16 +360,16 @@ 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, de_buf, bytes, offset)) {
>   				ret = -1;
>   				goto bail;
>   			}
> @@ -1138,7 +1146,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 +1643,7 @@ 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 +1782,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 +1876,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;
> @@ -3359,7 +3369,7 @@ static int ocfs2_find_dir_space_id(struct inode *dir, struct buffer_head *di_bh,
>   	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, de_buf, i_size_read(dir), offset)) {
>   			ret = -ENOENT;
>   			goto out;
>   		}
> @@ -3441,7 +3451,7 @@ 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;
>   		}
lei lu June 18, 2024, 5:58 a.m. UTC | #2
On Tue, Jun 18, 2024 at 11:29 AM Heming Zhao <heming.zhao@suse.com> wrote:
>
> On 6/17/24 19:40, lei lu wrote:
> > This adds sanity checks for ocfs2_dir_entry to make sure all members of
> > ocfs2_dir_entry don't stray beyond valid memory region.
> >
> > Signed-off-by: lei lu <llfamsec@gmail.com>
> > Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
> > ---
> >   fs/ocfs2/dir.c | 34 ++++++++++++++++++++++------------
> >   1 file changed, 22 insertions(+), 12 deletions(-)
> >
> > diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
> > index d620d4c53c6f..562f61daf77d 100644
> > --- a/fs/ocfs2/dir.c
> > +++ b/fs/ocfs2/dir.c
> > @@ -294,13 +294,16 @@ static void ocfs2_dx_dir_name_hash(struct inode *dir, const char *name, int len,
> >    * 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;
>
> from this patch, there are two cases for the parameter 'buf':
>
> 1>
> 'de' is the same as 'bh', de & bh both are the latest dir_entry offset.
> the sanity check always passes below cases. e.g:
> - ocfs2_search_dirblock()
> - ocfs2_find_dir_space_id()

Thanks for your time.

Sorry, I don't quite understand what you mean.
I think 'de' is not the same as 'bh'. e.g.:
- ocfs2_search_dirblock(): de is the latest dir_entry offset, bh is
the ocfs2_dinode offset, buf is the first dir_entry offset
- ocfs2_find_dir_space_id(): de is the latest dir_entry offset, bh is
the ocfs2_dinode offset, buf is the first dir_entry offset

Thanks,
LL

>
> 2>
> 'de' is different with 'bh', bh is the first dir_entry offset, de is
> the latest offset. these cases should be the expected code paths, e.g:
> - __ocfs2_delete_entry()
> - ocfs2_dir_foreach_blk_id()
> - ocfs2_dir_foreach_blk_el()
> - ocfs2_find_dir_space_el()
>
> If my thinking is right, case <1> needs to be revised to follow the design.
>
> -Heming
> >
> >       if (unlikely(rlen < OCFS2_DIR_REC_LEN(1)))
> >               error_msg = "rec_len is smaller than minimal";
> > @@ -308,6 +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(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";
> >       else if (unlikely(
> >                ((char *) de - bh->b_data) + rlen > dir->i_sb->s_blocksize))
> >               error_msg = "directory entry across blocks";
> > @@ -352,16 +360,16 @@ 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, de_buf, bytes, offset)) {
> >                               ret = -1;
> >                               goto bail;
> >                       }
> > @@ -1138,7 +1146,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 +1643,7 @@ 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 +1782,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 +1876,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;
> > @@ -3359,7 +3369,7 @@ static int ocfs2_find_dir_space_id(struct inode *dir, struct buffer_head *di_bh,
> >       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, de_buf, i_size_read(dir), offset)) {
> >                       ret = -ENOENT;
> >                       goto out;
> >               }
> > @@ -3441,7 +3451,7 @@ 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;
> >               }
>
heming.zhao@suse.com June 18, 2024, 6:13 a.m. UTC | #3
On 6/18/24 13:58, lei lu wrote:
> On Tue, Jun 18, 2024 at 11:29 AM Heming Zhao <heming.zhao@suse.com> wrote:
>>
>> On 6/17/24 19:40, lei lu wrote:
>>> This adds sanity checks for ocfs2_dir_entry to make sure all members of
>>> ocfs2_dir_entry don't stray beyond valid memory region.
>>>
>>> Signed-off-by: lei lu <llfamsec@gmail.com>
>>> Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
>>> ---
>>>    fs/ocfs2/dir.c | 34 ++++++++++++++++++++++------------
>>>    1 file changed, 22 insertions(+), 12 deletions(-)
>>>
>>> diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
>>> index d620d4c53c6f..562f61daf77d 100644
>>> --- a/fs/ocfs2/dir.c
>>> +++ b/fs/ocfs2/dir.c
>>> @@ -294,13 +294,16 @@ static void ocfs2_dx_dir_name_hash(struct inode *dir, const char *name, int len,
>>>     * 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;
>>
>> from this patch, there are two cases for the parameter 'buf':
>>
>> 1>
>> 'de' is the same as 'bh', de & bh both are the latest dir_entry offset.
>> the sanity check always passes below cases. e.g:
>> - ocfs2_search_dirblock()
>> - ocfs2_find_dir_space_id()
> 
> Thanks for your time.
> 
> Sorry, I don't quite understand what you mean.

Oh, sorry for my typo, my mean: 'de' is the same as 'buf' not 'bh'.

The patch code in ocfs2_search_dirblock() is:
   if (!ocfs2_check_dir_entry(dir, de, bh, de_buf, bytes, offset))

the de is same as de_buf, so in ocfs2_check_dir_entry() the next_offset becomes
   next_offset = rlen; //because "de == de_buf"

In this case, rlen is not the current dir entry end boundary.

> I think 'de' is not the same as 'bh'. e.g.:
> - ocfs2_search_dirblock(): de is the latest dir_entry offset, bh is
> the ocfs2_dinode offset, buf is the first dir_entry offset

buf is not the first dir_entry offset.

> - ocfs2_find_dir_space_id(): de is the latest dir_entry offset, bh is
> the ocfs2_dinode offset, buf is the first dir_entry offset

ditto

-Heming
> 
> Thanks,
> LL
> 
>>
>> 2>
>> 'de' is different with 'bh', bh is the first dir_entry offset, de is
>> the latest offset. these cases should be the expected code paths, e.g:
>> - __ocfs2_delete_entry()
>> - ocfs2_dir_foreach_blk_id()
>> - ocfs2_dir_foreach_blk_el()
>> - ocfs2_find_dir_space_el()
>>
>> If my thinking is right, case <1> needs to be revised to follow the design.
>>
>> -Heming
>>>
>>>        if (unlikely(rlen < OCFS2_DIR_REC_LEN(1)))
>>>                error_msg = "rec_len is smaller than minimal";
>>> @@ -308,6 +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(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";
>>>        else if (unlikely(
>>>                 ((char *) de - bh->b_data) + rlen > dir->i_sb->s_blocksize))
>>>                error_msg = "directory entry across blocks";
>>> @@ -352,16 +360,16 @@ 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, de_buf, bytes, offset)) {
>>>                                ret = -1;
>>>                                goto bail;
>>>                        }
>>> @@ -1138,7 +1146,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 +1643,7 @@ 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 +1782,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 +1876,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;
>>> @@ -3359,7 +3369,7 @@ static int ocfs2_find_dir_space_id(struct inode *dir, struct buffer_head *di_bh,
>>>        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, de_buf, i_size_read(dir), offset)) {
>>>                        ret = -ENOENT;
>>>                        goto out;
>>>                }
>>> @@ -3441,7 +3451,7 @@ 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;
>>>                }
>>
lei lu June 18, 2024, 6:20 a.m. UTC | #4
On Tue, Jun 18, 2024 at 2:13 PM Heming Zhao <heming.zhao@suse.com> wrote:
>
> On 6/18/24 13:58, lei lu wrote:
> > On Tue, Jun 18, 2024 at 11:29 AM Heming Zhao <heming.zhao@suse.com> wrote:
> >>
> >> On 6/17/24 19:40, lei lu wrote:
> >>> This adds sanity checks for ocfs2_dir_entry to make sure all members of
> >>> ocfs2_dir_entry don't stray beyond valid memory region.
> >>>
> >>> Signed-off-by: lei lu <llfamsec@gmail.com>
> >>> Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
> >>> ---
> >>>    fs/ocfs2/dir.c | 34 ++++++++++++++++++++++------------
> >>>    1 file changed, 22 insertions(+), 12 deletions(-)
> >>>
> >>> diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
> >>> index d620d4c53c6f..562f61daf77d 100644
> >>> --- a/fs/ocfs2/dir.c
> >>> +++ b/fs/ocfs2/dir.c
> >>> @@ -294,13 +294,16 @@ static void ocfs2_dx_dir_name_hash(struct inode *dir, const char *name, int len,
> >>>     * 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;
> >>
> >> from this patch, there are two cases for the parameter 'buf':
> >>
> >> 1>
> >> 'de' is the same as 'bh', de & bh both are the latest dir_entry offset.
> >> the sanity check always passes below cases. e.g:
> >> - ocfs2_search_dirblock()
> >> - ocfs2_find_dir_space_id()
> >
> > Thanks for your time.
> >
> > Sorry, I don't quite understand what you mean.
>
> Oh, sorry for my typo, my mean: 'de' is the same as 'buf' not 'bh'.
>
> The patch code in ocfs2_search_dirblock() is:
>    if (!ocfs2_check_dir_entry(dir, de, bh, de_buf, bytes, offset))
>
> the de is same as de_buf, so in ocfs2_check_dir_entry() the next_offset becomes
>    next_offset = rlen; //because "de == de_buf"
>
> In this case, rlen is not the current dir entry end boundary.
>
> > I think 'de' is not the same as 'bh'. e.g.:
> > - ocfs2_search_dirblock(): de is the latest dir_entry offset, bh is
> > the ocfs2_dinode offset, buf is the first dir_entry offset
>
> buf is not the first dir_entry offset.
>
> > - ocfs2_find_dir_space_id(): de is the latest dir_entry offset, bh is
> > the ocfs2_dinode offset, buf is the first dir_entry offset
>
> ditto

Oh, you are right, We should use first_de here.

Thanks,
LL

>
> -Heming
> >
> > Thanks,
> > LL
> >
> >>
> >> 2>
> >> 'de' is different with 'bh', bh is the first dir_entry offset, de is
> >> the latest offset. these cases should be the expected code paths, e.g:
> >> - __ocfs2_delete_entry()
> >> - ocfs2_dir_foreach_blk_id()
> >> - ocfs2_dir_foreach_blk_el()
> >> - ocfs2_find_dir_space_el()
> >>
> >> If my thinking is right, case <1> needs to be revised to follow the design.
> >>
> >> -Heming
> >>>
> >>>        if (unlikely(rlen < OCFS2_DIR_REC_LEN(1)))
> >>>                error_msg = "rec_len is smaller than minimal";
> >>> @@ -308,6 +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(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";
> >>>        else if (unlikely(
> >>>                 ((char *) de - bh->b_data) + rlen > dir->i_sb->s_blocksize))
> >>>                error_msg = "directory entry across blocks";
> >>> @@ -352,16 +360,16 @@ 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, de_buf, bytes, offset)) {
> >>>                                ret = -1;
> >>>                                goto bail;
> >>>                        }
> >>> @@ -1138,7 +1146,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 +1643,7 @@ 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 +1782,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 +1876,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;
> >>> @@ -3359,7 +3369,7 @@ static int ocfs2_find_dir_space_id(struct inode *dir, struct buffer_head *di_bh,
> >>>        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, de_buf, i_size_read(dir), offset)) {
> >>>                        ret = -ENOENT;
> >>>                        goto out;
> >>>                }
> >>> @@ -3441,7 +3451,7 @@ 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;
> >>>                }
> >>
>
lei lu June 18, 2024, 6:32 a.m. UTC | #5
Hi,

Do you have further comments and/or suggestions? I will remake the
patch to fix the issue in case <1>.

Thanks,
LL

On Tue, Jun 18, 2024 at 2:20 PM lei lu <llfamsec@gmail.com> wrote:
>
> On Tue, Jun 18, 2024 at 2:13 PM Heming Zhao <heming.zhao@suse.com> wrote:
> >
> > On 6/18/24 13:58, lei lu wrote:
> > > On Tue, Jun 18, 2024 at 11:29 AM Heming Zhao <heming.zhao@suse.com> wrote:
> > >>
> > >> On 6/17/24 19:40, lei lu wrote:
> > >>> This adds sanity checks for ocfs2_dir_entry to make sure all members of
> > >>> ocfs2_dir_entry don't stray beyond valid memory region.
> > >>>
> > >>> Signed-off-by: lei lu <llfamsec@gmail.com>
> > >>> Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
> > >>> ---
> > >>>    fs/ocfs2/dir.c | 34 ++++++++++++++++++++++------------
> > >>>    1 file changed, 22 insertions(+), 12 deletions(-)
> > >>>
> > >>> diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
> > >>> index d620d4c53c6f..562f61daf77d 100644
> > >>> --- a/fs/ocfs2/dir.c
> > >>> +++ b/fs/ocfs2/dir.c
> > >>> @@ -294,13 +294,16 @@ static void ocfs2_dx_dir_name_hash(struct inode *dir, const char *name, int len,
> > >>>     * 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;
> > >>
> > >> from this patch, there are two cases for the parameter 'buf':
> > >>
> > >> 1>
> > >> 'de' is the same as 'bh', de & bh both are the latest dir_entry offset.
> > >> the sanity check always passes below cases. e.g:
> > >> - ocfs2_search_dirblock()
> > >> - ocfs2_find_dir_space_id()
> > >
> > > Thanks for your time.
> > >
> > > Sorry, I don't quite understand what you mean.
> >
> > Oh, sorry for my typo, my mean: 'de' is the same as 'buf' not 'bh'.
> >
> > The patch code in ocfs2_search_dirblock() is:
> >    if (!ocfs2_check_dir_entry(dir, de, bh, de_buf, bytes, offset))
> >
> > the de is same as de_buf, so in ocfs2_check_dir_entry() the next_offset becomes
> >    next_offset = rlen; //because "de == de_buf"
> >
> > In this case, rlen is not the current dir entry end boundary.
> >
> > > I think 'de' is not the same as 'bh'. e.g.:
> > > - ocfs2_search_dirblock(): de is the latest dir_entry offset, bh is
> > > the ocfs2_dinode offset, buf is the first dir_entry offset
> >
> > buf is not the first dir_entry offset.
> >
> > > - ocfs2_find_dir_space_id(): de is the latest dir_entry offset, bh is
> > > the ocfs2_dinode offset, buf is the first dir_entry offset
> >
> > ditto
>
> Oh, you are right, We should use first_de here.
>
> Thanks,
> LL
>
> >
> > -Heming
> > >
> > > Thanks,
> > > LL
> > >
> > >>
> > >> 2>
> > >> 'de' is different with 'bh', bh is the first dir_entry offset, de is
> > >> the latest offset. these cases should be the expected code paths, e.g:
> > >> - __ocfs2_delete_entry()
> > >> - ocfs2_dir_foreach_blk_id()
> > >> - ocfs2_dir_foreach_blk_el()
> > >> - ocfs2_find_dir_space_el()
> > >>
> > >> If my thinking is right, case <1> needs to be revised to follow the design.
> > >>
> > >> -Heming
> > >>>
> > >>>        if (unlikely(rlen < OCFS2_DIR_REC_LEN(1)))
> > >>>                error_msg = "rec_len is smaller than minimal";
> > >>> @@ -308,6 +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(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";
> > >>>        else if (unlikely(
> > >>>                 ((char *) de - bh->b_data) + rlen > dir->i_sb->s_blocksize))
> > >>>                error_msg = "directory entry across blocks";
> > >>> @@ -352,16 +360,16 @@ 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, de_buf, bytes, offset)) {
> > >>>                                ret = -1;
> > >>>                                goto bail;
> > >>>                        }
> > >>> @@ -1138,7 +1146,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 +1643,7 @@ 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 +1782,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 +1876,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;
> > >>> @@ -3359,7 +3369,7 @@ static int ocfs2_find_dir_space_id(struct inode *dir, struct buffer_head *di_bh,
> > >>>        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, de_buf, i_size_read(dir), offset)) {
> > >>>                        ret = -ENOENT;
> > >>>                        goto out;
> > >>>                }
> > >>> @@ -3441,7 +3451,7 @@ 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;
> > >>>                }
> > >>
> >
heming.zhao@suse.com June 18, 2024, 6:43 a.m. UTC | #6
On 6/18/24 14:32, lei lu wrote:
> Hi,
> 
> Do you have further comments and/or suggestions? I will remake the
> patch to fix the issue in case <1>.
> 
> Thanks,
> LL

The rest of your patch codes look good to me.

Thanks,
Heming

> 
> On Tue, Jun 18, 2024 at 2:20 PM lei lu <llfamsec@gmail.com> wrote:
>>
>> On Tue, Jun 18, 2024 at 2:13 PM Heming Zhao <heming.zhao@suse.com> wrote:
>>>
>>> On 6/18/24 13:58, lei lu wrote:
>>>> On Tue, Jun 18, 2024 at 11:29 AM Heming Zhao <heming.zhao@suse.com> wrote:
>>>>>
>>>>> On 6/17/24 19:40, lei lu wrote:
>>>>>> This adds sanity checks for ocfs2_dir_entry to make sure all members of
>>>>>> ocfs2_dir_entry don't stray beyond valid memory region.
>>>>>>
>>>>>> Signed-off-by: lei lu <llfamsec@gmail.com>
>>>>>> Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
>>>>>> ---
>>>>>>     fs/ocfs2/dir.c | 34 ++++++++++++++++++++++------------
>>>>>>     1 file changed, 22 insertions(+), 12 deletions(-)
>>>>>>
>>>>>> diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
>>>>>> index d620d4c53c6f..562f61daf77d 100644
>>>>>> --- a/fs/ocfs2/dir.c
>>>>>> +++ b/fs/ocfs2/dir.c
>>>>>> @@ -294,13 +294,16 @@ static void ocfs2_dx_dir_name_hash(struct inode *dir, const char *name, int len,
>>>>>>      * 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;
>>>>>
>>>>> from this patch, there are two cases for the parameter 'buf':
>>>>>
>>>>> 1>
>>>>> 'de' is the same as 'bh', de & bh both are the latest dir_entry offset.
>>>>> the sanity check always passes below cases. e.g:
>>>>> - ocfs2_search_dirblock()
>>>>> - ocfs2_find_dir_space_id()
>>>>
>>>> Thanks for your time.
>>>>
>>>> Sorry, I don't quite understand what you mean.
>>>
>>> Oh, sorry for my typo, my mean: 'de' is the same as 'buf' not 'bh'.
>>>
>>> The patch code in ocfs2_search_dirblock() is:
>>>     if (!ocfs2_check_dir_entry(dir, de, bh, de_buf, bytes, offset))
>>>
>>> the de is same as de_buf, so in ocfs2_check_dir_entry() the next_offset becomes
>>>     next_offset = rlen; //because "de == de_buf"
>>>
>>> In this case, rlen is not the current dir entry end boundary.
>>>
>>>> I think 'de' is not the same as 'bh'. e.g.:
>>>> - ocfs2_search_dirblock(): de is the latest dir_entry offset, bh is
>>>> the ocfs2_dinode offset, buf is the first dir_entry offset
>>>
>>> buf is not the first dir_entry offset.
>>>
>>>> - ocfs2_find_dir_space_id(): de is the latest dir_entry offset, bh is
>>>> the ocfs2_dinode offset, buf is the first dir_entry offset
>>>
>>> ditto
>>
>> Oh, you are right, We should use first_de here.
>>
>> Thanks,
>> LL
>>
>>>
>>> -Heming
>>>>
>>>> Thanks,
>>>> LL
>>>>
>>>>>
>>>>> 2>
>>>>> 'de' is different with 'bh', bh is the first dir_entry offset, de is
>>>>> the latest offset. these cases should be the expected code paths, e.g:
>>>>> - __ocfs2_delete_entry()
>>>>> - ocfs2_dir_foreach_blk_id()
>>>>> - ocfs2_dir_foreach_blk_el()
>>>>> - ocfs2_find_dir_space_el()
>>>>>
>>>>> If my thinking is right, case <1> needs to be revised to follow the design.
>>>>>
>>>>> -Heming
>>>>>>
>>>>>>         if (unlikely(rlen < OCFS2_DIR_REC_LEN(1)))
>>>>>>                 error_msg = "rec_len is smaller than minimal";
>>>>>> @@ -308,6 +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(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";
>>>>>>         else if (unlikely(
>>>>>>                  ((char *) de - bh->b_data) + rlen > dir->i_sb->s_blocksize))
>>>>>>                 error_msg = "directory entry across blocks";
>>>>>> @@ -352,16 +360,16 @@ 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, de_buf, bytes, offset)) {
>>>>>>                                 ret = -1;
>>>>>>                                 goto bail;
>>>>>>                         }
>>>>>> @@ -1138,7 +1146,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 +1643,7 @@ 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 +1782,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 +1876,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;
>>>>>> @@ -3359,7 +3369,7 @@ static int ocfs2_find_dir_space_id(struct inode *dir, struct buffer_head *di_bh,
>>>>>>         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, de_buf, i_size_read(dir), offset)) {
>>>>>>                         ret = -ENOENT;
>>>>>>                         goto out;
>>>>>>                 }
>>>>>> @@ -3441,7 +3451,7 @@ 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;
>>>>>>                 }
>>>>>
>>>
diff mbox series

Patch

diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
index d620d4c53c6f..562f61daf77d 100644
--- a/fs/ocfs2/dir.c
+++ b/fs/ocfs2/dir.c
@@ -294,13 +294,16 @@  static void ocfs2_dx_dir_name_hash(struct inode *dir, const char *name, int len,
  * 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,6 +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(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";
 	else if (unlikely(
 		 ((char *) de - bh->b_data) + rlen > dir->i_sb->s_blocksize))
 		error_msg = "directory entry across blocks";
@@ -352,16 +360,16 @@  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, de_buf, bytes, offset)) {
 				ret = -1;
 				goto bail;
 			}
@@ -1138,7 +1146,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 +1643,7 @@  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 +1782,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 +1876,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;
@@ -3359,7 +3369,7 @@  static int ocfs2_find_dir_space_id(struct inode *dir, struct buffer_head *di_bh,
 	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, de_buf, i_size_read(dir), offset)) {
 			ret = -ENOENT;
 			goto out;
 		}
@@ -3441,7 +3451,7 @@  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;
 		}