Re: Q: why didn't GCC warn about this uninitialized variable? (was: Re: [PATCH] perf tests: initialize sa.sa_flags)

From: Ingo Molnar
Date: Thu Mar 03 2016 - 08:47:25 EST



* Ingo Molnar <mingo@xxxxxxxxxx> wrote:

> So it's all highly inefficient and fragile.
>
> There's also another cost, the cost of finding the bugs themselves - for example
> here's a recent upstream kernel fix:
>
> commit e01d8718de4170373cd7fbf5cf6f9cb61cebb1e9
> Author: Peter Zijlstra <peterz@xxxxxxxxxxxxx>
> Date: Wed Jan 27 23:24:29 2016 +0100
>
> perf/x86: Fix uninitialized value usage
>
> When calling intel_alt_er() with .idx != EXTRA_REG_RSP_* we will not
> initialize alt_idx and then use this uninitialized value to index an
> array.
>
> When that is not fatal, it can result in an infinite loop in its
> caller __intel_shared_reg_get_constraints(), with IRQs disabled.
>
> Alternative error modes are random memory corruption due to the
> cpuc->shared_regs->regs[] array overrun, which manifest in either
> get_constraints or put_constraints doing weird stuff.
>
> Only took 6 hours of painful debugging to find this. Neither GCC nor
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> Smatch warnings flagged this bug.
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>
> --- a/arch/x86/kernel/cpu/perf_event_intel.c
> +++ b/arch/x86/kernel/cpu/perf_event_intel.c
> @@ -1960,7 +1960,8 @@ intel_bts_constraints(struct perf_event *event)
>
> static int intel_alt_er(int idx, u64 config)
> {
> - int alt_idx;
> + int alt_idx = idx;
> +
> if (!(x86_pmu.flags & PMU_FL_HAS_RSP_1))
> return idx;
>
> 6 hours of PeterZ time translates to quite a bit of code restructuring overhead to
> eliminate false positive warnings...

Btw., here's the wider context of that bug:

static int intel_alt_er(int idx, u64 config)
{
int alt_idx;

if (!(x86_pmu.flags & PMU_FL_HAS_RSP_1))
return idx;

if (idx == EXTRA_REG_RSP_0)
alt_idx = EXTRA_REG_RSP_1;

if (idx == EXTRA_REG_RSP_1)
alt_idx = EXTRA_REG_RSP_0;

if (config & ~x86_pmu.extra_regs[alt_idx].valid_mask)
return idx;

return alt_idx;
}

so it's a straightforward uninitialized variable bug.

I tried to distill a testcase out of it, and the following silly hack seems to
trigger it:

------------------------------->
#include <stdio.h>

#define PMU_FL_HAS_RSP_1 1
#define EXTRA_REG_RSP_1 2
#define EXTRA_REG_RSP_0 4

int global_flags = -1;

static int intel_alt_er(int idx, unsigned long long config)
{
int alt_idx;
int uninitialized = 1;

printf("idx: %d, config: %Ld\n", idx, config);

if (!(global_flags & PMU_FL_HAS_RSP_1))
return idx;

if (idx == EXTRA_REG_RSP_0) {
alt_idx = EXTRA_REG_RSP_1;
uninitialized = 0;
}

if (idx == EXTRA_REG_RSP_1) {
alt_idx = EXTRA_REG_RSP_0;
uninitialized = 0;
}

if (config & ~0xff)
return idx;

if (uninitialized)
printf("ugh, using uninitialized alt_idx (%d)!\n", alt_idx);

return alt_idx;
}

int main(int argc, char **argv)
{
argv++;

return intel_alt_er(argc, argc);
}
<-------------------------------

built with:

gcc -Wbad-function-cast -Wdeclaration-after-statement -Wformat-security -Wformat-y2k \
-Winit-self -Wmissing-declarations -Wmissing-prototypes -Wnested-externs \
-Wno-system-headers -Wold-style-definition -Wpacked -Wredundant-decls \
-Wshadow -Wstrict-aliasing=3 -Wstrict-prototypes -Wswitch-default -Wswitch-enum \
-Wundef -Wwrite-strings -Wformat \
-Werror -O6 -fno-omit-frame-pointer -ggdb3 -funwind-tables -Wall -Wextra -std=gnu99 -fstack-protector-all -D_FORTIFY_SOURCE=2 \
-o uninitialized uninitialized.c

gives:

triton:~> ./uninitialized 1
idx: 2, config: 2

triton:~> ./uninitialized 0 0
idx: 3, config: 3
ugh, using uninitialized alt_idx (2)!

I.e. I cannot get GCC to warn about this seemingly trivial bug, using:

gcc version 5.2.1 20151010 (Ubuntu 5.2.1-22ubuntu2)

Thanks,

Ingo