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@xxxxxxxxxx> > > 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@xxxxxxxxxxxx> > Signed-off-by: David Howells <dhowells@xxxxxxxxxx> > cc: Daniel Axtens <dja@xxxxxxxxxx> > > 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;