Yes, the code shown is potentially vulnerable to timing attack, the simplest form of side-channel attack, to determine the value of condition
. More generally, essentially any use of a variable quantity in C or Rust can leak it's value by side-channels, e.g. power analysis.
Among the few uses of a variable quantity that is generally* safe from timing attack in compiled languages (like C or Rust) on modern CPUs are bitwise and arithmetic operators & | ^ ~ + -
when operating on integer types; casting among these types; shift (<< >>
) of such quantity if the shift count is non-secret (including a public constant shift). Also, reading from or writing to a variable (or a vector at a non-secret offset) is safe.
With this in mind, assuming a
and b
are of unsigned
type, and condition
is 0
or 1
and of type ìnt
, this code is provably equivalent to the one in the question and is less unlikely to be constant-time:
unsigned ai = a ^ b;
unsigned bi = ((-(unsigned)conditional) & ai) ^ a; // b if conditional, a otherwise
ai ^= bi; // a if conditional, b otherwise
f(ai, bi);
g(bi, ai);
This produces the correct result because (-(unsigned)conditional)
has all it's bit at 0
if conditional
is 0
, and all it's bit at 1
if conditional
is 1
. This is an insurance of the C language.
This solves the data-dependent timing in the code shown, but is still potentially vulnerable to other side-channel attacks, e.g. power analysis. And of course there remains the potential for data-dependent timing inside the code of f
and g
.
* It's still advisable to check the generated code, especially when using signed quantities. An example is uint64_t j = i
where i
is an ìnt8_t
variable: duration conceivably could depend on the sign of i
on some combinations of CPU and compiler.