Hatena::Groupmoz-addon

hogezilla RSSフィード

当ページに書かれているコードは、修正BSDライセンスのもと、再頒布して頂いて構いません。

 | 

2012-01-06

Components.stack と Greasemonkey の GM_apiLeakCheck

| 23:15 | はてなブックマーク - Components.stack と Greasemonkey の GM_apiLeakCheck - hogezilla

Cu.import("resource://gre/modules/Services.jsm");

function hoge() {
  //debug
  Services.console.logStringMessage(JSON.stringify(Components.stack, null, "  "));
  // ...
}

などとするとスタックが再帰的に文字列か出来てデバッグに便利ーなわけである。

んで、これは本題ではない。

GM_apiLeakCheck

GreasemonkeyでGM_* API がコンテンツ側から呼び出されたものではないかどうかをチェックするGM_apiLeakCheckなる関数があり、これのテストをするために上記コードを編み出したわけだが...。

function GM_apiLeakCheck(apiName) {
  var stack = Components.stack;

  do {
    // Valid locations for GM API calls are:
    //  * Greasemonkey modules.
    //  * All of chrome.  (In the script update case, chrome will list values.)
    //  * Greasemonkey extension by path. (FF 3 does this instead of the above.)
    // Anything else on the stack and we will reject the API, to make sure that
    // the content window (whose path would be e.g. http://...) has no access.
    if (2 == stack.language
        && stack.filename.substr(0, 24) !== 'resource://greasemonkey/'
        && stack.filename.substr(0, 9) !== 'chrome://'
        && stack.filename.substr(0, gExtensionPath.length) !== gExtensionPath
        ) {
      GM_util.logError(new Error("Greasemonkey access violation: " +
          "unsafeWindow cannot call " + apiName + "."));
      return false;
    }

    stack = stack.caller;
  } while (stack);

  return true;
}

GM_apiLeakCheckが何をやっているかというと、callスタックを見て、UserScriptやGreasemonkey以外から呼ばれていないかチェックしている。

が、あることをすると、このチェックがうまく機能しなくなることを発見した。

あること

UserScript側
  • addEventListenerでイベント取得
    • GM_getValueなどのAPIを呼ぶ
コンテンツ側
  • function sendEvent(){}を設定
    • これはUserScript側で見ているイベントをdocument.createEvent(....);して、dispatchEventする関数
  • iframeを作っておく
    • iframe.src="javascript:window.top.sendEvent()"
    • としてsendEventを呼ぶ

普通にsendEventをすると、コールスタックにfilenameなどが載り、GM_apiLeakCheckで引っかかる。

{
  "language": 2,
  "languageName": "JavaScript",
  "filename": "file:///C:/Documents%20and%20Settings/teramako/Application%20Data/Mozilla/Firefox/Profiles/pdqh6298.gmTest/extensions/%7Be4a8a97b-f2ed-450b-b12d-ee082ba24781%7D/components/greasemonkey.js",
  "name": "GM_apiLeakCheck",
  "lineNumber": 53,
  "sourceLine": null,
  "caller": {
    "language": 2,
    "languageName": "JavaScript",
    "filename": "chrome://greasemonkey/content/miscapis.js",
    "name": null,
    "lineNumber": 20,
    "sourceLine": null,
    "caller": {
      "language": 2,
      "languageName": "JavaScript",
      "filename": "resource://greasemonkey/util/hitch.js",
      "name": null,
      "lineNumber": 27,
      "sourceLine": null,
      "caller": {
        "language": 1,
        "languageName": "C++",
        "filename": null,
        "name": null,
        "lineNumber": 0,
        "sourceLine": null,
        "caller": {
          "language": 2,
          "languageName": "JavaScript",
          "filename": "resource://greasemonkey/runScript.js",
          "name": null,
          "lineNumber": 45,
          "sourceLine": null,
          "caller": {
            "language": 1,
            "languageName": "C++",
            "filename": null,
            "name": null,
            "lineNumber": 0,
            "sourceLine": null,
            "caller": {
              "language": 2,
              "languageName": "JavaScript",
              "filename": "http://localhost/gmSecurity.html",
              "name": "sendEvent",
              "lineNumber": 1,
              "sourceLine": null,
              "caller": {
                "language": 1,
                "languageName": "C++",
                "filename": null,
                "name": null,
                "lineNumber": 0,
                "sourceLine": null,
                "caller": null
              }
            }
          }
        }
      }
    }
  }
}

