unsafeパッケージの使用

unsafeパッケージの使用

説明

Goの unsafe パッケージは、ポインター変換やメモリへの直接アクセスを行う低水準の機能です。型とメモリの安全性を迂回するため、ポインター計算や型変換を誤ると、メモリ破損、情報漏えい、異常終了につながるおそれがあります。実際の悪用可能性は、入力元とコードの動作によって異なります。

想定される影響

  • 誤ったアドレスへの書き込みで変数や構造体が破損し、プログラムが停止する可能性があります。
  • 意図しないメモリを読み、トークン、パスワード、鍵が漏えいするおそれがあります。
  • 攻撃者がメモリ破損の経路を制御できる場合、任意のコード実行につながる可能性があります。
  • メモリ配置や寿命への依存により、保守や検証が難しくなる場合があります。

対処方法

  • 通常のアプリケーション処理では、unsafe よりも型のあるフィールドアクセスと安全な標準ライブラリAPIを優先してください。
  • 必要な使用箇所は小さな関数へ分離し、ポインターの寿命、アラインメント、範囲に関する前提を記録してください。
  • 境界値テスト、ファジング、独立したコードレビューで前提を検証してください。
  • 権限変更には認証・認可の検証を別途実装してください。unsafe の削除や関数によるラップだけで、権限検証が追加されるわけではありません。

例

変更前

go
package main

import (
    "fmt"
    "unsafe"
)

type User struct {
    ID   int
    Role int
}

func main() {
    u := User{ID: 1, Role: 0} // 0: 一般ユーザー

    // 変更前: unsafeによる直接のメモリ操作
    // ポインターをuintptrへ変換し、オフセットを加えてから戻す
    basePtr := unsafe.Pointer(&u)

    // 固定の配置を仮定し、IDの直後にRoleがあるものとして手動計算
    idSize := unsafe.Sizeof(u.ID)
    rolePtr := (*int)(unsafe.Pointer(uintptr(basePtr) + idSize))

    // メモリを直接変更し、Roleを1(管理者)にする
    *rolePtr = 1

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

変更後

go
package main

import "fmt"

type Role int

const (
    RoleUser  Role = 0
    RoleAdmin Role = 1
)

type User struct {
    ID   int
    Role Role
}

// 権限昇格などの重要な変更は明示的な関数で行う
func PromoteToAdmin(u *User) {
    // 権限変更ポリシー、ログ、監査などをここに実装する
    u.Role = RoleAdmin
}

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

    // アプリケーションの処理を通じてRoleを変更
    PromoteToAdmin(&u)

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

解説:

  • 変更前: フィールドのオフセットを手動で計算し、Role を変更します。表示した2つの int フィールドの配置が誤っている、またはこのプログラムだけで外部から攻撃できるという意味ではありません。構造体の変更後も、手動計算の妥当性を確認する必要があることを示します。
  • 変更後: u.Role を直接参照し、手動のアドレス計算をなくします。この例の PromoteToAdmin は無条件にロールを変更するため、実際の権限検証と監査は別途実装する必要があります。

参考資料