みずりゅの自由帳

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

Go言語でのInteger Overflow対策にはstrconv.ParseIntを利用する

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

発表者の@rung氏からInteger Overflowの根本対策の話を聞いたので、実際にコードにして確認してみた。



なお、前回書いた記事についてはこちらを参照。

mzryuka.hatenablog.jp

対策について

前回の復習から。

Integer Overflowへの対策に関して、@rung氏のつぶやき。

すなわち、「strconv.ParseInt」か「strconv.ParseUint」を使い適切なbitSizeを指定する、とあります。

代わりに利用する関数の確認

この2つは、どちらも引数に「変換対象文字列」「基数」「ビットサイズ」を渡します。

使い方は、こんな感じ。
72を基数10、32ビットサイズで変換します。

v, err := strconv.ParseInt("72", 10, 32)

なお、指定したビットサイズ内で表現しきれない場合、errにエラーが設定されます。
下記の例では、7ビットでは表現できない72をパースしようとして、エラーとなります。

v, err := strconv.ParseInt("72", 10, 7)

errには、以下のように範囲外である旨のメッセージが格納されていました。

strconv.ParseInt: parsing "72": value out of range

詳細はドキュメントを参照ください。

https://golang.org/pkg/strconv/#ParseInt

https://golang.org/pkg/strconv/#ParseUint

strconv.ParseIntに置き換えて実行してみる

では、実際に「strconv.Atoi」を「strconv.ParseInt」へ置き換えてみましょう。
基数10、64ビットで対応します。

package main

import (
    "fmt"
    "strconv"
)

func main() {
    s := "2147483648"
//    bigValue, err := strconv.Atoi(s)
    bigValue, err := strconv.ParseInt(s, 10, 64)
    if err != nil {
        panic(err)
    }
    if bigValue < 0 {
        return
    }
    fmt.Printf("%v is %v\n", bigValue, int32(bigValue))
}

この実行結果はこちら。

2147483648 is -2147483648

この場合は、前回同様に「bigValue」と「int32(bigValue)」は別の値となります。

では、64ビットから32ビットにしてみます。

package main

import (
    "fmt"
    "strconv"
)

func main() {
    s := "2147483648"
//    bigValue, err := strconv.Atoi(s)
    bigValue, err := strconv.ParseInt(s, 10, 32)
    if err != nil {
        panic(err)
    }
    if bigValue < 0 {
        return
    }
    fmt.Printf("%v is %v\n", bigValue, int32(bigValue))
}

32ビットでは「2,147,483,648」を表現できないので、errに値が設定されてきます。
この実行結果は、こうなります。

panic: strconv.ParseInt: parsing "2147483648": value out of range

まとめ

実際に「strconv.ParseInt」を試してみたわけですが、確かに適切なビットサイズ指定を行う場合には有効そうです。
一方で、適当(もしくはあまり何も考えずに)に大きめなビットサイズを設定した場合は、Integer Overflowが起きる状況はそのままです。

ちなみに、ビットサイズを適切に設定しろ、と言うのはドキュメントにも書かれていました。

The parse functions return the widest type (float64, int64, and uint64), but if the size argument specifies a narrower width the result can be converted to that narrower type without data loss:

直訳だと、以下のような感じ。

パース関数は最も幅の広い型(float64、int64、および uint64)を返しますが、size 引数でより狭い幅を指定した場合、結果はデータを失うことなくその狭い型に変換されます。

詳細はこちらをご参照。

golang.org



そういえば、「Gophers Office Hours #14 Goとセキュリティ vol.2」が11/2に実施されるそうです。
早速、申し込みをしました。

mercari.connpass.com