Firefoxのprincipalとjsセキュリティ
Security check basics - MDC
やっと理解できたかも。
Determining the subject principal
セキュリティチェックを要求されると、セキュリティマネージャは current subject principal (セキュリティチェックを受けるactionをしようとしているactorのprincipal)を決定します。
Gecko 1.9のセキュリティマネージャはXPConnectが保持しているスタックから"current JSContext"を得ます。
このJSContextはそのコンテキストで今実行されているスクリプトのコールスタックを持っています。
- スタックにjavascriptがなければ、subject principalは"system"です(全能のprincipalです)。
- スタックにjavascriptがあるときは、スタックを上から下にscripted functionにあたるまで走査します。scripted functionには必ず関連付けられたprincipalがあります。
(scripted function を生成するとき、誰がそれを生成しているかを告げる必要があります)。それがsubject principal になります。
Determining the object principal
GeckoはあるJavascriptObjectのobject principalを決めるとき、オブジェクト自身がprincipalを持っているところまでしっているところまでオブジェクトの__parent__チェインをたどります。(ふつうはWindowかDocumentです)。
これが object principal として使われます。
拡張機能の関数が持つprinciapl
拡張のコンテキスト、といっているものは、chromeのURIを持っているwindowからロードされたスクリプトで生成された関数、オブジェクトのことだ。
上述の Determining the subject principal に書かれているとおり、すべてのscripted functionはprincipalを持つ。そしてscripted functionのprincipalは、原則的にはその関数を生成したコンテキストのものが引き継がれる(ここは予想)。
拡張機能のscripted functionはchromeのURIからロードされるので、拡張機能内で定義した関数はsystemのprincipalを持つことになる。
GMの中で作られた関数が持つprincipal
関数のprincipalはその関数を生成したscripted functionのprincipalになりますが、例外的にComponents.utils.Sandboxを使ってこの原則を変更することができます。
Components.utils.Sandboxを使うと、任意のオブジェクトのprincipalをsandboxのprincipalとして設定することができます。
if(!sandbox) { sandbox = new Components.utils.Sandbox(win); // Use DOM Window sandbox.__win__ = win; context.sandboxes.push(sandbox); } Components.utils.evalInSandbox(scriptToEval, sandbox);
こうしてsandboxが作られます。winのprincipalのcodebaseはそのGreasemonkeyが実行されるページ(http, https, file)のURIになります。
結果としてGreasemonkeyの中で作られる関数のprincipalはすべて開いたページのprincipalになります。だからsystem principalを必要とするComponents.classesにはアクセスできません。
続、Greasemonekyスクリプトのサンドボックスに値を追加する - 実用にある例で考えます。
便宜的に元のものからテストのための関数をはずして書いています。
// system principal var GreasemonkeyService = Components.classes["@greasemonkey.mozdev.org/greasemonkey-service;1"].getService().wrappedJSObject; addBefore(GreasemonkeyService, 'evalInSandbox', function(code, codebase, sandbox){ sandbox.GM_test = function(func){ func(); }; }); function addBefore(target, name, before) { var original = target[name]; target[name] = function() { before.apply(target, arguments); return original.apply(target, arguments); } } // window principal GM_test( function malfunc(){ args.callee.caller; } );
sandbox.GM_testはchromeのuriからロードされるgreasemonkey.jsに記述されているのでsystem principalを持っています。
一方Greasemonkeyスクリプトの中で定義されているmalfuncはevalInSandboxのsandboxでprincipalをページのURIのものに変更されたコンテキストから作られるので必然的にページのprincipalを持つことになります。
GM_Testで登録された関数malfuncをsystem principalを持つ関数(上のid:brazilさんの例とはcallerが異なるかもしれない。今回はGMのevalInSandboxからよびだした)から呼び出すと、args.callee.callerにアクセスしたときに(コンテキストをまたいでevalでcallerにアクセスできないのはなぜか参照)
checkObjectAccessが呼ばれ(これはどんなprincipalを持つscripted functionを実行していても行われる)、そこで subject principal とobject principal が比較される。
args.callee.callerにアクセスしたときにセキュリティチェックが行われる。
subject principal
subjectはセキュリティチェックが必要なオブジェクトにアクセスしようとしている関数だと考えるとわかりやすい(でも不正確だと思う)。
Determining the subject principal にしたがってsubject principalを決定する。
コールスタックを上から下にたどってはじめに出てきたscripted functionのprincipalがprincipalとするとある。言い換えると最も内側の関数から外側にたどっていってはじめのscripted functionのprincipalということになる。
args.callee.callerにアクセスが発生した時点でのコールスタックは、上からmalfunc,GM_test,... となっているのでsubject principalはmalfuncのprincipalになる。malfuncのprincipalのcodebaseはページのuriになる。
object principal
同様にDetermining the object principalにしたがってobject principalを決める。
objectはアクセスされるオブジェクトのことである。args.callee.callerはGM_testになるのでここでのobjectは関数GM_testになる。
__parent__がよくわかってないのでよくわかんないんだけど、直感的にはGM_testの__parent__をたどれば拡張機能をロードしたchromeWindowにたどり着く。このwindowはchromeのuriを持つ。
ゆえにobject principalはsystemになる。
subject principalとobject principalが一致しないため、セキュリティチェックにひっかかることになる。
まとめ
principalのチェックは強力で、これだけで危険(system principalの必要とするようなもの)なことを悪意あるコードから実行することはできなさそう。
実行コンテキストで権限が決まるのではなく、その関数がどこから生成されたものかで決まる。そのため、ふつうのウインドウで悪意ある関数を作っても、その関数には特権(system principal)がつかないため(ふつうのウインドウのprincipalがつく)、
特権のコンテキスト(正確にはコールスタックの一番下の関数がsystem principalを持つ場合)がその関数を呼んだとしても、その関数につけられているふつうのprincipalが変わるわけではないので特権は与えられない。
ただ、実装に穴がある可能性(セキュリティチェックをするべきところでしていなかったとか、そういうの)がないわけではない。
あと特権はなくてもsetter/getterに無限ループを入れたりすることでDoSは可能なのでやっぱり注意は必要。
やっとわかったという程度で理解してるとはいえないレベルなので、ほかの事象についてもこれで考えてみる。