# Go Security Deep Dive

> Go Language Security Audit Deep Dive Module
> Covers: Concurrency Safety, unsafe Package, cgo, Web Frameworks

---

## Overview

Although Go is memory-safe, it still presents unique security challenges: concurrency race conditions, unsafe package abuse, cgo boundary issues, and web framework-specific vulnerabilities.

---

## Concurrency Safety (Race Conditions)

### 1. Data Race Detection

```go
// Dangerous: concurrent read/write of map
var cache = make(map[string]string)

func GetCache(key string) string {
    return cache[key]  // Concurrent read
}

func SetCache(key, value string) {
    cache[key] = value  // Concurrent write - DATA RACE!
}

// Safe: use sync.RWMutex
var (
    cache = make(map[string]string)
    mu    sync.RWMutex
)

func GetCache(key string) string {
    mu.RLock()
    defer mu.RUnlock()
    return cache[key]
}

func SetCache(key, value string) {
    mu.Lock()
    defer mu.Unlock()
    cache[key] = value
}

// Or use sync.Map
var cache sync.Map

func GetCache(key string) (string, bool) {
    v, ok := cache.Load(key)
    if !ok {
        return "", false
    }
    return v.(string), true
}
```

### 2. Channel Race

```go
// Dangerous: send on closed channel
func worker(ch chan int) {
    for i := 0; i < 10; i++ {
        ch <- i
    }
    close(ch)  // Close
}

func main() {
    ch := make(chan int)
    go worker(ch)

    // Another goroutine may send after close
    go func() {
        ch <- 100  // panic: send on closed channel
    }()
}

// Safe: use sync.Once or context
var closeOnce sync.Once

func safeClose(ch chan int) {
    closeOnce.Do(func() {
        close(ch)
    })
}
```

### 3. Read-Modify-Write Race

```go
// Dangerous: non-atomic counter
var counter int

func increment() {
    counter++  // Non-atomic: read → add 1 → write
}

// Safe: use atomic
var counter int64

func increment() {
    atomic.AddInt64(&counter, 1)
}

// Safe: use mutex
var (
    counter int
    mu      sync.Mutex
)

func increment() {
    mu.Lock()
    counter++
    mu.Unlock()
}
```

### 4. Check-Use Race (TOCTOU)

```go
// Dangerous: check then use
func processFile(path string) error {
    // Check if file exists
    if _, err := os.Stat(path); os.IsNotExist(err) {
        return err
    }

    // Race window: file may be deleted or replaced after check

    return os.Remove(path)  // Use
}

// Safe: operate directly, handle error
func processFile(path string) error {
    err := os.Remove(path)
    if os.IsNotExist(err) {
        return nil  // Or return error
    }
    return err
}
```

### 5. Race Detector

```bash
# Enable race detection at build time
go build -race ./...

# Detect during testing
go test -race ./...

# Detect at runtime
go run -race main.go
```

---

## unsafe Package Safety

### 1. Common Dangerous Usages

```go
import "unsafe"

// Dangerous: arbitrary memory access
func readMemory(addr uintptr, size int) []byte {
    // May cause segfault or information leak
    return (*[1 << 30]byte)(unsafe.Pointer(addr))[:size]
}

// Dangerous: type conversion bypass
func bypassTypeSystem(i int) *string {
    // Cast int pointer to string pointer - undefined behavior
    return (*string)(unsafe.Pointer(&i))
}

// Dangerous: modifying immutable data
func modifyString(s string) {
    // Strings are immutable in Go
    // Modifying through unsafe may cause crash
    sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
    data := (*[1 << 30]byte)(unsafe.Pointer(sh.Data))[:sh.Len]
    data[0] = 'X'  // Dangerous!
}
```

### 2. Detection Rules

