Skip to content

Latest commit

 

History

History
301 lines (257 loc) · 20.4 KB

vulnerabilities-workflow-1.md

File metadata and controls

301 lines (257 loc) · 20.4 KB

Here is the combined list of vulnerabilities, with duplicates removed and formatted as requested:

Combined Vulnerability List for reflect2 Project

  • Vulnerability 1: Out-of-bounds write in UnsafeSliceType.SetIndex

    • Description:

      1. An attacker can create a slice of a specific type (e.g., []int) using reflect2.TypeOf([]int{}).MakeSlice(length, capacity).
      2. The attacker obtains the reflect2.SliceType for this slice.
      3. The attacker calls the SetIndex(slice, index, value) method of the reflect2.SliceType with an index that is greater than or equal to the current length of the slice.
      4. The SetIndex method in unsafe_slice.go does not perform bounds checking to ensure that the provided index is within the valid range (0 to length-1).
      5. Consequently, the UnsafeSetIndex method is called with the out-of-bounds index.
      6. Inside UnsafeSetIndex, the arrayAt function calculates a memory address by adding index * elemSize to the base address of the slice's data. This calculation does not validate if the index is within the slice's bounds.
      7. The typedmemmove function then writes the provided value to the memory location calculated by arrayAt, which is outside the allocated memory region of the slice when index is out-of-bounds.
      8. 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.
    • 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.
    • Vulnerability Rank: high

    • Currently implemented mitigations:

      • None. The code lacks explicit bounds checking in the SetIndex, UnsafeSetIndex, and UnsafeGetIndex methods of UnsafeSliceType. The type assertions using assertType only verify the type compatibility of the arguments, not the validity of the index.
    • Missing mitigations:

      • Implement bounds checking within the UnsafeSliceType.UnsafeSetIndex, UnsafeSliceType.SetIndex, and UnsafeSliceType.UnsafeGetIndex methods. Before calculating the memory address using arrayAt, verify that the provided index is less than the current slice length (header.Len). If the index 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.
    • 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 a reflect2.SliceType or control the index passed to GetIndex.
      • 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 or UnsafeGetIndex.
      • The unsafe implementation of slice operations must be active (i.e. not using the safe variant).
    • 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:

      1. 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)
            }
        }
      2. Compile and run the test code.
      3. Observe the output. If the output is [VULNERABLE] Adjacent variable overwritten! Out-of-bounds write confirmed. and the AdjacentVar value is 0x64 (decimal 100), then the vulnerability is present.
      4. To test GetIndex vulnerability, modify the test code to read out-of-bounds index and observe if it reads adjacent memory.
  • Vulnerability 2: Integer Overflow in Unsafe Slice Growth (calcNewCap Overflows)

    • Description:

      1. An attacker can indirectly trigger slice growth by using functions like Append or Grow on an UnsafeSliceType through user-controlled operations.
      2. When slice Grow is needed (new length exceeds current capacity), the UnsafeSliceType.UnsafeGrow function is called.
      3. UnsafeGrow calculates the new capacity using the calcNewCap(header.Cap, newLength) function.
      4. In calcNewCap, if the initial slice capacity (cap) is a large value and the expectedCap (which is the newLength) is also large, the capacity calculation cap += cap or cap += cap / 4 can result in an integer overflow. This overflow leads to a negative newCap value.
      5. This negative newCap value is then passed to type2.UnsafeMakeSlice(header.Len, newCap) to allocate new memory for the slice.
      6. UnsafeMakeSlice uses unsafe_NewArray with the negative newCap. Due to integer underflow, negative newCap might wrap around to a very large positive number, leading to allocation of an unexpectedly large memory chunk.
      7. 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 in typedslicecopy or if header.Len is also large, this can lead to a heap buffer overflow during the memory copy operation, as typedslicecopy might write beyond the intended buffer boundaries.
      8. 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 where Len is set to newLength even though the underlying array is far smaller. This misalignment can open the door to out‐of‑bounds writes.
    • 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 the calcNewCap routine or in UnsafeGrow.
      • None. The code does not contain any explicit checks to prevent integer overflows in the calcNewCap function or handle potential negative capacity values.
    • 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 to UnsafeMakeSlice. Ensure that the capacity is within reasonable limits and is not negative.
      • Error Handling: Implement proper error handling in UnsafeGrow and calcNewCap. 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.
    • 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 to UnsafeGrow (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's UnsafeSliceType, specifically operations that can trigger slice growth, such as Append or Grow.
      • 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 in calcNewCap overflows.
    • 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 calls calcNewCap(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 for newLength such that the loop’s arithmetic wraps around, newCap will be computed incorrectly.
        • After the call to calcNewCap, the code allocates a new slice via UnsafeMakeSlice, copies the elements with typedslicecopy, and then sets header.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)
      
    • Security Test Case:

      1. Identify or simulate a code path (for example, through a JSON deserialization endpoint using json‑iterator) that eventually calls UnsafeGrow on a slice.

      2. 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.

      3. Invoke the unsafe slice growth function with this manipulated newLength.

      4. After UnsafeGrow returns, inspect the returned slice header; verify that the Cap (capacity) is unexpectedly much lower than the set Len (length).

      5. 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).

      6. Confirm that when the overflow condition is met, subsequent accesses to the slice lead to out-of-bounds memory writes.

      7. Setup: Create a Go program that imports the reflect2 library with unsafe configuration: cfg := reflect2.ConfigUnsafe.

      8. 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).

      9. Trigger Overflow: Call UnsafeGrow on this slice with a newLength that is slightly larger than the current capacity. This should trigger the integer overflow in calcNewCap. For instance, if the initial capacity is (1 << 30), set newLength to (1 << 30) + 10.

      10. Execute UnsafeGrow: Execute the sliceType.UnsafeGrow(slicePtr, newLength) function.

      11. 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) and sliceType.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.
      12. 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 and typedslicecopy 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.