[Devel] [PATCH v2] lib/kmapset: fix undefined behavior in kmapset_cmp()

Kirill Tkhai ktkhai at virtuozzo.com
Fri Dec 7 18:49:42 MSK 2018


On 07.12.2018 15:38, Pavel Butsykin wrote:
> Here we are dealing with undefined behavior when taking the address from lvalue
> which doesn't designate an object. The expression '&a->b' is undefined behavior
> in the C language in that case if 'a' is NULL pointer. But in most cases this
> causes no problems, until compiler starts using an aggressive optimization
> policy.
> 
> That's what I got after compiling the kernel with gcc9:
> 
> [    0.422039] BUG: unable to handle kernel paging request at fffffffffffffff0
> [    0.422557] IP: [<ffffffff9134bd9c>] kmapset_hash+0x37c/0x730
> [    0.422971] PGD 6a69e067 PUD 6a6a0067 PMD 0
> [    0.423276] Oops: 0000 [#1] SMP KASAN
> [    0.423546] Modules linked in:
> [    0.423782] CPU: 0 PID: 0 Comm: swapper/0 ve: 0 Not tainted 3.10.0-862.20.2.ovz.73.6 #67 73.6
> [    0.424341] Hardware name: Virtuozzo KVM, BIOS rel-1.10.2-0-g5f4c7b1-prebuilt.qemu-project.org 04/01/2014
> [    0.425044] task: ffffffff926a53c0 ti: ffffffff92688000 task.ti: ffffffff92688000
> [    0.425528] RIP: 0010:[<ffffffff9134bd9c>]  [<ffffffff9134bd9c>] kmapset_hash+0x37c/0x730
> [    0.426072] RSP: 0000:ffffffff9268fae8  EFLAGS: 00010a02
> [    0.426416] RAX: 0000000000000000 RBX: dffffc0000000000 RCX: 1ffffffffffffffe
> [    0.426876] RDX: 0000000000000000 RSI: 1ffffffffffffffe RDI: fffffffffffffff0
> [    0.427339] RBP: ffffffff9268fb48 R08: 0000000000000000 R09: 0000000000000000
> [    0.427947] R10: 0000000000000000 R11: 0000000000000000 R12: ffff880067191508
> [    0.428466] R13: ffffffffffffffe8 R14: ffff8800671915a0 R15: ffffffffffffffe8
> [    0.428937] FS:  0000000000000000(0000) GS:ffff880067600000(0000) knlGS:0000000000000000
> [    0.429455] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> [    0.429826] CR2: fffffffffffffff0 CR3: 000000006a69a000 CR4: 00000000000606b0
> [    0.430296] Call Trace:
> [    0.430464]  [<ffffffff9134c529>] kmapset_commit+0x99/0x160
> [    0.430828]  [<ffffffff9134c490>] ? kmapset_put+0xf0/0xf0
> [    0.431193]  [<ffffffff910bf830>] kernfs_get_ve_perms+0xc0/0x120
> [    0.431587]  [<ffffffff910b9b69>] kernfs_add_one+0x289/0x4b0
> [    0.431965]  [<ffffffff910b9e87>] kernfs_create_dir_ns+0xf7/0x150
> [    0.432358]  [<ffffffff910c2e43>] sysfs_create_dir_ns+0xc3/0x1f0
> [    0.432746]  [<ffffffff912ff4dc>] kobject_add_internal+0x1ec/0xa20
> [    0.433160]  [<ffffffff91300164>] kobject_add+0x124/0x190
> ...
> 
> It's an attempt to obtain link_a->key when map_a->links.first is NULL.
> Disassembly listing showed that this happened because the compiler optimized
> condition - "while (&link_a->map_link)". Looks like gcc noticed the check of
> map->links.first pointer to NULL in kmapset_hash() above, and decided that if
> the pointer isn't NULL then 'while' condition is always true, but if the
> pointer is NULL then it's undefined behavior and gcc doesn’t care about it.
> 
> The second undefined behavior is pointer overflow, the compiler could assumes
> &link_a->map_link cannot be NULL if the member's offset is greater than 0. In
> the case when map_a->links.first is NULL, then link_a will be equal to
> (NULL - 24) and expression &((struct kmapset_link *)(NULL - 24))->map_link will
> refer to NULL. But the problem is that POINTER_PLUS_EXPR in gcc is unsigned
> sizetype, and negative pointer (NULL - 24) appear as very large positive, so for
> this reason the compiler can also optimize this 'while' condition.
> 
> But gcc with -fno-delete-null-pointer-checks and -fno-strict-overflow flags
> should not optimize such NULL pointer checks. The kernel uses these flags, so it
> turned out to be gcc9 bug - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88367
> 
> Let's fix this UB stuff by using hlist_for_each_entry() that safely iterates
> hlist with hlist_entry_safe().
> 
> Signed-off-by: Pavel Butsykin <pbutsykin at virtuozzo.com>

Looks OK

> ---
>  v2: fix list_entry() to hlist_entry()
> 
>  lib/kmapset.c | 8 ++------
>  1 file changed, 2 insertions(+), 6 deletions(-)
> 
> diff --git a/lib/kmapset.c b/lib/kmapset.c
> index e369603b0bca..79261d0d0554 100644
> --- a/lib/kmapset.c
> +++ b/lib/kmapset.c
> @@ -40,18 +40,14 @@ static long kmapset_cmp(struct kmapset_map *map_a, struct kmapset_map *map_b)
>  	if (map_a->size != map_b->size)
>  		return map_a->size - map_b->size;
>  
> -	link_a = hlist_entry(map_a->links.first,
> -			struct kmapset_link, map_link);
>  	link_b = hlist_entry(map_b->links.first,
>  			struct kmapset_link, map_link);
> -	while (&link_a->map_link) {
> +	hlist_for_each_entry(link_a, &map_a->links, map_link) {
>  		if (link_a->key != link_b->key)
>  			return (long)link_a->key - (long)link_b->key;
>  		if (link_a->value != link_b->value)
>  			return link_a->value - link_b->value;
> -		link_a = list_entry(link_a->map_link.next,
> -				struct kmapset_link, map_link);
> -		link_b = list_entry(link_b->map_link.next,
> +		link_b = hlist_entry(link_b->map_link.next,
>  				struct kmapset_link, map_link);
>  	}
>  
> 


More information about the Devel mailing list