```regex
# unsafe package usage
import\s+"unsafe"
unsafe\.Pointer
unsafe\.Sizeof
unsafe\.Offsetof
unsafe\.Alignof

# Dangerous uintptr conversion
uintptr\(unsafe\.Pointer
\(\*[^)]+\)\(unsafe\.Pointer
```

### 3. Safe Alternatives

```go
// Use encoding/binary instead of unsafe type conversion
import "encoding/binary"

func intToBytes(i int32) []byte {
    buf := make([]byte, 4)
    binary.LittleEndian.PutUint32(buf, uint32(i))
    return buf
}

// Use reflect instead of unsafe memory operations
import "reflect"

func getFieldByName(obj interface{}, name string) interface{} {
    v := reflect.ValueOf(obj)
    f := v.FieldByName(name)
    return f.Interface()
}
```

---

## cgo Safety

### 1. Memory Safety Issues

```go
/*
#include <stdlib.h>
#include <string.h>

void vulnerable_copy(char* dst, char* src, int len) {
    memcpy(dst, src, len);  // May be out of bounds
}
*/
import "C"
import "unsafe"

// Dangerous: buffer overflow
func copyData(dst, src []byte) {
    C.vulnerable_copy(
        (*C.char)(unsafe.Pointer(&dst[0])),
        (*C.char)(unsafe.Pointer(&src[0])),
        C.int(len(src)),  // If src > dst, overflow
    )
}

// Dangerous: dangling pointer
func dangling() *C.char {
    s := "hello"
    cs := C.CString(s)
    // Forgot C.free(unsafe.Pointer(cs))
    return cs  // After return, Go string may be collected
}

// Safe: proper memory management
func safeCopy(dst, src []byte) {
    if len(src) > len(dst) {
        src = src[:len(dst)]
    }
    copy(dst, src)  // Use Go built-in copy
}

func safeString() string {
    cs := C.some_c_function()
    defer C.free(unsafe.Pointer(cs))
    return C.GoString(cs)  // Copy to Go memory
}
```

### 2. Detection Rules

```regex
# cgo usage
import\s+"C"
/\*.*#include.*\*/
C\.[A-Za-z]+\(

# Dangerous cgo patterns
C\.CString\([^)]+\)(?!.*C\.free)
C\.malloc\(
unsafe\.Pointer.*C\.
```

---

## Command Injection

### 1. Dangerous Patterns

```go
import "os/exec"

// Dangerous: shell execution with user input
func runCommand(userInput string) error {
    cmd := exec.Command("sh", "-c", userInput)
    return cmd.Run()
}

// Dangerous: string concatenation
func runCommand(filename string) error {
    cmd := exec.Command("sh", "-c", "cat "+filename)
    return cmd.Run()
}

// Safe: execute directly, no shell
func runCommand(filename string) error {
    cmd := exec.Command("cat", filename)
    return cmd.Run()
}

// Safer: use built-in functions
func readFile(filename string) ([]byte, error) {
    // Validate path
    cleanPath := filepath.Clean(filename)
    if !filepath.IsAbs(cleanPath) {
        cleanPath = filepath.Join(baseDir, cleanPath)
    }
    return os.ReadFile(cleanPath)
}
```

### 2. Detection Rules

```regex
# Dangerous command execution
exec\.Command\s*\(\s*"(sh|bash|cmd)"\s*,\s*"-c"
exec\.Command.*\+\s*[a-zA-Z]
exec\.CommandContext.*\+

# syscall execution
syscall\.Exec\s*\(
syscall\.ForkExec\s*\(
```

---

## SQL Injection

### 1. Dangerous Patterns

```go
import "database/sql"

// Dangerous: string concatenation
func getUser(db *sql.DB, id string) (*User, error) {
    query := "SELECT * FROM users WHERE id = " + id
    row := db.QueryRow(query)
    // ...
}

// Dangerous: fmt.Sprintf
func getUser(db *sql.DB, id string) (*User, error) {
    query := fmt.Sprintf("SELECT * FROM users WHERE id = '%s'", id)
    row := db.QueryRow(query)
    // ...
}

// Safe: parameterized queries
func getUser(db *sql.DB, id string) (*User, error) {
    query := "SELECT * FROM users WHERE id = $1"
    row := db.QueryRow(query, id)
    // ...
}
```