スタック中に"filename": "http://localhost/gmSecurity.html"が出てきて、ここで引っかかるわけだ。

が、ifram.src 経由でsendEventを実行すると、載らない。

{
  "language": 2,
  "languageName": "JavaScript",
  "filename": "file:///C:/Documents%20and%20Settings/teramako/Application%20Data/Mozilla/Firefox/Profiles/pdqh6298.gmTest/extensions/%7Be4a8a97b-f2ed-450b-b12d-ee082ba24781%7D/components/greasemonkey.js",
  "name": "GM_apiLeakCheck",
  "lineNumber": 53,
  "sourceLine": null,
  "caller": {
    "language": 2,
    "languageName": "JavaScript",
    "filename": "chrome://greasemonkey/content/miscapis.js",
    "name": null,
    "lineNumber": 20,
    "sourceLine": null,
    "caller": {
      "language": 2,
      "languageName": "JavaScript",
      "filename": "resource://greasemonkey/util/hitch.js",
      "name": null,
      "lineNumber": 27,
      "sourceLine": null,
      "caller": {
        "language": 1,
        "languageName": "C++",
        "filename": null,
        "name": null,
        "lineNumber": 0,
        "sourceLine": null,
        "caller": {
          "language": 2,
          "languageName": "JavaScript",
          "filename": "resource://greasemonkey/runScript.js",
          "name": null,
          "lineNumber": 32,
          "sourceLine": null,
          "caller": {
            "language": 1,
            "languageName": "C++",
            "filename": null,
            "name": null,
            "lineNumber": 0,
            "sourceLine": null,
            "caller": null
          }
        }
      }
    }
  }
}

また、このスタック状態は普通に想定されるイベントをキャッチした時と同一のスタックとなり、区別が付かない。

本来ならば、以下の様なものがコールスタックの最後にあるべきだろう。

            "caller": {
              "language": 2,
              "languageName": "JavaScript",
              "filename": "javascript:window.top.sendEvent()",
              "name": null,
              "lineNumber": 1,
              "sourceLine": null,
              "caller": null
            }
  • iframe.srcに設定したjavascript URL経由
  • eventの発行

をするとスタックから漏れるっぽい感じ

問題点

コンテンツ側で任意のタイミングでイベント発行することで、ユーザの動作と関係なくUserScriptを操作することが可能になる。

何が行われるかはUserScriptに依存するが、もしかするとヤバイことができるかもしれない。

「イベント発行 -> UserScriptがキャッチ -> GM_xmlHttpRequest でコンテンツ側にあるURLを使って何か送信」という流れがあった場合、コンテンツ側で任意のURLを送信できる可能性が出てくる。

もちろん、dispatchされたEventオブジェクトのisTrustedプロパティは false となるためUserScript側で判別は可能ではあるが、これをUserScript作者に強いるのはちょっとアレだ。(今のところコレしか防ぐ手立てが見つからないのが現状)

で、一応Greasemonkey側には報告してみている

が、しかし、スタックに現れないのはGreasemonkeyの問題というより、FirefoxのエンジンであるGecko側の問題の様に思える。

Bugzilla報告すべきか...すべきなのかもしれないが、これを英語で説明できる自身が皆無...。

追記(2012-01-08)

テストコードとかもろもろを https://gist.github.com/1565506 に入れた。

あと、バグジラった。=> Bug 716262 – Components.stack looses stacks of function has dipatchEvent via iframe.src

どうかな?

トラックバック - http://moz-addon.g.hatena.ne.jp/teramako/20120106
 |