mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2] isofs: validate directory records in isofs_read_level3_size()
@ 2026-09-23 15:32 Matthias Goergens
  2026-09-23 17:13 ` Jan Kara
  0 siblings, 1 reply; 4+ messages in thread
From: Matthias Goergens @ 2026-09-23 15:32 UTC (permalink / raw)
  To: Jan Kara
  Cc: Christian Brauner, chenyichong, linux-fsdevel, linux-kernel,
	Matthias Goergens

isofs_read_level3_size() walks the multi-extent directory records of a
file and dereferences de->size and de->flags for each one without ever
checking the record's length byte.  A record with a short length placed
near the end of a block makes those fixed-field reads run past the
record, and for a record at the end of the last block of a page, past
the buffer.

readdir, lookup and the NFS get_parent path have validated every record
with isofs_dir_record_valid() since commit e2ee4078ec58 ("isofs:
validate directory records consistently").  Do the same here, after
moving on to the next block when the previous record ended exactly at
the end of the block or the rest of the block is zero padding, as
commit 3c01d9263683 ("isofs: Fix handling of directories with tight
blocks") does for the other two walkers.

The check rejects a record that would run past its block, so the code
that reassembled such a record from two blocks can no longer run.
ECMA-119 does not allow directory records to straddle sector
boundaries, which is what commit b2eb2e288604 ("isofs: Drop support of
directory entries straddling blocks") relied on when it removed the
same code from readdir and lookup.  Remove it here too, along with its
temporary record buffer.

Found by fuzzing fs/isofs in a userspace harness with ASan
(heap-buffer-overflow reads in isonum_733(de->size)).

Signed-off-by: Matthias Goergens <matthias.goergens@gmail.com>
---
v2: squash the two patches into one, so the record is checked only
after the move to the next block, as Jan suggested.  In v1, 1/2 relied
on the straddle code that 2/2 then removed to handle a record ending
exactly at the end of a block.  The resulting code is the same as after
v1's 2/2.  Based on jack/linux-fs for_next, and cites the tight-block
fix as 3c01d9263683.

isofs_read_inode() still reassembles a first record that straddles a
block.  Only an NFS file handle can point it at one now, and the level
3 walk then returns -EIO.  I can send a follow-up that makes it reject
such records too.

v1: https://lore.kernel.org/all/20260922140137.1768064-1-matthias.goergens@gmail.com/

 fs/isofs/inode.c | 44 +++++++++++++-------------------------------
 1 file changed, 13 insertions(+), 31 deletions(-)

diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c
index b766b5c9c593b..184350d2e6ad5 100644
--- a/fs/isofs/inode.c
+++ b/fs/isofs/inode.c
@@ -1176,7 +1176,6 @@ static int isofs_read_level3_size(struct inode *inode)
 	unsigned long block, offset, block_saved, offset_saved;
 	int i = 0;
 	int more_entries = 0;
-	struct iso_directory_record *tmpde = NULL;
 	struct iso_inode_info *ei = ISOFS_I(inode);
 
 	inode->i_size = 0;
@@ -1200,9 +1199,12 @@ static int isofs_read_level3_size(struct inode *inode)
 				goto out_noread;
 		}
 		de = (struct iso_directory_record *) (bh->b_data + offset);
-		de_len = *(unsigned char *) de;
 
-		if (de_len == 0) {
+		/*
+		 * If we are at the end of a block (or at its zero-padded
+		 * tail), move on to the next block.
+		 */
+		if (offset >= bufsize || de->length[0] == 0) {
 			brelse(bh);
 			bh = NULL;
 			++block;
@@ -1210,32 +1212,18 @@ static int isofs_read_level3_size(struct inode *inode)
 			continue;
 		}
 
+		if (!isofs_dir_record_valid(de, offset, bufsize)) {
+			printk(KERN_NOTICE "iso9660: Corrupted directory entry in block %lu of inode %llu\n",
+			       block, inode->i_ino);
+			brelse(bh);
+			return -EIO;
+		}
+
+		de_len = de->length[0];
 		block_saved = block;
 		offset_saved = offset;
 		offset += de_len;
 
-		/* Make sure we have a full directory entry */
-		if (offset >= bufsize) {
-			int slop = bufsize - offset + de_len;
-			if (!tmpde) {
-				tmpde = kmalloc(256, GFP_KERNEL);
-				if (!tmpde)
-					goto out_nomem;
-			}
-			memcpy(tmpde, de, slop);
-			offset &= bufsize - 1;
-			block++;
-			brelse(bh);
-			bh = NULL;
-			if (offset) {
-				bh = sb_bread(inode->i_sb, block);
-				if (!bh)
-					goto out_noread;
-				memcpy((void *)tmpde+slop, bh->b_data, offset);
-			}
-			de = tmpde;
-		}
-
 		inode->i_size += isonum_733(de->size);
 		if (i == 1) {
 			ei->i_next_section_block = block_saved;
@@ -1249,17 +1237,11 @@ static int isofs_read_level3_size(struct inode *inode)
 			goto out_toomany;
 	} while (more_entries);
 out:
-	kfree(tmpde);
 	brelse(bh);
 	return 0;
 
-out_nomem:
-	brelse(bh);
-	return -ENOMEM;
-
 out_noread:
 	printk(KERN_INFO "ISOFS: unable to read i-node block %lu\n", block);
-	kfree(tmpde);
 	return -EIO;
 
 out_toomany:

base-commit: c8437ca3d4386af1ae1f2869643e82fdb9c1f0f5
-- 
2.55.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v2] isofs: validate directory records in isofs_read_level3_size()
  2026-09-23 15:32 [PATCH v2] isofs: validate directory records in isofs_read_level3_size() Matthias Goergens
@ 2026-09-23 17:13 ` Jan Kara
  2026-09-23 17:52   ` Matthias Goergens
  0 siblings, 1 reply; 4+ messages in thread
From: Jan Kara @ 2026-09-23 17:13 UTC (permalink / raw)
  To: Matthias Goergens
  Cc: Jan Kara, Christian Brauner, chenyichong, linux-fsdevel, linux-kernel

On Wed 23-09-26 23:32:20, Matthias Goergens wrote:
> isofs_read_level3_size() walks the multi-extent directory records of a
> file and dereferences de->size and de->flags for each one without ever
> checking the record's length byte.  A record with a short length placed
> near the end of a block makes those fixed-field reads run past the
> record, and for a record at the end of the last block of a page, past
> the buffer.
> 
> readdir, lookup and the NFS get_parent path have validated every record
> with isofs_dir_record_valid() since commit e2ee4078ec58 ("isofs:
> validate directory records consistently").  Do the same here, after
> moving on to the next block when the previous record ended exactly at
> the end of the block or the rest of the block is zero padding, as
> commit 3c01d9263683 ("isofs: Fix handling of directories with tight
> blocks") does for the other two walkers.
> 
> The check rejects a record that would run past its block, so the code
> that reassembled such a record from two blocks can no longer run.
> ECMA-119 does not allow directory records to straddle sector
> boundaries, which is what commit b2eb2e288604 ("isofs: Drop support of
> directory entries straddling blocks") relied on when it removed the
> same code from readdir and lookup.  Remove it here too, along with its
> temporary record buffer.
> 
> Found by fuzzing fs/isofs in a userspace harness with ASan
> (heap-buffer-overflow reads in isonum_733(de->size)).
> 
> Signed-off-by: Matthias Goergens <matthias.goergens@gmail.com>

Thanks! I've added the patch to my tree.

								Honza

> ---
> v2: squash the two patches into one, so the record is checked only
> after the move to the next block, as Jan suggested.  In v1, 1/2 relied
> on the straddle code that 2/2 then removed to handle a record ending
> exactly at the end of a block.  The resulting code is the same as after
> v1's 2/2.  Based on jack/linux-fs for_next, and cites the tight-block
> fix as 3c01d9263683.
> 
> isofs_read_inode() still reassembles a first record that straddles a
> block.  Only an NFS file handle can point it at one now, and the level
> 3 walk then returns -EIO.  I can send a follow-up that makes it reject
> such records too.
> 
> v1: https://lore.kernel.org/all/20260922140137.1768064-1-matthias.goergens@gmail.com/
> 
>  fs/isofs/inode.c | 44 +++++++++++++-------------------------------
>  1 file changed, 13 insertions(+), 31 deletions(-)
> 
> diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c
> index b766b5c9c593b..184350d2e6ad5 100644
> --- a/fs/isofs/inode.c
> +++ b/fs/isofs/inode.c
> @@ -1176,7 +1176,6 @@ static int isofs_read_level3_size(struct inode *inode)
>  	unsigned long block, offset, block_saved, offset_saved;
>  	int i = 0;
>  	int more_entries = 0;
> -	struct iso_directory_record *tmpde = NULL;
>  	struct iso_inode_info *ei = ISOFS_I(inode);
>  
>  	inode->i_size = 0;
> @@ -1200,9 +1199,12 @@ static int isofs_read_level3_size(struct inode *inode)
>  				goto out_noread;
>  		}
>  		de = (struct iso_directory_record *) (bh->b_data + offset);
> -		de_len = *(unsigned char *) de;
>  
> -		if (de_len == 0) {
> +		/*
> +		 * If we are at the end of a block (or at its zero-padded
> +		 * tail), move on to the next block.
> +		 */
> +		if (offset >= bufsize || de->length[0] == 0) {
>  			brelse(bh);
>  			bh = NULL;
>  			++block;
> @@ -1210,32 +1212,18 @@ static int isofs_read_level3_size(struct inode *inode)
>  			continue;
>  		}
>  
> +		if (!isofs_dir_record_valid(de, offset, bufsize)) {
> +			printk(KERN_NOTICE "iso9660: Corrupted directory entry in block %lu of inode %llu\n",
> +			       block, inode->i_ino);
> +			brelse(bh);
> +			return -EIO;
> +		}
> +
> +		de_len = de->length[0];
>  		block_saved = block;
>  		offset_saved = offset;
>  		offset += de_len;
>  
> -		/* Make sure we have a full directory entry */
> -		if (offset >= bufsize) {
> -			int slop = bufsize - offset + de_len;
> -			if (!tmpde) {
> -				tmpde = kmalloc(256, GFP_KERNEL);
> -				if (!tmpde)
> -					goto out_nomem;
> -			}
> -			memcpy(tmpde, de, slop);
> -			offset &= bufsize - 1;
> -			block++;
> -			brelse(bh);
> -			bh = NULL;
> -			if (offset) {
> -				bh = sb_bread(inode->i_sb, block);
> -				if (!bh)
> -					goto out_noread;
> -				memcpy((void *)tmpde+slop, bh->b_data, offset);
> -			}
> -			de = tmpde;
> -		}
> -
>  		inode->i_size += isonum_733(de->size);
>  		if (i == 1) {
>  			ei->i_next_section_block = block_saved;
> @@ -1249,17 +1237,11 @@ static int isofs_read_level3_size(struct inode *inode)
>  			goto out_toomany;
>  	} while (more_entries);
>  out:
> -	kfree(tmpde);
>  	brelse(bh);
>  	return 0;
>  
> -out_nomem:
> -	brelse(bh);
> -	return -ENOMEM;
> -
>  out_noread:
>  	printk(KERN_INFO "ISOFS: unable to read i-node block %lu\n", block);
> -	kfree(tmpde);
>  	return -EIO;
>  
>  out_toomany:
> 
> base-commit: c8437ca3d4386af1ae1f2869643e82fdb9c1f0f5
> -- 
> 2.55.0
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v2] isofs: validate directory records in isofs_read_level3_size()
  2026-09-23 17:13 ` Jan Kara
