Skip to content

Commit d95ca91

Browse files
FiloSottilerolandshoemaker
authored andcommitted
crypto/elliptic: fix P-224 field reduction
This patch fixes two independent bugs in p224Contract, the function that performs the final complete reduction in the P-224 field. Incorrect outputs due to these bugs were observable from a high-level P224().ScalarMult() call. The first bug was in the calculation of out3GT. That mask was supposed to be all ones if the third limb of the value is greater than the third limb of P (out[3] > 0xffff000). Instead, it was also set if they are equal. That meant that if the third limb was equal, the value was always considered greater than or equal to P, even when the three bottom limbs were all zero. There is exactly one affected value, P - 1, which would trigger the subtraction by P even if it's lower than P already. The second bug was more easily hit, and is the one that caused the known high-level incorrect output: after the conditional subtraction by P, a potential underflow of the lowest limb was not handled. Any values that trigger the subtraction by P (values between P and 2^224-1, and P - 1 due to the bug above) but have a zero lowest limb would produce invalid outputs. Those conditions apply to the intermediate representation before the subtraction, so they are hard to trace to precise inputs. This patch also adds a test suite for the P-224 field arithmetic, including a custom fuzzer that automatically explores potential edge cases by combining limb values that have various meanings in the code. contractMatchesBigInt in TestP224Contract finds the second bug in less than a second without being tailored to it, and could eventually find the first one too by combining 0, (1 << 28) - 1, and the difference of (1 << 28) and (1 << 12). The incorrect P224().ScalarMult() output was found by the elliptic-curve-differential-fuzzer project running on OSS-Fuzz and reported by Philippe Antoine (Catena cyber). Fixes CVE-2021-3114 Fixes #43786 Change-Id: I50176602d544de3da854270d66a293bcaca57ad7 Reviewed-on: https://go-review.googlesource.com/c/go/+/284779 Run-TryBot: Roland Shoemaker <[email protected]> TryBot-Result: Go Bot <[email protected]> Trust: Ian Lance Taylor <[email protected]> Trust: Roland Shoemaker <[email protected]> Reviewed-by: Filippo Valsorda <[email protected]>
1 parent ecf4ebf commit d95ca91

File tree

2 files changed

+298
-20
lines changed

2 files changed

+298
-20
lines changed

src/crypto/elliptic/p224.go

