1. ホーム
  2. c

[解決済み] プロジェクトにアセンブリファイルが含まれている場合、mmapから予期しない実行許可が出ることがあります。

2022-12-22 04:37:06

質問

この件で壁に頭を打ち付けています。

私のプロジェクトでは、メモリ割り当てを mmap でメモリを確保する場合、マッピング ( /proc/self/maps ) は、それが読み取り可能で実行可能な領域であることを示しています。 とはいえ 読み出し可能なメモリのみを要求しました。

strace (これは良さそうでした) と他のデバッグを調べた後、この奇妙な問題を回避できそうな唯一のものを特定できました。プロジェクトからアセンブリ ファイルを削除して、純粋な C のみを残すことです (何だと!?)。

私は Ubunbtu 19.04 とデフォルトの gcc で作業しています。

ASM ファイル (これは空です) を使ってターゲットの実行ファイルをコンパイルした場合 mmap は読み取り可能で実行可能な領域を返し、もしそうせずにビルドした場合は正しく動作します。の出力を見てください。 /proc/self/maps の出力を見てください。

example.c

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

int main()
{
    void* p;
    p = mmap(NULL, 8192,PROT_READ,MAP_ANONYMOUS|MAP_PRIVATE,-1,0);

    {
        FILE *f;
        char line[512], s_search[17];
        snprintf(s_search,16,"%lx",(long)p);
        f = fopen("/proc/self/maps","r");
        while (fgets(line,512,f))
        {
            if (strstr(line,s_search)) fputs(line,stderr);
        }

        fclose(f);
    }

    return 0;
}

: 空のファイルです!

出力

ASM同梱版の場合

VirtualBox:~/mechanics/build$ gcc example.c example.s -o example && ./example
7f78d6e08000-7f78d6e0a000 r-xp 00000000 00:00 0 

ASM同梱版なし

VirtualBox:~/mechanics/build$ gcc example.c -o example && ./example
7f1569296000-7f1569298000 r--p 00000000 00:00 0 

どのように解決するのですか?

Linuxでは 実行領域 と呼ばれる READ_IMPLIES_EXEC で割り当てられたすべてのページが PROT_READ で割り当てられたすべてのページにも PROT_EXEC . 古い Linux カーネル はこれを使用していました。 と同じものを使っていた実行ファイルに対して gcc -z execstack . このプログラムは、それが自分自身で有効になっているかどうかを表示します。

#include <stdio.h>
#include <sys/personality.h>

int main(void) {
    printf("Read-implies-exec is %s\n", personality(0xffffffff) & READ_IMPLIES_EXEC ? "true" : "false");
    return 0;
}

もし、これを空の .s ファイルと共にコンパイルすると、有効になっていることがわかりますが、ファイルがなければ無効になります。初期値として、この の初期値は、バイナリに含まれる ELF メタ情報に由来します。 . 行う readelf -Wl example . この行は、空白を除いてコンパイルしたときに表示されます。 .s ファイルなしでコンパイルすると、この行が表示されます。

  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10

でも、これでコンパイルすると

  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10

RWE ではなく、単に RW . この理由は、リンカは、明示的にそうでないと言われない限り、あなたのアセンブリファイルがread-implies-execを必要とすると仮定し、プログラムのある部分がread-implies-execを必要とすると、プログラム全体に対してそれが有効になってしまうからです。GCCがコンパイルするアセンブリファイルは、この行で、これが不要であることを伝えます(これは -S ):

    .section        .note.GNU-stack,"",@progbits

デフォルトのセクションパーミッションには、e x ec が含まれます。 ELF の部分を参照してください。 .section ドキュメント は、"flags" と @attributes の意味についてです。

(のような別のセクションに切り替えることを忘れないでください。 .text または .data その後に .section ディレクティブの後、もしあなたの .s に依存していたのであれば .text に依存していたからです)。

その行を example.s (そして他のすべての .s ファイル)。 その存在は .note.GNU-stack セクションの存在は、このオブジェクトファイルが実行可能なスタックに依存しないことをリンカーに伝える役割を果たします。 RW の代わりに RWE の上に GNU_STACK を追加すると、プログラムは期待通りに動作します。

同様に はNASMの , a section ディレクティブで、 実行不可能なスタックを指定します。


5.4 から 5.8 までの最近の Linux カーネルでは、動作が変更されています。 を変更しました。x86-64 では、何も READ_IMPLIES_EXEC をオンにするものはもうありません。 せいぜい(RWEで GNU_STACK によって追加された ld によって追加された場合)、すべての読み取り可能なページではなく、スタックそのものが実行可能であることがわかります。 ( この回答 は5.8での最後の変更をカバーしていますが、その質問が .data x86-64 Linux 5.4上で)。

exec-all ( READ_IMPLIES_EXEC ) が発生するのは、リンカがレガシー 32 ビット実行ファイルに GNU_STACK のようなヘッダーエントリーが全くない。 しかし、ここに示すように、現代の ld が入力されたときでさえも、常に何らかの設定でそれを追加します。 .o ファイルにノートがない場合でもです。

それでもこの .note セクションを使うべきです。 しかし、もしあなたが自己修正コードを .data の中で自己修正するコードをテストしたり、古いチュートリアルに沿って シェルコードのテスト のような古いチュートリアルに従うこともできますが、これは最近のカーネルではオプションではありません。