### 2. GORM-Specific

```go
import "gorm.io/gorm"

// Dangerous: Raw concatenation
func search(db *gorm.DB, name string) []User {
    var users []User
    db.Raw("SELECT * FROM users WHERE name = '" + name + "'").Scan(&users)
    return users
}

// Dangerous: Where string
func search(db *gorm.DB, name string) []User {
    var users []User
    db.Where("name = '" + name + "'").Find(&users)
    return users
}

// Safe: parameterized
func search(db *gorm.DB, name string) []User {
    var users []User
    db.Where("name = ?", name).Find(&users)
    return users
}

// Safe: struct conditions
func search(db *gorm.DB, name string) []User {
    var users []User
    db.Where(&User{Name: name}).Find(&users)
    return users
}
```

### 3. Detection Rules

```regex
# String concatenation SQL
(Query|Exec|QueryRow)\s*\([^?]*\+
(Query|Exec|QueryRow)\s*\(.*fmt\.Sprintf

# GORM dangerous patterns
\.Raw\s*\([^?]*\+
\.Where\s*\("[^"]*'\s*\+
\.Order\s*\([^)]*\+
```

---

## Path Traversal

### 1. Dangerous Patterns

```go
// Dangerous: direct path concatenation
func serveFile(w http.ResponseWriter, r *http.Request) {
    filename := r.URL.Query().Get("file")
    data, _ := os.ReadFile("/data/" + filename)
    w.Write(data)
}
// payload: ?file=../../../etc/passwd

// Safe: path cleaning and validation
func serveFile(w http.ResponseWriter, r *http.Request) {
    filename := r.URL.Query().Get("file")

    // Clean path
    cleanPath := filepath.Clean(filename)

    // Ensure within allowed directory
    fullPath := filepath.Join("/data", cleanPath)
    if !strings.HasPrefix(fullPath, "/data/") {
        http.Error(w, "Invalid path", http.StatusBadRequest)
        return
    }

    data, err := os.ReadFile(fullPath)
    if err != nil {
        http.Error(w, "File not found", http.StatusNotFound)
        return
    }
    w.Write(data)
}
```

### 2. Zip Slip

```go
import "archive/zip"

// Dangerous: unvalidated paths during extraction
func unzip(src, dest string) error {
    r, _ := zip.OpenReader(src)
    defer r.Close()

    for _, f := range r.File {
        // Dangerous: f.Name may contain ../
        path := filepath.Join(dest, f.Name)

        rc, _ := f.Open()
        outFile, _ := os.Create(path)  // May write to arbitrary location
        io.Copy(outFile, rc)
        // ...
    }
    return nil
}

// Safe: validate extraction paths
func unzip(src, dest string) error {
    r, _ := zip.OpenReader(src)
    defer r.Close()

    dest = filepath.Clean(dest) + string(os.PathSeparator)

    for _, f := range r.File {
        path := filepath.Join(dest, f.Name)

        // Verify path is within target directory
        if !strings.HasPrefix(path, dest) {
            return fmt.Errorf("illegal file path: %s", f.Name)
        }

        // ... continue extraction
    }
    return nil
}
```

---

## SSRF

