Use of the unsafe package

Use of the unsafe package

Description

Go's unsafe package provides low-level pointer conversions and direct memory access. Bypassing type and memory safety can turn incorrect pointer calculations or type conversions into memory corruption, information exposure or crashes. Exploitability depends on input sources and what the code does.

Potential impact

  • Writes to the wrong address can corrupt variables or structures and crash the program.
  • Reads from unintended memory can expose tokens, passwords or keys.
  • An attacker who controls a memory-corruption path may be able to execute code.
  • Assumptions about memory layout and lifetime can make maintenance and verification difficult.

Remediation

  • Prefer typed field access and safe standard-library APIs over unsafe in ordinary application logic.
  • Isolate necessary uses in small functions and document assumptions about pointer lifetime, alignment and bounds.
  • Verify those assumptions with boundary tests, fuzzing and independent code review.
  • Implement separate authentication and authorization checks for permission changes. Removing unsafe or wrapping an assignment in a function does not add authorization.

Examples

Before

go
package main

import (
    "fmt"
    "unsafe"
)

type User struct {
    ID   int
    Role int
}

func main() {
    u := User{ID: 1, Role: 0} // 0: ordinary user

    // Before: direct memory manipulation with unsafe
    // Convert the pointer to uintptr, add an offset, then convert back
    basePtr := unsafe.Pointer(&u)

    // Manually assume a fixed layout with Role immediately after ID
    idSize := unsafe.Sizeof(u.ID)
    rolePtr := (*int)(unsafe.Pointer(uintptr(basePtr) + idSize))

    // Write memory directly to change Role to 1 (administrator)
    *rolePtr = 1

    fmt.Printf("User: ID=%d, Role=%d (관리자 권한으로 변조됨)\n", u.ID, u.Role)
}

After

go
package main

import "fmt"

type Role int

const (
    RoleUser  Role = 0
    RoleAdmin Role = 1
)

type User struct {
    ID   int
    Role Role
}

// Perform important changes through explicit functions
func PromoteToAdmin(u *User) {
    // Implement authorization policy, logging and auditing here
    u.Role = RoleAdmin
}

func main() {
    u := User{ID: 1, Role: RoleUser}

    // Change Role through application logic
    PromoteToAdmin(&u)

    fmt.Printf("User: ID=%d, Role=%d\n", u.ID, u.Role)
}

Explanation:

  • Before: The code calculates a field offset manually to change Role. This does not establish that the displayed pair of int fields has an invalid layout or that this program alone exposes an external attack. It illustrates the need to keep manual calculations valid when the structure changes.
  • After: Direct access to u.Role removes manual address calculation. PromoteToAdmin unconditionally changes the role in this example; actual authorization and auditing must be implemented separately.

References