acl4の開発ログ #02

  • (by K, 2025.12.11)

ここまでのもくじ

2025.12.11(木) #1

  • 基本方針(自分が思い出すために書く):
    • 簡単にできることはやる、簡単にできないことはやらない。
    • 明らかにおかしいことはエラーだと指摘するけど、おかしいとは限らない場合は指摘しない(=警告はない)。
    • ADbgLv=0の場合は、すべてのエラーチェックのためのオーバーヘッドは消えなければいけない。
    • ソースコードライブラリとして提供するので、コンパイラや環境を選ばない。
  • ここまでの(上記以外の)特徴:
    • [2025.12.10(水) #0] メモリリークは見つけられる。
    • [2025.12.10(水) #3] mallocしてないメモリに対するfree・二重freeは検出できる。
    • [2025.12.11(木) #0] デストラクタを呼び忘れているオブジェクトは検出できる。
    • acl4についてわいわいがやがやする場所は、osdev-jpのDiscordの「k-tanの個人チャンネル / acl4 : C言語でもここまでできる」です。

2025.12.11(木) #2

  • 次にやるのは use-after-free 対策案を試すこと。

2025.12.11(木) #3

  • use-after-free 対策をやってみます。
  • acl4はポインタを使ったメモリアクセスのすべてを見張ることはしません。その代わり、今でもこのポインタでアクセスできますよね?に対して、「はい大丈夫です」と回答することはできます。
  • これをまめにチェックするかどうかはユーザ任せです。まめにチェックしたととしても、ADbgLv=0にすれば全部消えます。
    // t20251211a.c: use-after-freeのテスト#0.
    #include "acl4.c"
    
    int main(void)
    {
        acl4_ini();
        char *s = Amalc(16); // malloc.
        AUaf u[1]; AUaf_ini(u, s _1); // これでuはsを監視するためのオブジェクトになる.
        AUaf_chk(u _); strcpy(s, "hello"); // ここではエラーならずに通してもらえる(あたりまえだけど一応テスト).
        Amfre(s, 16); // ここでメモリを開放してしまう.
        AUaf_chk(u _); puts(s); // これは許されないはず.
        return 0;
    }
  • これを実行すると、こうなります。うまくいっています。
    C:\rsd0027\progs\acl4>t20251211a
    t20251211a.c(11): AUaf_chk: invalid pointer
    
    C:\rsd0027\progs\acl4>

  • もっといじわるなテストをします。
    // t20251211b.c: use-after-freeのテスト#1.
    #include "acl4.c"
    
    int main(void)
    {
        acl4_ini();
        char *s = Amalc(16);
        AUaf u[1]; AUaf_ini(u, s _1); // これでuはsを監視するためのオブジェクトになる.
        AUaf_chk(u _); strcpy(s, "hello");
        Amfre(s, 16);
        char *a = Amalc(16); // a~cのどれかでsだった場所にもう一度割り当てさせたい.
        char *b = Amalc(16);
        char *c = Amalc(16);
        printf("s=%08x a=%08x b=%08x c=%08x\n", (uintptr_t) s, (uintptr_t) a, (uintptr_t) b, (uintptr_t) c);
        AUaf_chk(u _); puts(s); // これは許されないはず.
        return 0;
    }
  • これでもしsだった場所にもう一度メモリが割り当てられれば、メモリのアクセス権的にはsはアクセス可能なはずです。しかし意味的にはsを開放しているので、アクセスはエラーになるべきです。
  • どうなるのかというと、ちゃんとうまくいってエラーになります。
    C:\rsd0027\progs\acl4>t20251211b
    s=00f99c08 a=00f99c08 b=00f9db50 c=00f9dba8
    t20251211b.c(15): AUaf_chk: invalid pointer
    
    C:\rsd0027\progs\acl4>

2025.12.11(木) #4

  • エラーをテストする方法については十分に満足しているのですが、行番号を受け取るための処理には満足できません。もう少し考えます。

2025.12.12(金) #0

  • 現在最も有力な案:
    int func(A1_ int x, char *y, double z)
    {
        Aenter;
        ...
        Aleave;
        return ...;
    }
  • このように関数を宣言するときは引数パラメータの一番前に A1_ を書き、関数に入ったら Aenetr; を実行し、関数を抜ける直前に Aleave; を実行します。ライブラリはデバッグレベルに応じて、 A1_ の内容を調整して #define します。
  • この func を呼び出す際には、
        i = func(A_ 123, "hello", 4.56);
  • などとします。
  • また main 関数は特別で、 AenterMain; や AleaveMain; と書いてもらうことにします。
  • [Q] なぜライブラリ由来の引数パラメータが一番前なのか?一番後ろのほうがいいのでは?
  • [A] 私も最初はまさにそのように考えていましたが、一番後ろだと可変長引数の時にうまくいきません。統一的に扱うには一番前がいいと思いました。

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2025-12-12 (金) 09:16:16 (186d)