Handle minimum signed varint safely

This commit is contained in:
Brandon McGinty (chatgpt)
2026-08-09 14:22:46 -04:00
committed by Brandon McGinty
parent 626608c997
commit 4aa4fd834f
3 changed files with 52 additions and 29 deletions
+2 -2
View File
@@ -211,14 +211,14 @@ Priority 3: configuration, UI, and binding hardening
atomic value to 1.0 in New, apply configured volume during connect, allow atomic value to 1.0 in New, apply configured volume during connect, allow
zero, and save volume changes. zero, and save volume changes.
27. Config address and file error handling can panic [x] 27. Config address and file error handling can panic
File: config/user_config.go File: config/user_config.go
makeHostPort splits on ':' and panics for malformed addresses or IPv6. makeHostPort splits on ':' and panics for malformed addresses or IPv6.
fileExists dereferences info after non-ENOENT Stat failures. Replace with fileExists dereferences info after non-ENOENT Stat failures. Replace with
net.SplitHostPort (with explicit default-port policy) and return/report net.SplitHostPort (with explicit default-port policy) and return/report
errors from Stat rather than dereferencing nil. errors from Stat rather than dereferencing nil.
28. Config SaveConfig panics and is not robust [x] 28. Config SaveConfig panics and is not robust
File: config/user_config.go File: config/user_config.go
Configuration write/rename errors panic the client. Return errors to the Configuration write/rename errors panic the client. Return errors to the
caller, preserve the prior config on failure, and consider fsyncing the caller, preserve the prior config on failure, and consider fsyncing the
+21 -1
View File
@@ -1,6 +1,26 @@
package varint // import "git.stormux.org/storm/barnard/gumble/gumble/varint" package varint // import "git.stormux.org/storm/barnard/gumble/gumble/varint"
import "testing" import (
"math"
"testing"
)
// Regression: MinInt64 formerly caused unbounded recursive encoding, and a
// caller-provided short buffer caused an index panic.
func TestEncodeMinInt64AndShortBuffer(t *testing.T) {
buf := make([]byte, MaxVarintLen)
n := Encode(buf, math.MinInt64)
if n != MaxVarintLen {
t.Fatalf("length = %d, want %d", n, MaxVarintLen)
}
got, consumed := Decode(buf[:n])
if consumed != n || got != math.MinInt64 {
t.Fatalf("decoded (%d, %d)", got, consumed)
}
if n := Encode(make([]byte, 1), 128); n != 0 {
t.Fatalf("short buffer returned %d", n)
}
}
func TestRange(t *testing.T) { func TestRange(t *testing.T) {
+29 -26
View File
@@ -9,56 +9,59 @@ import (
// number. // number.
const MaxVarintLen = 10 const MaxVarintLen = 10
// Encode encodes the given value to varint format. // Encode encodes value in the Mumble varint format. It returns zero when b is
// too small, rather than panicking on a caller-provided short buffer.
func Encode(b []byte, value int64) int { func Encode(b []byte, value int64) int {
// 111111xx Byte-inverted negative two bit number (~xx) var encoded [MaxVarintLen]byte
n := encode(encoded[:], value)
if n == 0 || len(b) < n {
return 0
}
copy(b, encoded[:n])
return n
}
func encode(b []byte, value int64) int {
if value <= -1 && value >= -4 { if value <= -1 && value >= -4 {
b[0] = 0xFC | byte(^value&0xFF) b[0] = 0xFC | byte(^value&0xFF)
return 1 return 1
} }
// 111110__ + varint Negative recursive varint
if value < 0 { if value < 0 {
b[0] = 0xF8 b[0] = 0xF8
return 1 + Encode(b[1:], -value) // -math.MinInt64 overflows. The decoder intentionally interprets the
// following signed 64-bit payload as MinInt64 and negates it modulo 2^64.
if value == math.MinInt64 {
b[1] = 0xF4
binary.BigEndian.PutUint64(b[2:], uint64(value))
return 10
}
return 1 + encode(b[1:], -value)
} }
// 0xxxxxxx 7-bit positive number
if value <= 0x7F { if value <= 0x7F {
b[0] = byte(value) b[0] = byte(value)
return 1 return 1
} }
// 10xxxxxx + 1 byte 14-bit positive number
if value <= 0x3FFF { if value <= 0x3FFF {
b[0] = byte(((value >> 8) & 0x3F) | 0x80) b[0] = byte(value>>8)&0x3F | 0x80
b[1] = byte(value & 0xFF) b[1] = byte(value)
return 2 return 2
} }
// 110xxxxx + 2 bytes 21-bit positive number
if value <= 0x1FFFFF { if value <= 0x1FFFFF {
b[0] = byte((value>>16)&0x1F | 0xC0) b[0] = byte(value>>16)&0x1F | 0xC0
b[1] = byte((value >> 8) & 0xFF) b[1], b[2] = byte(value>>8), byte(value)
b[2] = byte(value & 0xFF)
return 3 return 3
} }
// 1110xxxx + 3 bytes 28-bit positive number
if value <= 0xFFFFFFF { if value <= 0xFFFFFFF {
b[0] = byte((value>>24)&0xF | 0xE0) b[0] = byte(value>>24)&0x0F | 0xE0
b[1] = byte((value >> 16) & 0xFF) b[1], b[2], b[3] = byte(value>>16), byte(value>>8), byte(value)
b[2] = byte((value >> 8) & 0xFF)
b[3] = byte(value & 0xFF)
return 4 return 4
} }
// 111100__ + int (32-bit) 32-bit positive number
if value <= math.MaxInt32 { if value <= math.MaxInt32 {
b[0] = 0xF0 b[0] = 0xF0
binary.BigEndian.PutUint32(b[1:], uint32(value)) binary.BigEndian.PutUint32(b[1:], uint32(value))
return 5 return 5
} }
// 111101__ + long (64-bit) 64-bit number b[0] = 0xF4
if value <= math.MaxInt64 { binary.BigEndian.PutUint64(b[1:], uint64(value))
b[0] = 0xF4 return 9
binary.BigEndian.PutUint64(b[1:], uint64(value))
return 9
}
return 0
} }