242 lines
4.7 KiB
Markdown
242 lines
4.7 KiB
Markdown
# Memory Safety Security Rules
|
|
|
|
Memory safety vulnerabilities can lead to crashes, data corruption, and security compromises.
|
|
|
|
**Rules:**
|
|
|
|
1. Integer overflow MUST be checked at boundaries — NEVER trust unchecked arithmetic on external input.
|
|
2. `unsafe` MUST NOT be used in application code — restrict to low-level libraries with thorough review.
|
|
3. Data races MUST be detected with `-race` flag in CI.
|
|
|
|
---
|
|
|
|
## Integer Overflow — High
|
|
|
|
Integer overflows can cause unexpected behavior and crashes.
|
|
|
|
**Bad:**
|
|
|
|
```go
|
|
func allocateBuffer(rows, cols int) []byte {
|
|
size := rows * cols // DON'T: Can overflow
|
|
return make([]byte, size)
|
|
}
|
|
```
|
|
|
|
**Good:**
|
|
|
|
```go
|
|
import "math"
|
|
|
|
func safeMultiply(a, b int) (int, error) {
|
|
if a == 0 || b == 0 {
|
|
return 0, nil
|
|
}
|
|
if a > math.MaxInt/b {
|
|
return 0, errors.New("integer overflow")
|
|
}
|
|
result := a * b
|
|
if result/b != a {
|
|
return 0, errors.New("overflow detected")
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func allocateBuffer(rows, cols int) ([]byte, error) {
|
|
size, err := safeMultiply(rows, cols)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
const maxBufferSize = 100 * 1024 * 1024 // 100MB limit
|
|
if size > maxBufferSize {
|
|
return nil, errors.New("buffer size exceeds limit")
|
|
}
|
|
return make([]byte, size), nil
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## math/big.Rat Issues — Low
|
|
|
|
Rat can consume large amounts of memory if denominators grow without bounds.
|
|
|
|
**Bad:**
|
|
|
|
```go
|
|
import "math/big"
|
|
|
|
func unsafeFraction(operations int) *big.Rat {
|
|
r := big.NewRat(1, 1)
|
|
for i := 0; i < operations; i++ {
|
|
r.Mul(r, big.NewRat(int64(i+1), int64(i+2))) // DON'T
|
|
}
|
|
return r // Could be memory intensive
|
|
}
|
|
```
|
|
|
|
**Good:**
|
|
|
|
```go
|
|
const maxRatNumBits = 1000
|
|
|
|
func safeFraction(operations int) (*big.Rat, error) {
|
|
r := big.NewRat(1, 1)
|
|
for i := 0; i < operations; i++ {
|
|
r.Mul(r, big.NewRat(int64(i+1), int64(i+2)))
|
|
if r.Num().BitLen() > maxRatNumBits || r.Denom().BitLen() > maxRatNumBits {
|
|
return nil, errors.New("fraction precision too large")
|
|
}
|
|
}
|
|
return r, nil
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Memory Aliasing Vulnerability — Medium
|
|
|
|
Memory aliasing can cause data corruption and race conditions.
|
|
|
|
**Bad:**
|
|
|
|
```go
|
|
func reverseBytes(data []byte) {
|
|
for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
|
|
data[i], data[j] = data[j], data[i] // DON'T if slices alias
|
|
}
|
|
}
|
|
```
|
|
|
|
**Good:**
|
|
|
|
```go
|
|
import "unsafe"
|
|
|
|
func checkOverlap(a, b []byte) bool {
|
|
if len(a) == 0 || len(b) == 0 {
|
|
return false
|
|
}
|
|
aStart := uintptr(unsafe.Pointer(&a[0]))
|
|
aEnd := aStart + uintptr(len(a))
|
|
bStart := uintptr(unsafe.Pointer(&b[0]))
|
|
bEnd := bStart + uintptr(len(b))
|
|
return aStart < bEnd && bStart < aEnd
|
|
}
|
|
|
|
func safeCopy(dest, src []byte) {
|
|
if checkOverlap(dest, src) {
|
|
temp := make([]byte, len(src))
|
|
copy(temp, src)
|
|
copy(dest, temp)
|
|
} else {
|
|
copy(dest, src)
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Use of unsafe Package — High
|
|
|
|
The unsafe package bypasses Go's type safety and memory safety.
|
|
|
|
**Bad:**
|
|
|
|
```go
|
|
import "unsafe"
|
|
|
|
func UnsafeStringToBytes(s string) []byte {
|
|
return (*[0x7fffffff]byte)(unsafe.Pointer(
|
|
(*reflect.StringHeader)(unsafe.Pointer(&s)).Data,
|
|
))[:len(s):len(s)] // DON'T: memory corruption risk
|
|
}
|
|
|
|
func TypePun(value uint64) float64 {
|
|
return *(*float64)(unsafe.Pointer(&value)) // DON'T
|
|
}
|
|
```
|
|
|
|
**Good:**
|
|
|
|
```go
|
|
// Safe string encoding
|
|
func StringToBytes(s string) []byte {
|
|
return []byte(s)
|
|
}
|
|
func BytesToString(b []byte) string {
|
|
return string(b)
|
|
}
|
|
|
|
// Safe type conversion
|
|
import "encoding/binary"
|
|
func Uint64ToFloat64(value uint64) float64 {
|
|
buf := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(buf, value)
|
|
bits := binary.LittleEndian.Uint64(buf)
|
|
return math.Float64frombits(bits)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Data Races — High
|
|
|
|
Go's race detector is your primary defense.
|
|
|
|
**Bad:**
|
|
|
|
```go
|
|
type Counter struct {
|
|
value int
|
|
}
|
|
|
|
func (c *Counter) Increment() {
|
|
c.value++ // DON'T: Data race without sync
|
|
}
|
|
```
|
|
|
|
**Good:**
|
|
|
|
```go
|
|
import "sync"
|
|
|
|
type Counter struct {
|
|
value int
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func (c *Counter) Increment() {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.value++
|
|
}
|
|
|
|
// Or atomic for simple cases
|
|
import "sync/atomic"
|
|
type AtomicCounter struct {
|
|
value int64
|
|
}
|
|
func (c *AtomicCounter) Increment() {
|
|
atomic.AddInt64(&c.value, 1)
|
|
}
|
|
```
|
|
|
|
## Always Run Race Detector
|
|
|
|
```bash
|
|
go test -race ./...
|
|
go build -race
|
|
```
|
|
|
|
---
|
|
|
|
## CWE References
|
|
|
|
- **CWE-190**: Integer Overflow or Wraparound
|
|
- **CWE-119**: Improper Restriction of Operations within Bounds
|
|
- **CWE-125**: Out-of-bounds Read
|
|
- **CWE-787**: Out-of-bounds Write
|
|
- **CWE-362**: Race Condition
|
|
- **CWE-367**: Time-of-check Time-of-use (TOCTOU)
|