みずりゅの自由帳

主に参加したイベントやソフトウェア技術/開発について記録しています

「Gophers Office Hours #13 〜セキュリティ回〜」で話されていたInteger Overflowの話。

2020年10月19日、オンラインイベント「Gophers Office Hours #13 〜セキュリティ回〜」に参加しました。
イベントの詳細はこちら。

mercari.connpass.com

このイベントでは、Go言語のセキュアコーディングの話をされていたのですが、その中で話されていた「Integer Overflow」の話が印象に残ったので、ブログの記事として記録しておきます。


発表者の@rung氏(Mercariのセキュリティエンジニアの方)があげた、セキュアコーディングのポイントは以下の3点でした。

  • unsafeを使わない限り、バッファオーバーフローとかは起こらない
    • Unsafeを利用すると「みんな大好きポインタ演算」が可能に。
  • Integer Overflowなどは起こりうる
  • Cgoは怖い

この中で、ホェーと声を出してしまったのが、2番目の「Integer Overflow」の話。

どんな話かというよりも、先にコードを載せます。
この時点で気付けたら凄い。

動作確認をしたいなら、 https://play.golang.org/ とかに、コードを貼り付けてみると良いでしょう。

package main
import (
    "fmt"
    "strconv"
)

func main() {
    bigValue, err := strconv.Atoi("2147483647")
    if err != nil {
        panic(err)
    }
    fmt.Println(int32(bigValue))
}


ちなみに、実行結果はこちら。
まぁ、納得ですね。

2147483647

では、上記のコードがこうなったらどうなるでしょうか?
変更箇所は数値のみです。

package main
import (
    "fmt"
    "strconv"
)

func main() {
    bigValue, err := strconv.Atoi("2147483648")
    if err != nil {
        panic(err)
    }
    fmt.Println(int32(bigValue))
}

ピンと来られた方もいるかと思いますが、実行するとこうなります。

-2147483648

想定では2147483648になるはずだったのに、-2147483648になってしまいました。
では、マイナスの値になったら処理を終了するようにコードを修正してみましょう。
コード中に、値「bigValue」が0未満ならreturnする処理を追加。
「bigValue < 0」のif文を入れてみましょう。

package main

import (
    "fmt"
    "strconv"
)

func main() {
    bigValue, err := strconv.Atoi("2147483648")
    if err != nil {
        panic(err)
    }
    if bigValue < 0 {
        return
    }
    fmt.Println(int32(bigValue))
}

で、実行結果は、こちら。

-2147483648

予想外でしたか?
それとも予想通りでしたでしょうか。

ポイントは、 "strconv.Atoi"と"int32"ですね。
strconv.Atoiで変換した際には、int64の値となって返ってきています。
つまり、「bigValue」はint64の型の値です。
文字通り、int64は64bitで、int32は32bit。
両者で表現できる値は、以下の通り。

  • int32: 符号あり、-2147483648  〜 2147483647
  • int64: 符号あり、-9223372036854775808 〜 9223372036854775807

マイナスの値は、2の補数で表現されています。
int32の場合、32番目のビット(先頭のビット)が1になれば負の値としての表現になります。
2147483647は、32番目以外のビットが1で32番目が0の時に表現できる最大の値*1です。
よって、それより1大きい2147483648では、32番目のビットが1*2となり、負の値の表現となります。


そして、if文でbigValueの値をチェックしているタイミングでは、int64で確認しているので0以上の値(2147483648)です。
しかし、マイナス値になるのは、最後に型変換を発生させたタイミングです。
この時にオーバーフローしてマイナス値になります。
つまり、型変換した後の値をチェックしないといけないわけです。

今回のサンプルコードでは一つの関数の中に値が設定されているので気付きやすいかもしれません。

しかし、これが細かく分割されていたとしたら。

たとえば、ユーザが入力するデータだとしたら。
入力直後にチェックを入れているからOK、とかなりそうじゃないですか?
そのコードがあったら、型変換後にチェックは入れないかもしれません。

...こわー。


なお、この問題は人間によるレビューでは気づかれにくい傾向もあるようです。
対処方法としては、Gosecなどの静的解析ツールを利用すること、だそうです。*3

本質的な対策について

※追記:ブログ投稿後、発表者のrung氏から、本件の本質的な対策についてのコメントをいただけました。ありがとうございます。

本件の本質的な対策としては、以下となるそうです。

この攻撃への対策はstrconv.Atoiを使うのをやめて、
strconv.ParseIntかstrconv.ParseUintを使い適切なbitSizeを指定すること




まとめ

イベントでは、Integer Overflow以外の話もされていましたが、自分の琴線に触れたのはInteger Overflowについてでした。
Gosec、実行するの忘れないようしようと心に刻みました(笑

なお、セキュリティ回は2回目も実施されるようです。(実施日は未定)
次も可能なら参加しようと考えています。

*1:2進数だと01111111111111111111111111111111

*2:2進数だと10000000000000000000000000000000

*3:Gosecでは、int64をint32やint16に変換する箇所をチェックしてれるそうです。