lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Fri, 01 Jan 2021 12:18:20 +1100
From:   Daniel Axtens <dja@...ens.net>
To:     David Howells <dhowells@...hat.com>
Cc:     dhowells@...hat.com, torvalds@...ux-foundation.org,
        marc.dionne@...istor.com, linux-afs@...ts.infradead.org,
        linux-fsdevel@...r.kernel.org, linux-kernel@...r.kernel.org
Subject: Re: [RFC][PATCH] afs: Work around strnlen() oops with CONFIG_FORTIFIED_SOURCE=y

Hi David,

> CONFIG_FORTIFIED_SOURCE=y now causes an oops in strnlen() from afs (see
> attached patch for an explanation).  Is replacing the use with memchr() the
> right approach?  Or should I be calling __real_strnlen() or whatever it's
> called?

You certainly shouldn't be calling __real_strnlen().

memchr() is probably the right answer if you want a small fix.

However, as Linus suggested, the 'right' answer is to re-engineer your
data structures so that the string operations you do don't overflow
their arrays.

Kind regards,
Daniel

>
> David
> ---
> From: David Howells <dhowells@...hat.com>
>
> afs: Work around strnlen() oops with CONFIG_FORTIFIED_SOURCE=y
>
> AFS has a structured layout in its directory contents (AFS dirs are
> downloaded as files and parsed locally by the client for lookup/readdir).
> The slots in the directory are defined by union afs_xdr_dirent.  This,
> however, only directly allows a name of a length that will fit into that
> union.  To support a longer name, the next 1-8 contiguous entries are
> annexed to the first one and the name flows across these.
>
> afs_dir_iterate_block() uses strnlen(), limited to the space to the end of
> the page, to find out how long the name is.  This worked fine until
> 6a39e62abbaf.  With that commit, the compiler determines the size of the
> array and asserts that the string fits inside that array.  This is a
> problem for AFS because we *expect* it to overflow one or more arrays.
>
> A similar problem also occurs in afs_dir_scan_block() when a directory file
> is being locally edited to avoid the need to redownload it.  There strlen()
> was being used safely because each page has the last byte set to 0 when the
> file is downloaded and validated (in afs_dir_check_page()).
>
> Fix this by using memchr() instead and hoping no one changes that to check
> the object size.
>
> The issue can be triggered by something like:
>
>         touch /afs/example.com/thisisaveryveryverylongname
>
> and it generates a report that looks like:
>
>         detected buffer overflow in strnlen
>         ------------[ cut here ]------------
>         kernel BUG at lib/string.c:1149!
>         ...
>         RIP: 0010:fortify_panic+0xf/0x11
>         ...
>         Call Trace:
>          afs_dir_iterate_block+0x12b/0x35b
>          afs_dir_iterate+0x14e/0x1ce
>          afs_do_lookup+0x131/0x417
>          afs_lookup+0x24f/0x344
>          lookup_open.isra.0+0x1bb/0x27d
>          open_last_lookups+0x166/0x237
>          path_openat+0xe0/0x159
>          do_filp_open+0x48/0xa4
>          ? kmem_cache_alloc+0xf5/0x16e
>          ? __clear_close_on_exec+0x13/0x22
>          ? _raw_spin_unlock+0xa/0xb
>          do_sys_openat2+0x72/0xde
>          do_sys_open+0x3b/0x58
>          do_syscall_64+0x2d/0x3a
>          entry_SYSCALL_64_after_hwframe+0x44/0xa9
>
> Fixes: 6a39e62abbaf ("lib: string.h: detect intra-object overflow in fortified string functions")
> Reported-by: Marc Dionne <marc.dionne@...istor.com>
> Signed-off-by: David Howells <dhowells@...hat.com>
> cc: Daniel Axtens <dja@...ens.net>
>
> diff --git a/fs/afs/dir.c b/fs/afs/dir.c
> index 9068d5578a26..4fafb4e4d0df 100644
> --- a/fs/afs/dir.c
> +++ b/fs/afs/dir.c
> @@ -350,6 +350,7 @@ static int afs_dir_iterate_block(struct afs_vnode *dvnode,
>  				 unsigned blkoff)
>  {
>  	union afs_xdr_dirent *dire;
> +	const u8 *p;
>  	unsigned offset, next, curr;
>  	size_t nlen;
>  	int tmp;
> @@ -378,9 +379,15 @@ static int afs_dir_iterate_block(struct afs_vnode *dvnode,
>  
>  		/* got a valid entry */
>  		dire = &block->dirents[offset];
> -		nlen = strnlen(dire->u.name,
> -			       sizeof(*block) -
> -			       offset * sizeof(union afs_xdr_dirent));
> +		p = memchr(dire->u.name, 0,
> +			   sizeof(*block) - offset * sizeof(union afs_xdr_dirent));
> +		if (!p) {
> +			_debug("ENT[%zu.%u]: %u unterminated dirent name",
> +			       blkoff / sizeof(union afs_xdr_dir_block),
> +			       offset, next);
> +			return afs_bad(dvnode, afs_file_error_dir_over_end);
> +		}
> +		nlen = p - dire->u.name;
>  
>  		_debug("ENT[%zu.%u]: %s %zu \"%s\"",
>  		       blkoff / sizeof(union afs_xdr_dir_block), offset,
> diff --git a/fs/afs/dir_edit.c b/fs/afs/dir_edit.c
> index 2ffe09abae7f..5ee4e992ed8f 100644
> --- a/fs/afs/dir_edit.c
> +++ b/fs/afs/dir_edit.c
> @@ -111,6 +111,8 @@ static int afs_dir_scan_block(union afs_xdr_dir_block *block, struct qstr *name,
>  			      unsigned int blocknum)
>  {
>  	union afs_xdr_dirent *de;
> +	const u8 *p;
> +	unsigned long offset;
>  	u64 bitmap;
>  	int d, len, n;
>  
> @@ -135,7 +137,11 @@ static int afs_dir_scan_block(union afs_xdr_dir_block *block, struct qstr *name,
>  			continue;
>  
>  		/* The block was NUL-terminated by afs_dir_check_page(). */
> -		len = strlen(de->u.name);
> +		offset = (unsigned long)de->u.name & (PAGE_SIZE - 1);
> +		p = memchr(de->u.name, 0, PAGE_SIZE - offset);
> +		if (!p)
> +			return -1;
> +		len = p - de->u.name;
>  		if (len == name->len &&
>  		    memcmp(de->u.name, name->name, name->len) == 0)
>  			return d;

Powered by blists - more mailing lists