危険なキャストをおこなってバグを誘発した例をみつけたのでご紹介します。
不具合の経緯としては
- あるグローバル変数をconst修飾子をつけて定義した。
- ところがこのグローバル変数のポインタを別のポインタ変数に代入するときに危険なキャスト(const外し)をおこなった。
- 代入先のポインタを操作してconst修飾子をつけた(read-onlyの)変数の書き換えを行った。
書込み禁止領域に書込みをおこなって「セグメンテーション違反」が発生する例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <stdio.h> #include <stdlib.h> int main(void) { char* hello = "Hello World"; printf("%s\n", hello); hello[0] = 'h'; /* <= セグメンテーション(アクセス)違反 */ printf("%s\n", hello); return (0); } |
文字列リテラル “Hello World” は、read-onlyです。WindowsやLinuxなどモダンなメモリ保護機能のあるOS環境下ではセグメンテーション違反が発生します。
JPCERT セキュアコーディングスタンダード
- 文字列リテラルを変更しない : https://www.jpcert.or.jp/sc-rules/c-str30-c.html
- 文字列リテラルの参照には const へのポインタを使用する : https://www.jpcert.or.jp/sc-rules/c-str05-c.html
危険なキャスト(const外し)をおこなった例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include <stdio.h> #include <stdlib.h> void swap(const char* x, const char* y) { char tmp; char* xx = (char*)x; char* yy = (char*)y; tmp = *xx; *xx = *yy; *yy = tmp; } int main(void) { char a = 'A'; char b = 'B'; printf("a = %c, b = %c\n", a, b); swap(&a, &b); printf("a = %c, b = %c\n", a, b); return (0); } |
上記のソースコード書き換えの『肝』は
1 |
char* xx = (char*)x; |
としてキャスト演算で const修飾子(read-only制約)を外しているところです。
キャスト演算というと
1 2 |
char c = "F"; int i = (int)c; |
のように異なる型の変換に使われることが多いですが、先の例のように const修飾 (read-only制約) を外すことにもつかえます。
C言語はコンピュータのハードウェア・レイヤ(メモリやアドレス)に直接触れることができる切れ味の鋭いプログラミング言語です。一方で生身のハードウェアを直接操作できるため上記のような『横紙破り』も許してしまいます。
おまけ
先に例としてあげたswap関数の関数引数に const修飾子 をつけることは説明以上の効果効用はありません。下記に実際にあり得る const修飾子 を引数に持つswap関数の例を挙げます。
下記のケースでは文字列ポインタ pa と pb がポイントしている実体 “Hello” と “Bye” は書き換えていません。”Hello” を “hello” や “HELLO” に変更することはできないし、変更してはなりません。文字列リテラルは『イミュータブル (immutable)』です。文字列ポインタ pa が “Hello” をポイントしているの? “Bye” をポイントしているの? という部分を swap しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <stdio.h> #include <stdlib.h> void swap(const char** x, const char** y) { const char* tmp; tmp = *x; *x = *y; *y = tmp; } int main(void) { char* pa = "Hello"; char* pb = "Bye"; printf("a = %s, b = %s\n", pa, pb); swap((const char**)&pa, (const char**)&pb); printf("a = %s, b = %s\n", pa, pb); return (0); } |