Here is the combined list of vulnerabilities, with duplicates removed and formatted as requested:
-
Vulnerability 1: Out-of-bounds write in
UnsafeSliceType.SetIndex
-
Description:
- An attacker can create a slice of a specific type (e.g.,
[]int
) usingreflect2.TypeOf([]int{}).MakeSlice(length, capacity)
. - The attacker obtains the
reflect2.SliceType
for this slice. - The attacker calls the
SetIndex(slice, index, value)
method of thereflect2.SliceType
with anindex
that is greater than or equal to the currentlength
of the slice. - The
SetIndex
method inunsafe_slice.go
does not perform bounds checking to ensure that the providedindex
is within the valid range (0 to length-1). - Consequently, the
UnsafeSetIndex
method is called with the out-of-boundsindex
. - Inside
UnsafeSetIndex
, thearrayAt
function calculates a memory address by addingindex * elemSize
to the base address of the slice's data. This calculation does not validate if theindex
is within the slice's bounds. - The
typedmemmove
function then writes the providedvalue
to the memory location calculated byarrayAt
, which is outside the allocated memory region of the slice whenindex
is out-of-bounds. - Similarly, in
UnsafeGetIndex
, an arbitrary index value is used to calculate the element’s memory address without bounds validation, potentially leading to out-of-bounds reads.
- An attacker can create a slice of a specific type (e.g.,
-
Impact:
- Memory corruption: Writing outside the intended bounds of the slice's memory can overwrite adjacent data in memory. This can lead to various security issues, including:
- Program crashes due to corrupted data structures.
- Unexpected or incorrect program behavior.
- Potential for arbitrary code execution if critical data structures or function pointers are overwritten.
- Arbitrary memory read or write via out‑of‑bounds pointer arithmetic.
- Potential memory corruption and the possibility to corrupt adjacent data.
- In a worst‑case scenario, this can be leveraged to achieve arbitrary code execution.
- Memory corruption: Writing outside the intended bounds of the slice's memory can overwrite adjacent data in memory. This can lead to various security issues, including:
-
Vulnerability Rank: high
-
Currently implemented mitigations:
- None. The code lacks explicit bounds checking in the
SetIndex
,UnsafeSetIndex
, andUnsafeGetIndex
methods ofUnsafeSliceType
. The type assertions usingassertType
only verify the type compatibility of the arguments, not the validity of the index.
- None. The code lacks explicit bounds checking in the
-
Missing mitigations:
- Implement bounds checking within the
UnsafeSliceType.UnsafeSetIndex
,UnsafeSliceType.SetIndex
, andUnsafeSliceType.UnsafeGetIndex
methods. Before calculating the memory address usingarrayAt
, verify that the providedindex
is less than the current slice length (header.Len
). If theindex
is out of bounds, the operation should be prevented, possibly by returning an error or panicking. - Validation on any external input that is ultimately used as an index parameter in unsafe slice operations.
- Explicit bounds checking against the slice length before performing the pointer arithmetic in the helper function
arrayAt
.
- Implement bounds checking within the
-
Preconditions:
- The attacker must be able to use the
reflect2
library to create and manipulate slices. - The attacker needs to be able to control the index and value passed to the
SetIndex
method of areflect2.SliceType
or control the index passed toGetIndex
. - The application (or a library using reflect2, such as json‑iterator) must expose a code path wherein an external attacker can inject or control the index value that is eventually passed to
UnsafeSetIndex
orUnsafeGetIndex
. - The unsafe implementation of slice operations must be active (i.e. not using the safe variant).
- The attacker must be able to use the
-
Source code analysis:
/code/unsafe_slice.go
:func (type2 *UnsafeSliceType) SetIndex(obj interface{}, index int, elem interface{}) { objEFace := unpackEFace(obj) assertType("SliceType.SetIndex argument 1", type2.ptrRType, objEFace.rtype) elemEFace := unpackEFace(elem) assertType("SliceType.SetIndex argument 3", type2.pElemRType, elemEFace.rtype) type2.UnsafeSetIndex(objEFace.data, index, elemEFace.data) // Calls UnsafeSetIndex without bounds check } func (type2 *UnsafeSliceType) UnsafeSetIndex(obj unsafe.Pointer, index int, elem unsafe.Pointer) { header := (*sliceHeader)(obj) elemPtr := arrayAt(header.Data, index, type2.elemSize, "i < s.Len") // Calls arrayAt, no bounds check typedmemmove(type2.elemRType, elemPtr, elem) // Memory write using unsafe.Pointer } func (type2 *UnsafeSliceType) GetIndex(obj interface{}, index int) interface{} { objEFace := unpackEFace(obj) assertType("SliceType.GetIndex argument 1", type2.ptrRType, objEFace.rtype) return type2.UnsafeGetIndex(objEFace.data, index) // Calls UnsafeGetIndex without bounds check } func (type2 *UnsafeSliceType) UnsafeGetIndex(obj unsafe.Pointer, index int) interface{} { header := (*sliceHeader)(obj) elemPtr := arrayAt(header.Data, index, type2.elemSize, "i < s.Len") // Calls arrayAt, no bounds check return type2.typedUnsafeGetIndex(elemPtr) }
/code/unsafe_link.go
:func arrayAt(p unsafe.Pointer, i int, eltSize uintptr, whySafe string) unsafe.Pointer { return add(p, uintptr(i)*eltSize, "i < len") // Address calculation, no bounds check }
- Visualization:
[Slice Header: Data Pointer, Len, Cap] --> [Element 0][Element 1]...[Element Len-1][...Capacity...] ^ | SetIndex/GetIndex(slice, index, value) --> arrayAt(Data Pointer, index, Element Size) | V [Memory Address (potentially out-of-bounds)] ----> typedmemmove / typedUnsafeGetIndex (write/read value)
-
Security test case:
- Prepare test code:
package main import ( "fmt" "reflect2" "unsafe" ) type TestStruct struct { Slice []int AdjacentVar int } func main() { testStruct := TestStruct{ Slice: make([]int, 1, 2), // Length 1, capacity 2 AdjacentVar: 0x12345678, // Initial value for adjacent variable } testStruct.Slice[0] = 1 sliceType := reflect2.TypeOf(testStruct.Slice).(reflect2.SliceType) outOfBoundsIndex := 1 newValue := 100 sliceType.SetIndex(&testStruct.Slice, outOfBoundsIndex, newValue) if testStruct.AdjacentVar == newValue { fmt.Println("[VULNERABLE] Adjacent variable overwritten! Out-of-bounds write confirmed.") fmt.Printf("AdjacentVar value: 0x%x\n", testStruct.AdjacentVar) } else { fmt.Println("[NOT VULNERABLE] Adjacent variable not overwritten. No out-of-bounds write detected.") fmt.Printf("AdjacentVar value: 0x%x\n", testStruct.AdjacentVar) } }
- Compile and run the test code.
- Observe the output. If the output is
[VULNERABLE] Adjacent variable overwritten! Out-of-bounds write confirmed.
and the AdjacentVar value is0x64
(decimal 100), then the vulnerability is present. - To test
GetIndex
vulnerability, modify the test code to read out-of-bounds index and observe if it reads adjacent memory.
- Prepare test code:
-
-
Vulnerability 2: Integer Overflow in Unsafe Slice Growth (calcNewCap Overflows)
-
Description:
- An attacker can indirectly trigger slice growth by using functions like
Append
orGrow
on anUnsafeSliceType
through user-controlled operations. - When slice
Grow
is needed (new length exceeds current capacity), theUnsafeSliceType.UnsafeGrow
function is called. UnsafeGrow
calculates the new capacity using thecalcNewCap(header.Cap, newLength)
function.- In
calcNewCap
, if the initial slice capacity (cap
) is a large value and theexpectedCap
(which is thenewLength
) is also large, the capacity calculationcap += cap
orcap += cap / 4
can result in an integer overflow. This overflow leads to a negativenewCap
value. - This negative
newCap
value is then passed totype2.UnsafeMakeSlice(header.Len, newCap)
to allocate new memory for the slice. UnsafeMakeSlice
usesunsafe_NewArray
with the negativenewCap
. Due to integer underflow, negativenewCap
might wrap around to a very large positive number, leading to allocation of an unexpectedly large memory chunk.- Subsequently,
typedslicecopy(type2.elemRType, *newHeader, *header)
attempts to copy the old slice data to the newly allocated (potentially huge) memory. If the allocated memory size due to overflow is not properly handled intypedslicecopy
or ifheader.Len
is also large, this can lead to a heap buffer overflow during the memory copy operation, astypedslicecopy
might write beyond the intended buffer boundaries. - As a result, the computed new capacity (
newCap
) becomes much lower than the requested new length. The subsequent call to allocate and then copy slice elements leads to a slice header whereLen
is set tonewLength
even though the underlying array is far smaller. This misalignment can open the door to out‐of‑bounds writes.
- An attacker can indirectly trigger slice growth by using functions like
-
Impact:
- Heap buffer overflow. This can lead to memory corruption, program crash, and potentially arbitrary code execution if an attacker can carefully control the overflow and subsequent memory operations.
- Memory corruption due to writing beyond the allocated slice buffer.
- Potential for arbitrary memory overwrite which, when exploited correctly, may lead to remote code execution.
- Although the vulnerability relies on triggering invalid slice growth rather than a simple panic, the impact goes far beyond merely causing a Crash.
-
Vulnerability Rank: Critical
-
Currently Implemented Mitigations:
- The unsafe implementation relies solely on internal type assertions (via
assertType
) for pointer correctness; however, there is no explicit check to catch integer overflow within thecalcNewCap
routine or inUnsafeGrow
. - None. The code does not contain any explicit checks to prevent integer overflows in the
calcNewCap
function or handle potential negative capacity values.
- The unsafe implementation relies solely on internal type assertions (via
-
Missing Mitigations:
- Sanity checks for the
newLength
value provided from external input. - Code to verify that arithmetic operations inside
calcNewCap
do not overflow (for example, by checking that the updated capacity is not lower than expected). - An enforcement that the newly allocated slice’s capacity must be at least as large as
newLength
before setting the header’s length. - Integer Overflow Checks: Implement checks within the
calcNewCap
function to detect and prevent integer overflows during capacity calculations. Ensure that the calculated capacity remains a positive and valid value. If an overflow is detected, the function should return an error or a maximum allowed capacity instead of a negative or wrapped-around value. - Capacity Validation: Validate the calculated
newCap
before passing it toUnsafeMakeSlice
. Ensure that the capacity is within reasonable limits and is not negative. - Error Handling: Implement proper error handling in
UnsafeGrow
andcalcNewCap
. If capacity calculation fails or memory allocation fails due to invalid capacity, the function should return an error and prevent further operations that could lead to memory corruption.
- Sanity checks for the
-
Preconditions:
- The application must be using the
reflect2
library with the unsafe configuration (ConfigUnsafe
). - An external attacker must be able to influence the value for
newLength
passed toUnsafeGrow
(for instance, via a JSON payload processed by a service that uses json‑iterator, which in turn uses reflect2). - The application code must perform operations on slices using
reflect2
'sUnsafeSliceType
, specifically operations that can trigger slice growth, such asAppend
orGrow
. - An attacker needs to find a way to control the input parameters that influence the slice's growth, specifically the target length in
Grow
or the number of elements appended, such that the capacity calculation incalcNewCap
overflows.
- The application must be using the
-
Source Code Analysis:
- In
/code/unsafe_slice.go
, the method:UnsafeGrow(obj unsafe.Pointer, newLength int)
first extracts the slice header.- It checks if
newLength
is less than or equal to the existing capacity; if not, it callscalcNewCap(header.Cap, newLength)
to calculate the new capacity. - Inside
calcNewCap
, the logic updates the capacity by either doubling or incrementing by a quarter, in a loop:func calcNewCap(cap int, expectedCap int) int { if cap == 0 { cap = expectedCap } else { for cap < expectedCap { if cap < 1024 { cap += cap } else { cap += cap / 4 } } } return cap }
- There is no check to ensure that arithmetic on
cap
does not overflow. If an attacker provides a value fornewLength
such that the loop’s arithmetic wraps around,newCap
will be computed incorrectly. - After the call to
calcNewCap
, the code allocates a new slice viaUnsafeMakeSlice
, copies the elements withtypedslicecopy
, and then setsheader.Len = newLength
even though the underlying capacity is insufficient.
// File: /code/unsafe_slice.go func (type2 *UnsafeSliceType) UnsafeGrow(obj unsafe.Pointer, newLength int) { header := (*sliceHeader)(obj) if newLength <= header.Cap { header.Len = newLength return } newCap := calcNewCap(header.Cap, newLength) // Potential Integer Overflow in calcNewCap newHeader := (*sliceHeader)(type2.UnsafeMakeSlice(header.Len, newCap)) // Negative newCap passed if overflow occurs typedslicecopy(type2.elemRType, *newHeader, *header) // Heap Overflow during copy if newHeader is corrupted header.Data = newHeader.Data header.Cap = newHeader.Cap header.Len = newLength } func calcNewCap(cap int, expectedCap int) int { if cap == 0 { cap = expectedCap } else { for cap < expectedCap { if cap < 1024 { cap += cap // Integer overflow possible here } else { cap += cap / 4 // Integer overflow possible here } } } return cap }
Vulnerability Flow Visualization:
[User Input/Action] --> [Application Code using reflect2.UnsafeSliceType.Grow/Append] | V [UnsafeSliceType.UnsafeGrow] --> [calcNewCap] - Calculates new capacity (Potential Overflow) | V [Returns newCap (potentially negative or huge)] | V [UnsafeGrow] --> [UnsafeMakeSlice(newCap)] - Allocates slice with potentially invalid size | V [UnsafeGrow] --> [typedslicecopy] - Copies data (Potential Heap Overflow if size is invalid)
- In
-
Security Test Case:
-
Identify or simulate a code path (for example, through a JSON deserialization endpoint using json‑iterator) that eventually calls
UnsafeGrow
on a slice. -
Prepare an input (or test harness) where the intended new length is set to a very high value (close to the maximum integer value) such that the iterative capacity increase in
calcNewCap
overflows. -
Invoke the unsafe slice growth function with this manipulated
newLength
. -
After
UnsafeGrow
returns, inspect the returned slice header; verify that theCap
(capacity) is unexpectedly much lower than the setLen
(length). -
Optionally, attempt to write to an element near the end of the oversized length and monitor for signs of memory corruption (this may require a controlled testing environment with memory instrumentation).
-
Confirm that when the overflow condition is met, subsequent accesses to the slice lead to out-of-bounds memory writes.
-
Setup: Create a Go program that imports the
reflect2
library with unsafe configuration:cfg := reflect2.ConfigUnsafe
. -
Slice Creation: Create an unsafe slice of integers using
reflect2
. Initialize it with a large capacity close to the maximum integer value divided by 2. For example, set initial capacity to(1 << 30)
. -
Trigger Overflow: Call
UnsafeGrow
on this slice with anewLength
that is slightly larger than the current capacity. This should trigger the integer overflow incalcNewCap
. For instance, if the initial capacity is(1 << 30)
, setnewLength
to(1 << 30) + 10
. -
Execute
UnsafeGrow
: Execute thesliceType.UnsafeGrow(slicePtr, newLength)
function. -
Observe Behavior: Run the program and observe the outcome.
- Expected Vulnerable Outcome: The program might crash due to a heap buffer overflow during the
typedslicecopy
operation, or it might continue to run with corrupted memory. Check for panics or unexpected program termination. - Verification of Overflow: After running the test, check the resulting slice's capacity and length using
sliceType.Cap(obj)
andsliceType.LengthOf(obj)
. If the integer overflow occurred, the capacity might be a negative value (represented as a large unsigned integer) or a much smaller positive value than expected, indicating wrap-around. Attempt to access elements beyond the original slice bounds to trigger a potential crash if memory is corrupted.
- Expected Vulnerable Outcome: The program might crash due to a heap buffer overflow during the
-
Code Example (Conceptual - needs refinement for precise exploitation):
package main import ( "fmt" "reflect" "github.com/modern-go/reflect2" "unsafe" ) func main() { cfg := reflect2.ConfigUnsafe sliceType := cfg.Type2(reflect.TypeOf([]int{})).(reflect2.SliceType) initialCap := (1 << 30) // Large capacity to trigger overflow easily initialLen := 10 slicePtr := sliceType.UnsafeMakeSlice(initialLen, initialCap) obj := sliceType.PackEFace(slicePtr) newLength := initialCap + 10 // Trigger overflow in calcNewCap fmt.Println("Initial Cap:", sliceType.Cap(obj)) fmt.Println("Initial Len:", sliceType.LengthOf(obj)) sliceType.UnsafeGrow(slicePtr, newLength) // Trigger UnsafeGrow to cause overflow fmt.Println("New Cap:", sliceType.Cap(obj)) fmt.Println("New Len:", sliceType.LengthOf(obj)) // Attempt to write beyond original boundary, might cause crash if overflow is exploited for i := 0; i < newLength + 10; i++ { elem := i * 100 elemPtr := unsafe.Pointer(&elem) sliceType.UnsafeSetIndex(slicePtr, i, elemPtr) // Potential out-of-bounds write val := sliceType.UnsafeGetIndex(slicePtr, i) fmt.Printf("Element %d: %d\n", i, *(*int)(val)) } fmt.Println("Program finished (if no crash)") }
Note: This test case is a starting point and might need adjustments to reliably trigger and demonstrate the heap overflow depending on the exact behavior of
unsafe_NewArray
andtypedslicecopy
with overflowed sizes, and the memory allocation behavior of the Go runtime. The key idea is to cause an integer overflow in capacity calculation and observe the memory corruption or crash.
-
-