```go
import "net/http"

// Dangerous: unvalidated URL
func fetchURL(w http.ResponseWriter, r *http.Request) {
    url := r.URL.Query().Get("url")
    resp, _ := http.Get(url)
    io.Copy(w, resp.Body)
}
// payload: ?url=http://169.254.169.254/latest/meta-data/

// Safe: URL whitelist validation
var allowedHosts = map[string]bool{
    "api.example.com": true,
    "cdn.example.com": true,
}

func fetchURL(w http.ResponseWriter, r *http.Request) {
    rawURL := r.URL.Query().Get("url")

    parsedURL, err := url.Parse(rawURL)
    if err != nil {
        http.Error(w, "Invalid URL", http.StatusBadRequest)
        return
    }

    // Check protocol
    if parsedURL.Scheme != "https" {
        http.Error(w, "Only HTTPS allowed", http.StatusBadRequest)
        return
    }

    // Check host whitelist
    if !allowedHosts[parsedURL.Host] {
        http.Error(w, "Host not allowed", http.StatusBadRequest)
        return
    }

    // Check for internal IPs
    ips, _ := net.LookupIP(parsedURL.Hostname())
    for _, ip := range ips {
        if isPrivateIP(ip) {
            http.Error(w, "Private IP not allowed", http.StatusBadRequest)
            return
        }
    }

    resp, _ := http.Get(rawURL)
    io.Copy(w, resp.Body)
}

func isPrivateIP(ip net.IP) bool {
    private := []string{
        "10.0.0.0/8",
        "172.16.0.0/12",
        "192.168.0.0/16",
        "127.0.0.0/8",
        "169.254.0.0/16",
    }
    for _, cidr := range private {
        _, subnet, _ := net.ParseCIDR(cidr)
        if subnet.Contains(ip) {
            return true
        }
    }
    return false
}
```

---

## Web Framework-Specific Vulnerabilities

### Gin

```go
import "github.com/gin-gonic/gin"

// Dangerous: trusting proxy headers
r := gin.Default()
// By default trusts all proxy headers, may lead to IP spoofing

// Safe: configure trusted proxies
r.SetTrustedProxies([]string{"192.168.1.0/24"})

// Dangerous: template injection
r.GET("/hello", func(c *gin.Context) {
    name := c.Query("name")
    c.HTML(http.StatusOK, "index.tmpl", gin.H{
        "content": template.HTML(name),  // Unescaped
    })
})
```

### Echo

```go
import "github.com/labstack/echo/v4"

// Dangerous: binding attack
type User struct {
    Name  string `json:"name"`
    Admin bool   `json:"admin"`
}

e.POST("/user", func(c echo.Context) error {
    u := new(User)
    c.Bind(u)  // User can set Admin=true
    // ...
})

// Safe: use DTO
type CreateUserDTO struct {
    Name string `json:"name"`
}
```

---

## Detection Commands

```bash
# Race detection
go build -race ./...
go test -race ./...

# unsafe usage
grep -rn "unsafe\." --include="*.go"

# cgo usage
grep -rn 'import "C"' --include="*.go"

# SQL injection
grep -rn "Query\|Exec\|QueryRow" --include="*.go" | grep "+"

# Command execution
grep -rn "exec\.Command" --include="*.go"

# Path operations
grep -rn "filepath\.Join\|os\.Open\|os\.ReadFile" --include="*.go"

# Static analysis tools
gosec ./...
staticcheck ./...
```

---

## Audit Checklist

```
[ ] Run go build -race to detect data races
[ ] Search for unsafe package usage, verify necessity
[ ] Check cgo code memory management
[ ] Check if SQL queries use parameterization
[ ] Check if command execution has shell injection risk
[ ] Check if file operations have path traversal
[ ] Check if HTTP requests have SSRF risk
[ ] Check web framework configuration (trusted proxies, binding, etc.)
[ ] Run gosec static analysis
[ ] Verify correct usage of concurrency primitives
```

---

## Minimal PoC Examples
```bash
# -race concurrency detection
go test -race ./...

# SSRF
curl "http://localhost:8080/fetch?url=http://169.254.169.254/latest/meta-data/"

# Path traversal
curl "http://localhost:8080/download?file=../../etc/passwd"
```

---

**Last Updated**: 2026-01-23
**Version**: 1.0.0
