Skip to content

Instances of operations on uninitialized values #753

@elichai

Description

@elichai

Hi,
Currently we have 2 instances of operations on uninitialized values, in the cmov implementation.
all the _cmov implementations read from the resulting variable(ie r->n[0] & mask0) so if r points to uninitialized values this is undefined behavior [1] [2]
Unlike the memcpy debate, actual user operations and conditions on uninit values are assumed to not happen in the compiler and are used for optimizations (especially in clang)

Instance 1:
In ecdsa_sign, if noncefp returns 0 then we get to:

secp256k1/src/secp256k1.c

Lines 517 to 518 in 41fc785

secp256k1_scalar_cmov(&r, &secp256k1_scalar_zero, !ret);
secp256k1_scalar_cmov(&s, &secp256k1_scalar_zero, !ret);

with both r and s being unitialized.
Instance 2:
In ecdh, we pass &tmpa here:
ECMULT_CONST_TABLE_GET_GE(&tmpa, pre_a, i, WINDOW_A);
to the ECMULT_CONST_TABLE_GET_GE in an uninit state, which become the r value in the macro.
Then in:
secp256k1_fe_cmov(&(r)->x, &(pre)[m].x, m == idx_n); \
secp256k1_fe_cmov(&(r)->y, &(pre)[m].y, m == idx_n); \

we cmov into &(r)->x which will perform operations on uninit values.
notice that under VERIFY we actually do clear those before:
VERIFY_SETUP(secp256k1_fe_clear(&(r)->x)); \
VERIFY_SETUP(secp256k1_fe_clear(&(r)->y)); \

Possible solutions:
The only solution I can currently think of is default initializing those arguments.

Mitigations:
We could add VALGRIND_CHECK_MEM_IS_DEFINED(r, sizeof(*r)); to all the cmovs but we only enable valgrind(-DVALGRIND) under tests, which use the VERIFY macro too, so that will not catch the second case.

[1] http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_451.htm
[2] https://markshroyer.com/2012/06/c-both-true-and-false/

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions