seclang01
の編集
http://essen.osask.jp/?seclang01
[
トップ
] [
編集
|
差分
|
バックアップ
|
添付
|
リロード
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
-- 雛形とするページ --
BracketName
EssenRev4
FormattingRules
FrontPage
Help
InterWiki
InterWikiName
InterWikiSandBox
K
MenuBar
PHP
PukiWiki
PukiWiki/1.4
PukiWiki/1.4/Manual
PukiWiki/1.4/Manual/Plugin
PukiWiki/1.4/Manual/Plugin/A-D
PukiWiki/1.4/Manual/Plugin/E-G
PukiWiki/1.4/Manual/Plugin/H-K
PukiWiki/1.4/Manual/Plugin/L-N
PukiWiki/1.4/Manual/Plugin/O-R
PukiWiki/1.4/Manual/Plugin/S-U
PukiWiki/1.4/Manual/Plugin/V-Z
RecentDeleted
SDL2_01
SandBox
WikiEngines
WikiName
WikiWikiWeb
YukiWiki
a21
a21_bbs01
a21_count
a21_memo01
a21_tl9a
a21_txt01
a21_txt01_10
a21_txt01_1a
a21_txt01_2
a21_txt01_2a
a21_txt01_2b
a21_txt01_3
a21_txt01_4
a21_txt01_5
a21_txt01_6
a21_txt01_6a
a21_txt01_7
a21_txt01_8
a21_txt01_8a
a21_txt01_9
a21_txt01_9a
a21_txt02
a21_txt02_1a
a21_txt02_1b
aclib00
aclib01
aclib02
aclib03
aclib04
aclib05
aclib06
aclib07
aclib08
aclib09
aclib10
aclib11
aclib12
aclib13
aclib14
aclib15
aclib16
aclib17
aclib18
aclib19
aclib20
aclib21
aclib22
aclib23
aclib24
aclib25
aclib_bbs
arm64_01
avm0001
edu0001
edu0002
esb02b_hrb
esb_dbg
esbasic0001
esbasic0002
esbasic0003
esbasic0004
esbasic0005
esbasic0006
esbasic0007
esbasic0008
esbasic0009
esbasic0010
esbasic0011
esbasic0012
esbasic0013
esbasic0014
esbasic0015
esbasic0016
esbasic0017
esbasic02a
esc0001
escm0001
essen_hist
esvm0001
esvm0002
esvm0003
esvm0004
esvm0005
esvm0006
esvm_i0
hh4a
idea0001
idea0002
idea0003
impressions
jck_0000
jck_0001
kawai
kbcl0_0000
kbcl0_0001
kbcl0_0002
kbcl0_0003
kbcl0_0004
kbcl0_0005
kbcl0_0006
kbcl0_0007
kclib1_0000
kclib1_0001
kclib1_0002
kclib1_0003
kclib1_0004
kclib1_0005
kclib1_0006
kclib1_0007
kclib1_0008
kclib1_0009
kclib1_0010
kpap0001
members
memo0001
osask4g
osask4g_r2
p20200311a
p20200610a
p20200610b
p20200624a
p20200711a
p20200716a
page0001
page0002
page0003
page0004
page0005
page0006
page0007
page0008
page0009
page0010
page0011
page0012
page0013
page0014
page0015
page0016
page0017
page0018
page0019
page0020
page0021
page0022
page0023
populars
seccamp
seccamp2019
sechack
sechack2019
seclang01
sh3_2020
sh3_2020_kw
sh3_2020_nk
sh3_2021_kw
sh3_2021_nk
sh3_kw_hist
termux001
termux002
text0001
text0001a
text0002
text0002a
text0003
text0004
text0005
text0006
text0006a
text0007
text0008
text0010
text0011
text0012
text0013
text0014
text0015
text0016
text0017
text0018
text0019
text0020
text0021
tl1c
tl2c
tl3c
tl3d
* プログラミング言語がセキュリティに関してやるべきこと#1 -(by [[K]], 2020.02.13) -(タイトルに#1ってついていますが、#2を作るかどうかは未定です。) ** (0) はじめに -私は、プログラミング言語を作りたい。・・・私は、プログラミング言語によって、セキュリティ上の問題の解決に寄与したい。 -プログラミング言語が、セキュリティに対してできることっていったいなんだろうか。 -これに対して、まず私は、''&size(20){「この言語を使っている限り、セキュリティ問題で悩まされることはもうないよ!」};''と言えるような言語を作ることが、ゴールなのではないかと考えた。 -これは理想であって、もちろんすぐに実現できるわけではないけれど、まずはそこを目指すことにする。 -さてこれを実現するためには、大きく分けて2つの設計思想があると思われる。 -[A] 危ない結果につながるかもしれない命令を削除する。これなら何をどう間違えてもセキュリティホールを作ることはない。間違いなく安全である。 -[B] 危ない結果につながるかもしれない命令は残すが、本当に「やらかした」場合にエラーを出して止まる。もし確実に検出できるのであれば、これも十分に安全である。 -これはたとえば、ポインタという言語上の機能について考えた時に、[A]のアプローチなら、ポインタは事故の元なので、そんなものはなくしてしまう。 -いっぽうで、[B]のアプローチなら、ヌルポインタアクセスとか、malloc範囲を超えたポインタアクセスをしてしまう場合にエラーを出すとか、そういう設計になる。 -私は、この[A]のアプローチには基本的に不満である。なぜなら、そんな風に機能を削除して行けば、まあ確かに安全にはなるかもしれないが、できることも少なくなってしまうからだ。 -それでは結局、やりたいことができなくなってその言語を使う理由そのものが怪しくなる。だからセキュリティ対策のために機能を削るというアプローチは正しくないと思う。 -しかし、できることが減らなければいいので、ポインタに変わる代替機能があり、しかもそれはポインタと同じくらいに便利でありさえすれば、ポインタが無くなることは何の問題もない。 ** (1) 言語で対処可能なセキュリティ問題の一覧 -以下に、言語側で支援が可能なセキュリティ問題を示す。これに対処できているとかいないとかを星取表の形で表現できたら、すごくわかりやすいのではないかと思う。 -それぞれの項目の詳細な説明は、後述する。 -記号の意味 --◎: 実行前に確実に検出できる(いわゆるコンパイル時エラー) --○: 実行時に確実に検出できる --△: 現在は対応していないが、将来的には対応予定 --×: 対応していないし、今のところは対応予定もない --?: 検討中 ---: 前提となる機能がない |番号||ES-BASIC 2020年1月末時点|ES-C 2020年1月末時点| |01-01|配列やポインタアクセスのレンジチェック|CENTER:○|CENTER:△| |01-02|不適切なポインタの減算|CENTER:-|CENTER:△| |01-03|ポインタアクセス時のアライメントチェック|CENTER:-|CENTER:△| |01-04|free後の不適切なアクセスをエラーにできる(use-after-free 脆弱性)|CENTER:-|CENTER:△| |01-05|二重freeをエラーにできる(double-free 脆弱性)|CENTER:-|CENTER:△| |01-06|メモリリークを発見できる(もしくはGCがあってリークがおきない)|CENTER:△|CENTER:△| |01-07|整数からポインタへの変換の禁止(特別な構文でのみ許可するなど)|CENTER:-|CENTER:△| |02-01|数値演算のオーバーフローがあった時にエラーにできる(かつ必要に応じてこれを部分的に抑制もできる)|CENTER:○|CENTER:△| |02-02|変数値に制約を持たせて、違反した時にエラーにできる|CENTER:○*1|CENTER:△*1| |03-01|エラー時に、どこでエラーが起きたのか、(最低でも)行単位で指摘できる|CENTER:○|CENTER:△| |03-02|どの行を何回実行したのかを報告できる|CENTER:○|CENTER:△| |03-03|エラー時にスタックトレースを表示できる|CENTER:△|CENTER:△| |04-01|通常実行モードと高速実行モードがあり、それは実行時にユーザが選べる|CENTER:○|CENTER:△| |04-02|実行を一時的に中断し、変数などを自由に確認したのちに、再開できる(そういう命令がある or 複数のブレークポイントが設定できる)|CENTER:○|CENTER:△| |04-03|キー入力などのアクションによっても、一時的に中断し、変数などを自由に確認したのちに、再開できる|CENTER:○|CENTER:△| |04-04|エラー時に、変数値などを自由に確認できる|CENTER:○|CENTER:△| |04-05|中断した状態をファイルに保存したり、そこから再開したりできる|CENTER:×|CENTER:△| |05-01|メモリを確保した時に、前のプログラムの実行結果などを不用意に渡さない|CENTER:×|CENTER:?| |06-01|ファジングやテストを支援する機能がある|CENTER:×|CENTER:△| -(*1) 変数宣言時に制約を書くことはできないが、デバッグトラップ関数を書くことで、同様のことは実現可能。 ** (2) 詳細説明 -[01-01] ''配列やポインタアクセスのレンジチェック'' --配列の添え字の指定が間違っていたとか、ポインタでmalloc域を超えたアクセスをしているとか、そういうエラーを検出する。 int a[10], b[10];などとしている状況で、int *p = &a[9]; のとき、p[1]はb[0]と同じところを指しているかもしれないが、基本的にはそういうアクセスも許すべきではない。意図してpを使ってbにアクセスしたのか、間違ってはみ出しただけなのか、分からないからである。 --当然のことながら、「ライブラリ関数にやらせれば、レンジチェックを回避したアクセスができてしまう」みたいなのはアウトである。 -[01-02] ''不適切なポインタの減算'' --2つのポインタの差を計算すると、それが同一の配列に属していれば、その間の要素の数を返すことになっているが、そうではない場合は、たとえ同じ型のポインタであってもエラーにすべきである。 -[01-03] ''ポインタアクセス時のアライメントチェック'' --int *p;があって、pはmalloc域の中を指していたとしても、何かのバグによってpが奇数番地になっていて、意味不明な値を書いたり読んだりしてしまうかもしれない。 そういうケースはエラーにできたほうがよい。 -[01-04] ''free後の不適切なアクセスをエラーにできる'' --当然だが、freeした領域にアクセスすることは許されない。それを検出できなければいけない。 --即座に思い付く安易な方法としては free(p); したら、自動的に p = 0; にしてしまえばいいと思うかもしれない。しかしこんな方法ではほとんど解決しない。なぜなら別の変数にポインタが残っているかもしれないからだ。 --だからこのアプローチでは解決とはみなさない。 --ではどうするか。すべての変数、すべての構造体(オブジェクト)を検査して、該当域を指しているものを探しつくして0にしていくべきなのか?さすがにそれは重いだろう。だからこれも現実的ではない。 --私ならこれを以下のアルゴリズムで解決する。 ---まずポインタは従来のメモリアドレスのほかに、管理用ハンドルとリビジョン番号も持つ。この管理用ハンドルからは、アクセス可能範囲とリビジョン番号を持つ構造体を得ることができる。 ---実際のメモリアクセス際しては、まず管理用ハンドルを使ってアクセス可能範囲を取得し、適合しているのかを確認する。 ---次に、取得したリビジョンとポインタ内のリビジョンが一致していることも確認する。 ---どちらも合格できていれば、その段階でメモリアクセスをする。 ---メモリがfreeされたとき、管理用ハンドルの先の構造体のリビジョン番号を更新する。これで、古いポインタを使ってアクセスしようとしたときは、全部エラー検出できるようになる。 ---リビジョン番号さえ更新されれば、その管理用ハンドルを他の領域を指し示すために再利用しても全く問題はない。 --上記のアルゴリズムであれば、ポインタの減算は、管理用ハンドルとリビジョンが一致するかを比較してからポインタの引き算をすればいいだけになる。 -[01-05] ''二重freeをエラーにできる'' --同じ領域に対して、freeを二度やってしまうのはもちろんエラーである(freeしたあとにmallocしてたまたま同じ領域を返されて、そこをfreeしたい場合を除く)。 -[01-06] ''メモリリークを発見できる'' --freeのし忘れは、やはり重大なバグの原因になりえるので、検出手段を提供するべきである。 --循環参照があっても、見逃さないような機能を是非サポートしてほしい。 -[01-07] ''整数からポインタへの変換の禁止'' --この必要性がよくわからないかもしれない。つまりはこういうことである。 --ある関数があったとして、そこにポインタ引数がなければ、メモリの任意の場所を勝手に読み書きするようなことはない、ということである。 --ポインタ引数があったとしても、その影響範囲はそのポインタでアクセス可能な範囲に限定される、と。 --これは実にあたりまえのことだけど、もし、整数からポインタへの変換が可能だと、int *p = (int *) 0x1234567; みたいにして、 p[123] = 4; などとすることで、関数は与えられもしない領域のメモリをいつでも破壊しうるということになってしまう。 --こんな可能性まで気にしなければいけないとしたら、とてもじゃないけどデバッグできない。 -[02-01] ''数値演算のオーバーフローがあった時にエラーにできる'' --何か適当な演算をしていて、オーバーフローに気付けないと、時には深刻なことになる。 --この計算値は絶対に正になる。なぜなら正の値しか加算していないからだ。と思って負の可能性を無視してコードを書いていたのに、オーバーフローによって負になってしまったら、当然だけど予期せぬ結果を招くだろう。 --もちろんここに示したそれ以外のセキュリティ機能によって、大きな被害が出る前にエラー停止できるかもしれないが、しかしそれでも「え?なんでこの変数が負になっているの?いつのまに???」となると原因究明は手間にになり、やはりオーバーフローした時点ですぐに指摘できればそのほうがずっとありがたい。 --(いやでも、自分の預金残高が、ものすごいプラスからものすごいマイナスへ、オーバーフローによって急変してしまったら、ポインタとかがおかしくなかったとしても、重大な障害だよね?!) -[02-02] ''変数値に制約を持たせて、違反した時にエラーにできる'' --この変数は7の倍数しかとらないとか、この変数は最大でも99にしからないなど、そういう制約を明らかにしておくことで、これに違反した時にすぐに気付けるようにする仕組みがあれば、きっと役立つことがあるだろう。 --しかし一方で、そういう記述はやや煩雑になりすぎるきらいもあり、言語仕様としてきれいにまとめるのは、結構な難易度がある(と[[K]]は思っている)。 -[03-01] ''エラー時に、どこでエラーが起きたのか、(最低でも)行単位で指摘できる'' --単にエラーがあったと分かるだけでは、ほとんどデバッグの役には立たない。一番大事なのはどこでエラーが起きたのか、である。これさえわかればエラー種別が不十分であっても文脈から容易に想像できる(できないこともあるが)。 --もちろんエラー種別も報告してもらえるのならそれに越したことはない。 -[03-02] ''どの行を何回実行したのかを報告できる'' --究極的には、どの行をどの順番で実行したかのログがあれば最強ではあるが、それは出力するのも集計するのもかなりのコストになりがちである。それなら、行ごとの実行回数が分かるだけでもかなり便利である。 --これがあれば、今回の実行でどのくらいの部分が実行テストされたのか確認できる。コンパイル時にすべてのエラーチェックが完全にできるのであれば、カバレッジなどどうでもよいのだが、実行時にチェックする言語の場合は、通っていないところについては十分なチェックがされていないので、それをプログラマに確認させるためにも、この機能は役に立つ。 -[03-03] ''エラー時にスタックトレースを表示できる'' --コールスタックがあれば、関数の奥深くでエラーが起きた際に、状況を把握しやすい。 -[04-01] ''通常実行モードと高速実行モードがあり、それは実行時にユーザが選べる'' --高速実行モードというのは、要するにプログラムを信用してこれらのエラーチェックを省略するということである。そうすればセキュリティチェックによる速度低下を心配することなしに実行できるだろう。 --むしろ高速モードがないと、「このチェックをやったら遅くなりそうだから」と心配するあまり、結局チェック機能を言語開発者が実装しないことになりかねない。 --言語利用者は、遅くなったら困るときと、遅くてもいいからちゃんとチェックしてほしいときの両方がありうるので、どちらのニーズも満たせるべきなのに。 --また、コードを信用するかどうかは、基本的には実行者の意志であるべきである。プログラム開発者は、「よしもうこれで十分」だと思っているかもしれないが、プログラムを利用するユーザはまだ心配かもしれない。 --別のケースとしては、実行環境が貧弱で、高速モードにしないと満足に実行できない状況かもしれない。 --コンパイラなどでは、リリースモードとデバッグモードがあるものがあるが、それはコンパイル速度が違うだけで、エラーチェックが厳密になったりするわけではないものもあり、そういうものはこの要件を満たしたとは言えない。 --ユーザが選べることに意義があるので、言語利用者が自分の作品を公開するときは、ソースコードを公開するか(そうすれば、ユーザは好きなモードで実行できるから)、もしくは両方のモードでのコンパイル済みバイナリをセットで提供するべきだろう。 -[04-02] ''実行を一時的に中断し、変数などを自由に確認したのちに、再開できる(そういう命令がある or 複数のブレークポイントが設定できる)'' --これは言語側の機能で実現してもいいし、デバッガなどとの連携で達成してもよい。 --(そのためには十分なデバッグ情報をデバッガ側に渡しておくこと。) -[04-03] ''キー入力などのアクションによっても、一時的に中断し、変数などを自由に確認したのちに、再開できる'' --これも言語側の機能で実現してもいいし、デバッガなどとの連携で達成してもよい。 -[04-04] ''エラー時に、変数値などを自由に確認できる'' --これも言語側の機能で実現してもいいし、デバッガなどとの連携で達成してもよい。 -[04-05] ''中断した状態をファイルに保存したり、そこから再開したりできる'' --この機能があれば、怪しくなる直前で保存して、そこから何度も効率よく試すことができる。 --これも言語側の機能で実現してもいいし、OSなどとの連携で達成してもよい。 -[05-01] ''メモリを確保した時に、前のプログラムの実行結果などを不用意に渡さない'' --ただし、後続のプログラムが、「ファイル保存しない」や「通信(送信)しない」や「変数の永続的な値を残さない」タイプであれば、この限りではない。 --safe-runみたいな命令があって、それで実行すると、メモリのfree時にクリアしてから返すようにするのがいいかもしれない。それなら簡単だ。しかしそれだけで十分なのかというと不安がある。ファイルや変数なども(安全な場所にセーブしたのちに)クリアできた方がいいのか?? --何でもかんでもクリアするだけでよければ簡単なのだが、そういう仕様だとデバッグがやりくくなるような気がして心配だ・・・。 -[06-01] ''ファジングやテストを支援する機能がある'' --この機能は結局何をしたいのかというと、「この入力値の組み合わせだとプログラムはセキュリティエラーを起こすと思うんだけど、想定の範囲内ですか?」と(半自動的に)教えてくれるものである。これは便利だ! --ここではES-Cがどういう機能を予定しているのか紹介したいと思う。そうすれば、どういうレベルのものを期待しているのかわかるだろうから。 --ES-Cでは、fuzz(0,100)みたいなコマンドがある。これはRUN命令に類するものではあるけど、内部ではファジングモードがONになって実行される。0,100というのはファジングの内部番号で、0~100の、合計101回がトライされるということになる。 --つまりRUNが101回、自動で行われると思えばよい。 --ES-Cはファジングモードで実行しているときは、入力命令や乱数生成命令に差し掛かったときに、入力値を「ファジングトラップ関数」に問い合わせるようになる。この時、ファジングの内部番号や、実行開始からの何回目の問い合わせか、何の命令による入力か、何行目の入力命令によるものか、などが通知されるので、それに応じて、適当な入力値を生成する。乱数でもいいし、ハッシュ値とかでもいい。この場面ではこのうちのどれかしか入力を想定しないでよい、みたいな仕様で作っている場合は、その制約を満たすような入力値を生成すればよい。 --デフォルトのデバッグトラップ関数はランダム関数なのだが、もっと的を絞ってファジングしたいときは、ユーザがファジングトラップ関数を書くこともできる。 --重たい関数を含む場合、ファジングモードではこの関数をスキップして、結果をいくつにするかファジングトラップ関数に問い合わせることもできる。 --ファジング実行中は、画面出力やファイル出力を完全に抑制することもできる(結局大事なのは、セキュリティエラーを起こすか、正常終了できるか、そのどちらなのかを突き止めることだけだと思うので・・・)。 --セキュリティエラーを起こした場合、ファジングの内部番号を表示して、どこでどんなエラーがあったのかを報告し、fuzz命令は終了する(それ以上の試行はしない、したかったらユーザがまたfuzz命令を次の番号から実行すればいい)。 --一つの試行に延々と時間をかけてもしょうがないので、ある程度時間がたったら(=問い合わせ回数上限に達したら)、タイムアウトとして正常終了扱いにすることもできる。
タイムスタンプを変更しない
* プログラミング言語がセキュリティに関してやるべきこと#1 -(by [[K]], 2020.02.13) -(タイトルに#1ってついていますが、#2を作るかどうかは未定です。) ** (0) はじめに -私は、プログラミング言語を作りたい。・・・私は、プログラミング言語によって、セキュリティ上の問題の解決に寄与したい。 -プログラミング言語が、セキュリティに対してできることっていったいなんだろうか。 -これに対して、まず私は、''&size(20){「この言語を使っている限り、セキュリティ問題で悩まされることはもうないよ!」};''と言えるような言語を作ることが、ゴールなのではないかと考えた。 -これは理想であって、もちろんすぐに実現できるわけではないけれど、まずはそこを目指すことにする。 -さてこれを実現するためには、大きく分けて2つの設計思想があると思われる。 -[A] 危ない結果につながるかもしれない命令を削除する。これなら何をどう間違えてもセキュリティホールを作ることはない。間違いなく安全である。 -[B] 危ない結果につながるかもしれない命令は残すが、本当に「やらかした」場合にエラーを出して止まる。もし確実に検出できるのであれば、これも十分に安全である。 -これはたとえば、ポインタという言語上の機能について考えた時に、[A]のアプローチなら、ポインタは事故の元なので、そんなものはなくしてしまう。 -いっぽうで、[B]のアプローチなら、ヌルポインタアクセスとか、malloc範囲を超えたポインタアクセスをしてしまう場合にエラーを出すとか、そういう設計になる。 -私は、この[A]のアプローチには基本的に不満である。なぜなら、そんな風に機能を削除して行けば、まあ確かに安全にはなるかもしれないが、できることも少なくなってしまうからだ。 -それでは結局、やりたいことができなくなってその言語を使う理由そのものが怪しくなる。だからセキュリティ対策のために機能を削るというアプローチは正しくないと思う。 -しかし、できることが減らなければいいので、ポインタに変わる代替機能があり、しかもそれはポインタと同じくらいに便利でありさえすれば、ポインタが無くなることは何の問題もない。 ** (1) 言語で対処可能なセキュリティ問題の一覧 -以下に、言語側で支援が可能なセキュリティ問題を示す。これに対処できているとかいないとかを星取表の形で表現できたら、すごくわかりやすいのではないかと思う。 -それぞれの項目の詳細な説明は、後述する。 -記号の意味 --◎: 実行前に確実に検出できる(いわゆるコンパイル時エラー) --○: 実行時に確実に検出できる --△: 現在は対応していないが、将来的には対応予定 --×: 対応していないし、今のところは対応予定もない --?: 検討中 ---: 前提となる機能がない |番号||ES-BASIC 2020年1月末時点|ES-C 2020年1月末時点| |01-01|配列やポインタアクセスのレンジチェック|CENTER:○|CENTER:△| |01-02|不適切なポインタの減算|CENTER:-|CENTER:△| |01-03|ポインタアクセス時のアライメントチェック|CENTER:-|CENTER:△| |01-04|free後の不適切なアクセスをエラーにできる(use-after-free 脆弱性)|CENTER:-|CENTER:△| |01-05|二重freeをエラーにできる(double-free 脆弱性)|CENTER:-|CENTER:△| |01-06|メモリリークを発見できる(もしくはGCがあってリークがおきない)|CENTER:△|CENTER:△| |01-07|整数からポインタへの変換の禁止(特別な構文でのみ許可するなど)|CENTER:-|CENTER:△| |02-01|数値演算のオーバーフローがあった時にエラーにできる(かつ必要に応じてこれを部分的に抑制もできる)|CENTER:○|CENTER:△| |02-02|変数値に制約を持たせて、違反した時にエラーにできる|CENTER:○*1|CENTER:△*1| |03-01|エラー時に、どこでエラーが起きたのか、(最低でも)行単位で指摘できる|CENTER:○|CENTER:△| |03-02|どの行を何回実行したのかを報告できる|CENTER:○|CENTER:△| |03-03|エラー時にスタックトレースを表示できる|CENTER:△|CENTER:△| |04-01|通常実行モードと高速実行モードがあり、それは実行時にユーザが選べる|CENTER:○|CENTER:△| |04-02|実行を一時的に中断し、変数などを自由に確認したのちに、再開できる(そういう命令がある or 複数のブレークポイントが設定できる)|CENTER:○|CENTER:△| |04-03|キー入力などのアクションによっても、一時的に中断し、変数などを自由に確認したのちに、再開できる|CENTER:○|CENTER:△| |04-04|エラー時に、変数値などを自由に確認できる|CENTER:○|CENTER:△| |04-05|中断した状態をファイルに保存したり、そこから再開したりできる|CENTER:×|CENTER:△| |05-01|メモリを確保した時に、前のプログラムの実行結果などを不用意に渡さない|CENTER:×|CENTER:?| |06-01|ファジングやテストを支援する機能がある|CENTER:×|CENTER:△| -(*1) 変数宣言時に制約を書くことはできないが、デバッグトラップ関数を書くことで、同様のことは実現可能。 ** (2) 詳細説明 -[01-01] ''配列やポインタアクセスのレンジチェック'' --配列の添え字の指定が間違っていたとか、ポインタでmalloc域を超えたアクセスをしているとか、そういうエラーを検出する。 int a[10], b[10];などとしている状況で、int *p = &a[9]; のとき、p[1]はb[0]と同じところを指しているかもしれないが、基本的にはそういうアクセスも許すべきではない。意図してpを使ってbにアクセスしたのか、間違ってはみ出しただけなのか、分からないからである。 --当然のことながら、「ライブラリ関数にやらせれば、レンジチェックを回避したアクセスができてしまう」みたいなのはアウトである。 -[01-02] ''不適切なポインタの減算'' --2つのポインタの差を計算すると、それが同一の配列に属していれば、その間の要素の数を返すことになっているが、そうではない場合は、たとえ同じ型のポインタであってもエラーにすべきである。 -[01-03] ''ポインタアクセス時のアライメントチェック'' --int *p;があって、pはmalloc域の中を指していたとしても、何かのバグによってpが奇数番地になっていて、意味不明な値を書いたり読んだりしてしまうかもしれない。 そういうケースはエラーにできたほうがよい。 -[01-04] ''free後の不適切なアクセスをエラーにできる'' --当然だが、freeした領域にアクセスすることは許されない。それを検出できなければいけない。 --即座に思い付く安易な方法としては free(p); したら、自動的に p = 0; にしてしまえばいいと思うかもしれない。しかしこんな方法ではほとんど解決しない。なぜなら別の変数にポインタが残っているかもしれないからだ。 --だからこのアプローチでは解決とはみなさない。 --ではどうするか。すべての変数、すべての構造体(オブジェクト)を検査して、該当域を指しているものを探しつくして0にしていくべきなのか?さすがにそれは重いだろう。だからこれも現実的ではない。 --私ならこれを以下のアルゴリズムで解決する。 ---まずポインタは従来のメモリアドレスのほかに、管理用ハンドルとリビジョン番号も持つ。この管理用ハンドルからは、アクセス可能範囲とリビジョン番号を持つ構造体を得ることができる。 ---実際のメモリアクセス際しては、まず管理用ハンドルを使ってアクセス可能範囲を取得し、適合しているのかを確認する。 ---次に、取得したリビジョンとポインタ内のリビジョンが一致していることも確認する。 ---どちらも合格できていれば、その段階でメモリアクセスをする。 ---メモリがfreeされたとき、管理用ハンドルの先の構造体のリビジョン番号を更新する。これで、古いポインタを使ってアクセスしようとしたときは、全部エラー検出できるようになる。 ---リビジョン番号さえ更新されれば、その管理用ハンドルを他の領域を指し示すために再利用しても全く問題はない。 --上記のアルゴリズムであれば、ポインタの減算は、管理用ハンドルとリビジョンが一致するかを比較してからポインタの引き算をすればいいだけになる。 -[01-05] ''二重freeをエラーにできる'' --同じ領域に対して、freeを二度やってしまうのはもちろんエラーである(freeしたあとにmallocしてたまたま同じ領域を返されて、そこをfreeしたい場合を除く)。 -[01-06] ''メモリリークを発見できる'' --freeのし忘れは、やはり重大なバグの原因になりえるので、検出手段を提供するべきである。 --循環参照があっても、見逃さないような機能を是非サポートしてほしい。 -[01-07] ''整数からポインタへの変換の禁止'' --この必要性がよくわからないかもしれない。つまりはこういうことである。 --ある関数があったとして、そこにポインタ引数がなければ、メモリの任意の場所を勝手に読み書きするようなことはない、ということである。 --ポインタ引数があったとしても、その影響範囲はそのポインタでアクセス可能な範囲に限定される、と。 --これは実にあたりまえのことだけど、もし、整数からポインタへの変換が可能だと、int *p = (int *) 0x1234567; みたいにして、 p[123] = 4; などとすることで、関数は与えられもしない領域のメモリをいつでも破壊しうるということになってしまう。 --こんな可能性まで気にしなければいけないとしたら、とてもじゃないけどデバッグできない。 -[02-01] ''数値演算のオーバーフローがあった時にエラーにできる'' --何か適当な演算をしていて、オーバーフローに気付けないと、時には深刻なことになる。 --この計算値は絶対に正になる。なぜなら正の値しか加算していないからだ。と思って負の可能性を無視してコードを書いていたのに、オーバーフローによって負になってしまったら、当然だけど予期せぬ結果を招くだろう。 --もちろんここに示したそれ以外のセキュリティ機能によって、大きな被害が出る前にエラー停止できるかもしれないが、しかしそれでも「え?なんでこの変数が負になっているの?いつのまに???」となると原因究明は手間にになり、やはりオーバーフローした時点ですぐに指摘できればそのほうがずっとありがたい。 --(いやでも、自分の預金残高が、ものすごいプラスからものすごいマイナスへ、オーバーフローによって急変してしまったら、ポインタとかがおかしくなかったとしても、重大な障害だよね?!) -[02-02] ''変数値に制約を持たせて、違反した時にエラーにできる'' --この変数は7の倍数しかとらないとか、この変数は最大でも99にしからないなど、そういう制約を明らかにしておくことで、これに違反した時にすぐに気付けるようにする仕組みがあれば、きっと役立つことがあるだろう。 --しかし一方で、そういう記述はやや煩雑になりすぎるきらいもあり、言語仕様としてきれいにまとめるのは、結構な難易度がある(と[[K]]は思っている)。 -[03-01] ''エラー時に、どこでエラーが起きたのか、(最低でも)行単位で指摘できる'' --単にエラーがあったと分かるだけでは、ほとんどデバッグの役には立たない。一番大事なのはどこでエラーが起きたのか、である。これさえわかればエラー種別が不十分であっても文脈から容易に想像できる(できないこともあるが)。 --もちろんエラー種別も報告してもらえるのならそれに越したことはない。 -[03-02] ''どの行を何回実行したのかを報告できる'' --究極的には、どの行をどの順番で実行したかのログがあれば最強ではあるが、それは出力するのも集計するのもかなりのコストになりがちである。それなら、行ごとの実行回数が分かるだけでもかなり便利である。 --これがあれば、今回の実行でどのくらいの部分が実行テストされたのか確認できる。コンパイル時にすべてのエラーチェックが完全にできるのであれば、カバレッジなどどうでもよいのだが、実行時にチェックする言語の場合は、通っていないところについては十分なチェックがされていないので、それをプログラマに確認させるためにも、この機能は役に立つ。 -[03-03] ''エラー時にスタックトレースを表示できる'' --コールスタックがあれば、関数の奥深くでエラーが起きた際に、状況を把握しやすい。 -[04-01] ''通常実行モードと高速実行モードがあり、それは実行時にユーザが選べる'' --高速実行モードというのは、要するにプログラムを信用してこれらのエラーチェックを省略するということである。そうすればセキュリティチェックによる速度低下を心配することなしに実行できるだろう。 --むしろ高速モードがないと、「このチェックをやったら遅くなりそうだから」と心配するあまり、結局チェック機能を言語開発者が実装しないことになりかねない。 --言語利用者は、遅くなったら困るときと、遅くてもいいからちゃんとチェックしてほしいときの両方がありうるので、どちらのニーズも満たせるべきなのに。 --また、コードを信用するかどうかは、基本的には実行者の意志であるべきである。プログラム開発者は、「よしもうこれで十分」だと思っているかもしれないが、プログラムを利用するユーザはまだ心配かもしれない。 --別のケースとしては、実行環境が貧弱で、高速モードにしないと満足に実行できない状況かもしれない。 --コンパイラなどでは、リリースモードとデバッグモードがあるものがあるが、それはコンパイル速度が違うだけで、エラーチェックが厳密になったりするわけではないものもあり、そういうものはこの要件を満たしたとは言えない。 --ユーザが選べることに意義があるので、言語利用者が自分の作品を公開するときは、ソースコードを公開するか(そうすれば、ユーザは好きなモードで実行できるから)、もしくは両方のモードでのコンパイル済みバイナリをセットで提供するべきだろう。 -[04-02] ''実行を一時的に中断し、変数などを自由に確認したのちに、再開できる(そういう命令がある or 複数のブレークポイントが設定できる)'' --これは言語側の機能で実現してもいいし、デバッガなどとの連携で達成してもよい。 --(そのためには十分なデバッグ情報をデバッガ側に渡しておくこと。) -[04-03] ''キー入力などのアクションによっても、一時的に中断し、変数などを自由に確認したのちに、再開できる'' --これも言語側の機能で実現してもいいし、デバッガなどとの連携で達成してもよい。 -[04-04] ''エラー時に、変数値などを自由に確認できる'' --これも言語側の機能で実現してもいいし、デバッガなどとの連携で達成してもよい。 -[04-05] ''中断した状態をファイルに保存したり、そこから再開したりできる'' --この機能があれば、怪しくなる直前で保存して、そこから何度も効率よく試すことができる。 --これも言語側の機能で実現してもいいし、OSなどとの連携で達成してもよい。 -[05-01] ''メモリを確保した時に、前のプログラムの実行結果などを不用意に渡さない'' --ただし、後続のプログラムが、「ファイル保存しない」や「通信(送信)しない」や「変数の永続的な値を残さない」タイプであれば、この限りではない。 --safe-runみたいな命令があって、それで実行すると、メモリのfree時にクリアしてから返すようにするのがいいかもしれない。それなら簡単だ。しかしそれだけで十分なのかというと不安がある。ファイルや変数なども(安全な場所にセーブしたのちに)クリアできた方がいいのか?? --何でもかんでもクリアするだけでよければ簡単なのだが、そういう仕様だとデバッグがやりくくなるような気がして心配だ・・・。 -[06-01] ''ファジングやテストを支援する機能がある'' --この機能は結局何をしたいのかというと、「この入力値の組み合わせだとプログラムはセキュリティエラーを起こすと思うんだけど、想定の範囲内ですか?」と(半自動的に)教えてくれるものである。これは便利だ! --ここではES-Cがどういう機能を予定しているのか紹介したいと思う。そうすれば、どういうレベルのものを期待しているのかわかるだろうから。 --ES-Cでは、fuzz(0,100)みたいなコマンドがある。これはRUN命令に類するものではあるけど、内部ではファジングモードがONになって実行される。0,100というのはファジングの内部番号で、0~100の、合計101回がトライされるということになる。 --つまりRUNが101回、自動で行われると思えばよい。 --ES-Cはファジングモードで実行しているときは、入力命令や乱数生成命令に差し掛かったときに、入力値を「ファジングトラップ関数」に問い合わせるようになる。この時、ファジングの内部番号や、実行開始からの何回目の問い合わせか、何の命令による入力か、何行目の入力命令によるものか、などが通知されるので、それに応じて、適当な入力値を生成する。乱数でもいいし、ハッシュ値とかでもいい。この場面ではこのうちのどれかしか入力を想定しないでよい、みたいな仕様で作っている場合は、その制約を満たすような入力値を生成すればよい。 --デフォルトのデバッグトラップ関数はランダム関数なのだが、もっと的を絞ってファジングしたいときは、ユーザがファジングトラップ関数を書くこともできる。 --重たい関数を含む場合、ファジングモードではこの関数をスキップして、結果をいくつにするかファジングトラップ関数に問い合わせることもできる。 --ファジング実行中は、画面出力やファイル出力を完全に抑制することもできる(結局大事なのは、セキュリティエラーを起こすか、正常終了できるか、そのどちらなのかを突き止めることだけだと思うので・・・)。 --セキュリティエラーを起こした場合、ファジングの内部番号を表示して、どこでどんなエラーがあったのかを報告し、fuzz命令は終了する(それ以上の試行はしない、したかったらユーザがまたfuzz命令を次の番号から実行すればいい)。 --一つの試行に延々と時間をかけてもしょうがないので、ある程度時間がたったら(=問い合わせ回数上限に達したら)、タイムアウトとして正常終了扱いにすることもできる。
テキスト整形のルールを表示する