前回書いた、「「Gophers Office Hours #13 〜セキュリティ回〜」で話されていたInteger Overflowの話。」の続き。
発表者の@rung氏からInteger Overflowの根本対策の話を聞いたので、実際にコードにして確認してみた。
なお、前回書いた記事についてはこちらを参照。
対策について
前回の復習から。
Integer Overflowへの対策に関して、@rung氏のつぶやき。
本日お話した、Goで起きがちなInteger Overflowのお話について、まとめていただきました。ありがとうございます。https://t.co/WEOrjLcutK
— Hiroki Suezawa (@rung) 2020年10月19日
そして対策を話していませんでした。対策はstrconv.ParseIntかstrconv.ParseUintを使い適切なbitSizeを指定する、です。 #mercarigo
すなわち、「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
詳細はドキュメントを参照ください。
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 引数でより狭い幅を指定した場合、結果はデータを失うことなくその狭い型に変換されます。
詳細はこちらをご参照。
そういえば、「Gophers Office Hours #14 Goとセキュリティ vol.2」が11/2に実施されるそうです。
早速、申し込みをしました。