Skip to content

Commit a201458

Browse files
committed
fixing race condition and cwe-338
1 parent 50f042b commit a201458

File tree

4 files changed

+70
-15
lines changed

4 files changed

+70
-15
lines changed

child/child.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
package child
55

66
import (
7+
"crypto/rand"
78
"errors"
89
"fmt"
910
"io"
1011
"log"
11-
"math/rand"
12+
"math/big"
1213
"os"
1314
"os/exec"
1415
"strings"
@@ -475,8 +476,14 @@ func (c *Child) randomSplay() <-chan time.Time {
475476
}
476477

477478
ns := c.splay.Nanoseconds()
478-
offset := rand.Int63n(ns)
479-
t := time.Duration(offset)
479+
// Use crypto/rand for secure random generation (CWE-338 fix)
480+
n, err := rand.Int(rand.Reader, big.NewInt(ns))
481+
if err != nil {
482+
// Fallback to no splay on error
483+
c.logger.Printf("[WARN] (child) failed to generate random splay: %v", err)
484+
return time.After(0)
485+
}
486+
t := time.Duration(n.Int64())
480487

481488
c.logger.Printf("[DEBUG] (child) waiting %.2fs for random splay", t.Seconds())
482489

dependency/file_test.go

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package dependency
66
import (
77
"fmt"
88
"os"
9+
"path/filepath"
910
"testing"
1011
"time"
1112

@@ -136,15 +137,39 @@ func TestFileQuery_Fetch(t *testing.T) {
136137
}
137138
})
138139

139-
syncWriteFile := func(name string, data []byte, perm os.FileMode) error {
140-
f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC|os.O_SYNC, perm)
140+
syncWriteFile := func(name string, data []byte, _ os.FileMode) error {
141+
// Write to a temporary file in the same directory to ensure atomic rename
142+
dir := filepath.Dir(name)
143+
tmpFile, err := os.CreateTemp(dir, ".syncwrite-*")
144+
if err != nil {
145+
return err
146+
}
147+
tmpName := tmpFile.Name()
148+
defer os.Remove(tmpName)
149+
150+
_, err = tmpFile.Write(data)
141151
if err == nil {
142-
_, err = f.Write(data)
143-
if err1 := f.Close(); err1 != nil && err == nil {
144-
err = err1
145-
}
152+
err = tmpFile.Sync()
153+
}
154+
if err1 := tmpFile.Close(); err1 != nil && err == nil {
155+
err = err1
156+
}
157+
if err != nil {
158+
return err
159+
}
160+
161+
// Atomically rename the temp file to the target file
162+
if err := os.Rename(tmpName, name); err != nil {
163+
return err
146164
}
147-
return err
165+
166+
// Ensure the directory entry is synced (best effort)
167+
if dirFile, err := os.Open(dir); err == nil {
168+
dirFile.Sync()
169+
dirFile.Close()
170+
}
171+
172+
return nil
148173
}
149174
t.Run("fires_changes", func(t *testing.T) {
150175
f, err := os.CreateTemp("", "")

dependency/vault_common.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
package dependency
55

66
import (
7+
"crypto/rand"
78
"encoding/json"
89
"fmt"
910
"log"
10-
"math/rand"
11+
"math/big"
1112
"sync"
1213
"time"
1314

@@ -77,6 +78,21 @@ type renewer interface {
7778
secrets() (*Secret, *api.Secret)
7879
}
7980

81+
// cryptoRandFloat64 generates a cryptographically secure random float64 in [0.0, 1.0)
82+
func cryptoRandFloat64() float64 {
83+
// Generate a random 53-bit integer (mantissa precision of float64)
84+
max := big.NewInt(1 << 53)
85+
n, err := rand.Int(rand.Reader, max)
86+
if err != nil {
87+
// Fallback to a reasonable default if crypto/rand fails
88+
// This should never happen in practice
89+
log.Printf("[WARN] crypto/rand failed, using 0.5 as fallback: %v", err)
90+
return 0.5
91+
}
92+
// Convert to float64 in range [0.0, 1.0)
93+
return float64(n.Int64()) / float64(max.Int64())
94+
}
95+
8096
func renewSecret(clients *ClientSet, d renewer) error {
8197
log.Printf("[TRACE] %s: starting renewer", d)
8298

@@ -166,15 +182,15 @@ func leaseCheckWait(s *Secret) time.Duration {
166182
sleep = sleep / 3.0
167183

168184
// Use some randomness so many clients do not hit Vault simultaneously.
169-
sleep = sleep * (rand.Float64() + 1) / 2.0
185+
sleep = sleep * (cryptoRandFloat64() + 1) / 2.0
170186
} else if !rotatingSecret {
171187
// If the secret doesn't have a rotation period, this is a non-renewable leased
172188
// secret.
173189
// For non-renewable leases set the renew duration to use much of the secret
174190
// lease as possible. Use a stagger over the configured threshold
175191
// fraction of the lease duration so that many clients do not hit
176192
// Vault simultaneously.
177-
finalFraction := VaultLeaseRenewalThreshold + (rand.Float64()-0.5)*0.1
193+
finalFraction := VaultLeaseRenewalThreshold + (cryptoRandFloat64()-0.5)*0.1
178194
if finalFraction >= 1.0 || finalFraction <= 0.0 {
179195
// If the fraction randomly winds up outside of (0.0-1.0), clamp
180196
// back down to the VaultLeaseRenewalThreshold provided by the user,

watch/view.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
package watch
55

66
import (
7+
"crypto/rand"
78
"errors"
89
"fmt"
910
"log"
10-
"math/rand"
11+
"math/big"
1112
"reflect"
1213
"sync"
1314
"time"
@@ -311,7 +312,13 @@ const minDelayBetweenUpdates = time.Millisecond * 100
311312
func rateLimiter(start time.Time) time.Duration {
312313
remaining := minDelayBetweenUpdates - time.Since(start)
313314
if remaining > 0 {
314-
dither := time.Duration(rand.Int63n(20000000)) // 0-20ms
315+
// Use crypto/rand for secure random generation (CWE-338 fix)
316+
n, err := rand.Int(rand.Reader, big.NewInt(20000000))
317+
if err != nil {
318+
// Fallback to no dither on error
319+
return remaining
320+
}
321+
dither := time.Duration(n.Int64()) // 0-20ms in nanoseconds
315322
return remaining + dither
316323
}
317324
return 0

0 commit comments

Comments
 (0)