参考文献
はじめに
最近Chromeの脆弱性が話題に上がりますが、この諸悪の根源がC++のメモリ管理ということを知り、あ、これはRustがマジに来そうだなー、という勝手な期待的な想像でメモリセキュアなRustに興味を持ちました。
Rustを学ぶにあたり所有権の概念を理解するためには、メモリ管理の知見がイマイチだったので調べてみることにしました。
C系言語はメモリの問題で悩まされて来ました(今後もその呪縛から逃れることはできないと断言できる)
Rustは所有権により変数のリソースの束縛←→解放を自覚と責任をもってコーディングする必要があります。
それはつまり、与えられたリソースに対する束縛が1つだけであることを保証するということ。
これがC系言語にとって、どれだけ羨ましいことか、これと同じことができるようになれば、どれだけの苦労から開放されるか。
我が身をRustに束縛されたい!
どうにかして手に入れたい、そのためには手段を選ばないとやぶさかでないでしょうね。
セキュリティーホールとは
セキュリティホール は、脆弱性についての俗表現である。 脆弱性は、コンピュータソフトウェアの欠陥(バグ、不具合、あるいはシステム上の盲点)の一つで、本来操作できないはずの操作(権限のないユーザが権限を超えた操作を実行するなど)ができてしまったり、見えるべきでない情報が第三者に見えてしまうような不具合をいう。ハードウェアおよびそれを含めたシステム全般の欠陥を指すこともある。
このような欠陥は古くから存在したが、特に問題視されるようになったのはインターネットの発展に伴って脆弱性がネットワークを介して容易に攻撃されうる状態になっているからである。
原因としては、プログラムのコーディング間違いや、システムの設定間違い、システム設計上の考慮不足、故意に作られ秘密にされた機能の漏洩などがある。
なぜセキュリティーホールができるのか
- 開発者が意図的に開発過程でつくることがある (故意)
- 開発者のスキルレベルの低さによって生まれてしまう (過失)
- 第3者によって脆弱性をハックされることで生まれる (悪意)
セキュリティーホールが生まれる場所 & 対策
復習
スタック
コンパイル時に予めメモリ領域を確保する、固定サイズで変更不可能
関数の引数やローカル変数が入る
ヒープ
コンパイル後に必要に応じてメモリを確保していく、柔軟性が高い
プログラムのデータが入る
バッファ系
-
Auto変数が多すぎてスタック領域を越えてメモリが壊れる(穴になる)
- 対策: スタック領域を意識した設計をする
-
ヒープ領域の任意のポインタを取得してポインタを介して書き換えを行う
- 対策: 安全な言語を使う、ライブラリを使わない、入力データの検証、メモリ管理の徹底
スクリプト系
- SQLのリスクエストに不正な文字列を組み込んでDBの内容を書き換える
- 入力フォームなどに不正な文字列を入力、OSに対する命令文を実行させ、不正な操作を行う
- 対策: WAF(ウェブアプリケーションファイアウォール)で対策する
情報漏洩
- ソーシャル・エンジニアリング
- 対策: 人格を磨く
セキュリティーホールまとめ
- 安全な言語を使う
- メモリ管理が得意な言語 Rust を使う
- 外部ネットワークに接続しない
- ネットに繋がなきゃ安心だよね(´ε` )
- 経験豊富な人と切磋琢磨する
- いくら勉強ができても経験には敵うわけがない
束縛 (binding)
Rustを学んでいると、いたるところで束縛がでてくる
束縛とは、変数名や関数名などを実体と結びつけること
束縛には、静的束縛と動的束縛とがある
静的束縛
- コンパイル時に分かっている→スタックに保存される
- プリミティブ値
動的束縛
- 実行してみないと分かっていない→ヒープに保存される
- プリミティブ値以外
変数束縛
一般的に、変数束縛とは名前を値へ束縛するという使われ方をします。
しかし、Rustの束縛は他の言語と微妙に違います。
他の言語: 名前 → 値
Rust : 値 → 名前
Rustでは、名前だけ存在していて、値は存在しない状態を許しません。
変数の初期化はできない
上でも、説明しましたが次のように、名前のみ
で初期化
するなんてことはできません
。
fn main() {
let x: i32; // ここでエラー
x = 5;
println!("{}", x);
}
必ず、値とセットで初期化します。
fn main() {
let x: i32 = 5; // OK
println!("{}", x);
}
パターン
Rustはなぜ名前だけ定義することができないのか?
その理由がパターンと呼ばれる概念です。
一般的なプログラミング言語では、左に変数名、右に値
だと思います。
Rustでは、右辺と左辺が評価
されることで、いわゆる変数が誕生するのです。
Rustの所有権について
RustはC系プログラマの悲願を達成するために生まれた言語であり、所有権や借用やライフサイクルはそのために考えられた仕様なのです。
つまり、Rust習得の鬼門であり、その暁には永遠の喜びを与えくれ、ネクストステージに導いてくれます。
所有権
プリミティブ(オブジェクトでなく、メソッドを持たないデータ)以外のものをコピーしようとすると所有権が移動して、コピー元のデータを参照しようとするとエラーとなる。
なぜかというと、所有権が移るということは、リソースが移る
ということだから
<エラー> 所有権を移動したとき
fn main() {
let s1 = String::from("hello world!");
let s2 = s1; // 所有権の移動(ownership moved)
println!("{}",s1);
}
// 実行結果
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:4:19
|
2 | let s1 = String::from("hello world!");
| -- move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 | println!("{}",s1);
| ^^ value borrowed here after move
<エラー対処法> 所有権を借用する
参照することで所有権は移動しない
のでコピー元を出力できる
(リソースはそのままなので)
これをRustでは所有権の借用(borrowing)と呼ぶ
借用した束縛はそれがスコープから外れるときにリソースを割当解除しません。スコープ外で元の束縛を再び使うことができることを意味します。
参照は束縛とちょうど同じようにイミュータブルです。
fn main() {
let s1 = String::from("hello world!");
let s2 = &s1; // 借用(borrowing)
println!("{}",s1);
}
mut (mutable) 可変できるデータ
Rustのデフォルトはimutable(可変できないデータ)
他のプログラミング言語とは少し異なり、デフォルトでは(mutを省略すると
)可変できないデータとなる。
let x = 5;
x = 6; // エラー
let mut x = 5
x = 6; // OK
データのやりとり
mut ←→ mut OK
mut ←→ imut NO
直接、mutなオブジェクトにimutなデータを渡せない場合は、一旦、imutなデータをmutに所有権を移動してから、mutなオブジェクトに渡す
fn func(s: &mut String) -> &mut String {
s
}
fn main() {
let s1 = String::from("hello world!");
let s2 = func(&mut s1);
println!("{}", s2);
}
// 実行結果
error[E0596]: cannot borrow `s1` as mutable, as it is not declared as mutable
--> src/main.rs:9:19
|
6 | let s1 = String::from("hello world!");
| -- help: consider changing this to be mutable: `mut s1`
...
9 | let s2 = func(&mut s1);
| ^^^^^^^ cannot borrow as mutable
fn func(s: &mut String) -> &mut String {
s
}
fn main() {
let s1 = String::from("hello world!");
let mut tmp = s1;
let s2 = func(&mut tmp);
println!("{}", s2);
}
// 実行結果
hello world!
&mut参照と参照の違い
一般的な参照
「データ競合」は2つ以上のポインタがメモリの同じ場所に同時にアクセスするとき、少なくともそれらの1つが書込みを行っていて、作業が同期されていないところで「データ競合」は起きます。
Rustの参照
書込みを行わないのであれば、参照は好きな数だけ使うことができます。 &mut は同時に1つしか持つことができないので、データ競合は起き得ません。 これがRustがデータ競合をコンパイル時に回避する方法です。もしルールを破れば、そのときはエラーが出るでしょう。