@ 2026-09-23 17:52   ` Matthias Goergens
  2026-09-23 18:28     ` Jan Kara
  0 siblings, 1 reply; 4+ messages in thread
From: Matthias Goergens @ 2026-09-23 17:52 UTC (permalink / raw)
  To: Jan Kara; +Cc: Christian Brauner, chenyichong, linux-fsdevel, linux-kernel

On Wed 23-09-26 19:13:03, Jan Kara wrote:
> Thanks! I've added the patch to my tree.

Thanks!  One correction to my commit message, if you're still able to
edit it: the out-of-bounds reads from all three fuzzer reproducers are
on de->flags, not in isonum_733(de->size).  The last paragraph would
then read:

  Found by fuzzing fs/isofs in a userspace harness with ASan
  (heap-buffer-overflow reads of de->flags).

Matthias

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v2] isofs: validate directory records in isofs_read_level3_size()
  2026-09-23 17:52   ` Matthias Goergens
@ 2026-09-23 18:28     ` Jan Kara
  0 siblings, 0 replies; 4+ messages in thread
From: Jan Kara @ 2026-09-23 18:28 UTC (permalink / raw)
  To: Matthias Goergens
  Cc: Jan Kara, Christian Brauner, chenyichong, linux-fsdevel, linux-kernel

On Thu 24-09-26 01:52:11, Matthias Goergens wrote:
> On Wed 23-09-26 19:13:03, Jan Kara wrote:
> > Thanks! I've added the patch to my tree.
> 
> Thanks!  One correction to my commit message, if you're still able to
> edit it: the out-of-bounds reads from all three fuzzer reproducers are
> on de->flags, not in isonum_733(de->size).  The last paragraph would
> then read:
> 
>   Found by fuzzing fs/isofs in a userspace harness with ASan
>   (heap-buffer-overflow reads of de->flags).

No problem. Updated.

								Honza
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-09-23 18:28 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-23 15:32 [PATCH v2] isofs: validate directory records in isofs_read_level3_size() Matthias Goergens
2026-09-23 17:13 ` Jan Kara
2026-09-23 17:52   ` Matthias Goergens
2026-09-23 18:28     ` Jan Kara

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®