+26-15
Original file line numberDiff line numberDiff line change
@@ -386,21 +386,25 @@ func p224Invert(out, in *p224FieldElement) {
386386
// p224Contract converts a FieldElement to its unique, minimal form.
387387
//
388388
// On entry, in[i] < 2**29
389-
// On exit, in[i] < 2**28
389+
// On exit, out[i] < 2**28 and out < p
390390
func p224Contract(out, in *p224FieldElement) {
391391
copy(out[:], in[:])
392392

393+
// First, carry the bits above 28 to the higher limb.
393394
for i := 0; i < 7; i++ {
394395
out[i+1] += out[i] >> 28
395396
out[i] &= bottom28Bits
396397
}
397398
top := out[7] >> 28
398399
out[7] &= bottom28Bits
399400

401+
// Use the reduction identity to carry the overflow.
402+
//
403+
// a + top * 2²²⁴ = a + top * 2⁹⁶ - top
400404
out[0] -= top
401405
out[3] += top << 12
402406

403-
// We may just have made out[i] negative. So we carry down. If we made
407+
// We may just have made out[0] negative. So we carry down. If we made
404408
// out[0] negative then we know that out[3] is sufficiently positive
405409
// because we just added to it.
406410
for i := 0; i < 3; i++ {
@@ -425,13 +429,12 @@ func p224Contract(out, in *p224FieldElement) {
425429
// There are two cases to consider for out[3]:
426430
// 1) The first time that we eliminated top, we didn't push out[3] over
427431
// 2**28. In this case, the partial carry chain didn't change any values
428-
// and top is zero.
432+
// and top is now zero.
429433
// 2) We did push out[3] over 2**28 the first time that we eliminated top.
430-
// The first value of top was in [0..16), therefore, prior to eliminating
431-
// the first top, 0xfff1000 <= out[3] <= 0xfffffff. Therefore, after
432-
// overflowing and being reduced by the second carry chain, out[3] <=
433-
// 0xf000. Thus it cannot have overflowed when we eliminated top for the
434-
// second time.
434+
// The first value of top was in [0..2], therefore, after overflowing
435+
// and being reduced by the second carry chain, out[3] <= 2<<12 - 1.
436+
// In both cases, out[3] cannot have overflowed when we eliminated top for
437+
// the second time.
435438

436439
// Again, we may just have made out[0] negative, so do the same carry down.
437440
// As before, if we made out[0] negative then we know that out[3] is
@@ -470,12 +473,11 @@ func p224Contract(out, in *p224FieldElement) {
470473
bottom3NonZero |= bottom3NonZero >> 1
471474
bottom3NonZero = uint32(int32(bottom3NonZero<<31) >> 31)
472475

473-
// Everything depends on the value of out[3].
474-
// If it's > 0xffff000 and top4AllOnes != 0 then the whole value is >= p
475-
// If it's = 0xffff000 and top4AllOnes != 0 and bottom3NonZero != 0,
476-
// then the whole value is >= p
476+
// Assuming top4AllOnes != 0, everything depends on the value of out[3].
477+
// If it's > 0xffff000 then the whole value is > p
478+
// If it's = 0xffff000 and bottom3NonZero != 0, then the whole value is >= p
477479
// If it's < 0xffff000, then the whole value is < p
478-
n := out[3] - 0xffff000
480+
n := 0xffff000 - out[3]
479481
out3Equal := n
480482
out3Equal |= out3Equal >> 16
481483
out3Equal |= out3Equal >> 8
@@ -484,8 +486,8 @@ func p224Contract(out, in *p224FieldElement) {
484486
out3Equal |= out3Equal >> 1
485487
out3Equal = ^uint32(int32(out3Equal<<31) >> 31)
486488

487-
// If out[3] > 0xffff000 then n's MSB will be zero.
488-
out3GT := ^uint32(int32(n) >> 31)
489+
// If out[3] > 0xffff000 then n's MSB will be one.
490+
out3GT := uint32(int32(n) >> 31)
489491

490492
mask := top4AllOnes & ((out3Equal & bottom3NonZero) | out3GT)
491493
out[0] -= 1 & mask
@@ -494,6 +496,15 @@ func p224Contract(out, in *p224FieldElement) {
494496
out[5] -= 0xfffffff & mask
495497
out[6] -= 0xfffffff & mask
496498
out[7] -= 0xfffffff & mask
499+
500+
// Do one final carry down, in case we made out[0] negative. One of
501+
// out[0..3] needs to be positive and able to absorb the -1 or the value
502+
// would have been < p, and the subtraction wouldn't have happened.
503+
for i := 0; i < 3; i++ {
504+
mask := uint32(int32(out[i]) >> 31)
505+
out[i] += (1 << 28) & mask
506+
out[i+1] -= 1 & mask
507+
}
497508
}
498509

499510
// Group element functions.

src/crypto/elliptic/p224_test.go

+272-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ package elliptic
66

77
import (
88
"math/big"
9+
"math/bits"
10+
"math/rand"
11+
"reflect"
912
"testing"
13+
"testing/quick"
1014
)
1115

1216
var toFromBigTests = []string{
@@ -21,16 +25,16 @@ func p224AlternativeToBig(in *p224FieldElement) *big.Int {
2125
ret := new(big.Int)
2226
tmp := new(big.Int)
2327

24-
for i := uint(0); i < 8; i++ {
28+
for i := len(in) - 1; i >= 0; i-- {
29+
ret.Lsh(ret, 28)
2530
tmp.SetInt64(int64(in[i]))
26-
tmp.Lsh(tmp, 28*i)
2731
ret.Add(ret, tmp)
2832
}
29-
ret.Mod(ret, p224.P)
33+
ret.Mod(ret, P224().Params().P)
3034
return ret
3135
}
3236

33-
func TestToFromBig(t *testing.T) {
37+
func TestP224ToFromBig(t *testing.T) {
3438
for i, test := range toFromBigTests {
3539
n, _ := new(big.Int).SetString(test, 16)
3640
var x p224FieldElement
@@ -41,7 +45,270 @@ func TestToFromBig(t *testing.T) {
4145
}
4246
q := p224AlternativeToBig(&x)
4347
if n.Cmp(q) != 0 {
44-
t.Errorf("#%d: %x != %x (alternative)", i, n, m)
48+
t.Errorf("#%d: %x != %x (alternative)", i, n, q)
4549
}
4650
}
4751
}
52+
53+
// quickCheckConfig32 will make each quickcheck test run (32 * -quickchecks)
54+
// times. The default value of -quickchecks is 100.
55+
var quickCheckConfig32 = &quick.Config{MaxCountScale: 32}
56+
57+
// weirdLimbs can be combined to generate a range of edge-case field elements.
58+
var weirdLimbs = [...]uint32{
59+
0, 1, (1 << 29) - 1,
60+
(1 << 12), (1 << 12) - 1,
61+
(1 << 28), (1 << 28) - 1,
62+
}
63+
64+
func generateLimb(rand *rand.Rand) uint32 {
65+
const bottom29Bits = 0x1fffffff
66+
n := rand.Intn(len(weirdLimbs) + 3)
67+
switch n {
68+
case len(weirdLimbs):
69+
// Random value.
70+
return uint32(rand.Int31n(1 << 29))
71+
case len(weirdLimbs) + 1:
72+
// Sum of two values.
73+
k := generateLimb(rand) + generateLimb(rand)
74+
return k & bottom29Bits
75+
case len(weirdLimbs) + 2:
76+
// Difference of two values.
77+
k := generateLimb(rand) - generateLimb(rand)
78+
return k & bottom29Bits
79+
default:
80+
return weirdLimbs[n]
81+
}
82+
}
83+
84+
func (p224FieldElement) Generate(rand *rand.Rand, size int) reflect.Value {
85+
return reflect.ValueOf(p224FieldElement{
86+
generateLimb(rand),
87+
generateLimb(rand),
88+
generateLimb(rand),
89+
generateLimb(rand),
90+
generateLimb(rand),
91+
generateLimb(rand),
92+
generateLimb(rand),
93+
generateLimb(rand),
94+
})
95+
}
96+
97+
func isInBounds(x *p224FieldElement) bool {
98+
return bits.Len32(x[0]) <= 29 &&
99+
bits.Len32(x[1]) <= 29 &&
100+
bits.Len32(x[2]) <= 29 &&
101+
bits.Len32(x[3]) <= 29 &&
102+
bits.Len32(x[4]) <= 29 &&
103+
bits.Len32(x[5]) <= 29 &&
104+
bits.Len32(x[6]) <= 29 &&
105+
bits.Len32(x[7]) <= 29
106+
}
107+
108+
func TestP224Mul(t *testing.T) {
109+
mulMatchesBigInt := func(a, b, out p224FieldElement) bool {
110+
var tmp p224LargeFieldElement
111+
p224Mul(&out, &a, &b, &tmp)
112+
113+
exp := new(big.Int).Mul(p224AlternativeToBig(&a), p224AlternativeToBig(&b))
114+
exp.Mod(exp, P224().Params().P)
115+
got := p224AlternativeToBig(&out)
116+
if exp.Cmp(got) != 0 || !isInBounds(&out) {
117+
t.Logf("a = %x", a)
118+
t.Logf("b = %x", b)
119+
t.Logf("p224Mul(a, b) = %x = %v", out, got)
120+
t.Logf("a * b = %v", exp)
121+
return false
122+
}
123+
124+
return true
125+
}
126+
127+
a := p224FieldElement{0xfffffff, 0xfffffff, 0xf00ffff, 0x20f, 0x0, 0x0, 0x0, 0x0}
128+
b := p224FieldElement{1, 0, 0, 0, 0, 0, 0, 0}
129+
if !mulMatchesBigInt(a, b, p224FieldElement{}) {
130+
t.Fail()
131+
}
132+
133+
if err := quick.Check(mulMatchesBigInt, quickCheckConfig32); err != nil {
134+
t.Error(err)
135+
}
136+
}
137+
138+
func TestP224Square(t *testing.T) {
139+
squareMatchesBigInt := func(a, out p224FieldElement) bool {
140+
var tmp p224LargeFieldElement
141+
p224Square(&out, &a, &tmp)
142+
143+
exp := p224AlternativeToBig(&a)
144+
exp.Mul(exp, exp)
145+
exp.Mod(exp, P224().Params().P)
146+
got := p224AlternativeToBig(&out)
147+
if exp.Cmp(got) != 0 || !isInBounds(&out) {
148+
t.Logf("a = %x", a)
149+
t.Logf("p224Square(a, b) = %x = %v", out, got)
150+
t.Logf("a * a = %v", exp)
151+
return false
152+
}
153+
154+
return true
155+
}
156+
157+
if err := quick.Check(squareMatchesBigInt, quickCheckConfig32); err != nil {
158+
t.Error(err)
159+
}
160+
}
161+
162+
func TestP224Add(t *testing.T) {
163+
addMatchesBigInt := func(a, b, out p224FieldElement) bool {
164+
p224Add(&out, &a, &b)
165+
166+
exp := new(big.Int).Add(p224AlternativeToBig(&a), p224AlternativeToBig(&b))
167+
exp.Mod(exp, P224().Params().P)
168+
got := p224AlternativeToBig(&out)
169+
if exp.Cmp(got) != 0 {
170+
t.Logf("a = %x", a)
171+
t.Logf("b = %x", b)
172+
t.Logf("p224Add(a, b) = %x = %v", out, got)
173+
t.Logf("a + b = %v", exp)
174+
return false
175+
}
176+
177+
return true
178+
}
179+
180+
if err := quick.Check(addMatchesBigInt, quickCheckConfig32); err != nil {
181+
t.Error(err)
182+
}
183+
}
184+
185+
func TestP224Reduce(t *testing.T) {
186+
reduceMatchesBigInt := func(a p224FieldElement) bool {
187+
out := a
188+
// TODO: generate higher values for functions like p224Reduce that are
189+
// expected to work with higher input bounds.
190+
p224Reduce(&out)
191+
192+
exp := p224AlternativeToBig(&a)
193+
got := p224AlternativeToBig(&out)
194+
if exp.Cmp(got) != 0 || !isInBounds(&out) {
195+
t.Logf("a = %x = %v", a, exp)
196+
t.Logf("p224Reduce(a) = %x = %v", out, got)
197+
return false
198+
}
199+
200+
return true
201+
}
202+
203+
if err := quick.Check(reduceMatchesBigInt, quickCheckConfig32); err != nil {
204+
t.Error(err)
205+
}
206+
}
207+
208+
func TestP224Contract(t *testing.T) {
209+
contractMatchesBigInt := func(a, out p224FieldElement) bool {
210+
p224Contract(&out, &a)
211+
212+
exp := p224AlternativeToBig(&a)
213+
got := p224AlternativeToBig(&out)
214+
if exp.Cmp(got) != 0 {
215+
t.Logf("a = %x = %v", a, exp)
216+
t.Logf("p224Contract(a) = %x = %v", out, got)
217+
return false
218+
}
219+
220+
// Check that out < P.
221+
for i := range p224P {
222+
k := 8 - i - 1
223+
if out[k] > p224P[k] {
224+
t.Logf("p224Contract(a) = %x", out)
225+
return false
226+
}
227+
if out[k] < p224P[k] {
228+
return true
229+
}
230+
}
231+
t.Logf("p224Contract(a) = %x", out)
232+
return false
233+
}
234+
235+
if !contractMatchesBigInt(p224P, p224FieldElement{}) {
236+
t.Error("p224Contract(p) is broken")
237+
}
238+
pMinus1 := p224FieldElement{0, 0, 0, 0xffff000, 0xfffffff, 0xfffffff, 0xfffffff, 0xfffffff}
239+
if !contractMatchesBigInt(pMinus1, p224FieldElement{}) {
240+
t.Error("p224Contract(p - 1) is broken")
241+
}
242+
// Check that we can handle input above p, but lowest limb zero.
243+
a := p224FieldElement{0, 1, 0, 0xffff000, 0xfffffff, 0xfffffff, 0xfffffff, 0xfffffff}
244+
if !contractMatchesBigInt(a, p224FieldElement{}) {
245+
t.Error("p224Contract(p + 2²⁸) is broken")
246+
}
247+
// Check that we can handle input above p, but lowest three limbs zero.
248+
b := p224FieldElement{0, 0, 0, 0xffff001, 0xfffffff, 0xfffffff, 0xfffffff, 0xfffffff}
249+
if !contractMatchesBigInt(b, p224FieldElement{}) {
250+
t.Error("p224Contract(p + 2⁸⁴) is broken")
251+
}
252+
253+
if err := quick.Check(contractMatchesBigInt, quickCheckConfig32); err != nil {
254+
t.Error(err)
255+
}
256+
}
257+
258+
func TestP224IsZero(t *testing.T) {
259+
if got := p224IsZero(&p224FieldElement{}); got != 1 {
260+
t.Errorf("p224IsZero(0) = %d, expected 1", got)
261+
}
262+
if got := p224IsZero((*p224FieldElement)(&p224P)); got != 1 {
263+
t.Errorf("p224IsZero(p) = %d, expected 1", got)
264+
}
265+
if got := p224IsZero(&p224FieldElement{1}); got != 0 {
266+
t.Errorf("p224IsZero(1) = %d, expected 0", got)
267+
}
268+
269+
isZeroMatchesBigInt := func(a p224FieldElement) bool {
270+
isZero := p224IsZero(&a)
271+
272+
big := p224AlternativeToBig(&a)
273+
if big.Sign() == 0 && isZero != 1 {
274+
return false
275+
}
276+
if big.Sign() != 0 && isZero != 0 {
277+
return false
278+
}
279+
return true
280+
}
281+
282+
if err := quick.Check(isZeroMatchesBigInt, quickCheckConfig32); err != nil {
283+
t.Error(err)
284+
}
285+
}
286+
287+
func TestP224Invert(t *testing.T) {
288+
var out p224FieldElement
289+
290+
p224Invert(&out, &p224FieldElement{})
291+
if got := p224IsZero(&out); got != 1 {
292+
t.Errorf("p224Invert(0) = %x, expected 0", out)
293+
}
294+
295+
p224Invert(&out, (*p224FieldElement)(&p224P))
296+
if got := p224IsZero(&out); got != 1 {
297+
t.Errorf("p224Invert(p) = %x, expected 0", out)
298+
}
299+
300+
p224Invert(&out, &p224FieldElement{1})
301+
p224Contract(&out, &out)
302+
if out != (p224FieldElement{1}) {
303+
t.Errorf("p224Invert(1) = %x, expected 1", out)
304+
}
305+
306+
var tmp p224LargeFieldElement
307+
a := p224FieldElement{1, 2, 3, 4, 5, 6, 7, 8}
308+
p224Invert(&out, &a)
309+
p224Mul(&out, &out, &a, &tmp)
310+
p224Contract(&out, &out)
311+
if out != (p224FieldElement{1}) {
312+
t.Errorf("p224Invert(a) * a = %x, expected 1", out)
313+
}
314+
}

0 commit comments

Comments
 (0)