web-dev-qa-db-ja.com

GCCがファイルに従って実行可能バイナリではなく共有オブジェクトを作成するのはなぜですか?

自分が作成しているライブラリがあります。次のいずれかを実行すると、すべてのオブジェクトがコンパイルおよびリンクされます:ar rcs lib/libryftts.a $^

gcc -shared $^ -o lib/libryftts.so

私のメイクファイルで。また、それらを/usr/local/libに正常にインストールすることもできます。nmでファイルをテストすると、すべての関数が存在します。私の問題は、gcc testing/test.c -lryftts -o test && file ./testまたはgcc testing/test.c lib/libryftts.a -o test && file ./testを実行すると次のように表示されることです。

test: ELF 64-bit LSB shared objectではなくtest: ELF 64-bit LSB executable。何が悪いのですか?

27
Luke Smith

何が悪いのですか?

何もない。

GCCはデフォルトで-pieバイナリをビルドするように設定されているようです。これらのバイナリは実際にはare共有ライブラリ(タイプET_DYN)ですが、通常の実行可能ファイルと同じように実行されます。

したがって、バイナリを実行するだけでよく、(動作する場合は)心配する必要はありません。

または、バイナリをgcc -no-pie ...とリンクすると、タイプET_EXECの非PIE実行可能ファイルが生成され、fileELF 64-bit LSB executableと表示されます。

28

file 5.36はそれをはっきり言っています

file 5.36は、実行可能ファイルがPIEであるかどうかにかかわらず、 に実際に明確に出力します(= /// =) -a-linux-binary-was-compiled-as-position-independent-code/435038#435038

たとえば、PIE実行可能ファイルは次のように表示されます。

main.out:ELF 64ビットLSBパイ実行可能ファイル、x86-64、バージョン1(SYSV)、動的にリンク、ストリップされない

そして非PIEのものとして:

main.out:ELF 64ビットLSB実行可能ファイル、x86-64、バージョン1(SYSV)、静的にリンク、削除されない

この機能は5.33で導入されましたが、単純なchmod +xチェックだけを行いました。その前に、PIEのshared objectを出力しました。

5.34では、より専門的なDF_1_PIE ELFメタデータのチェックを開始することを意図していましたが、コミット時の実装のバグ 9109a696f3289ba00eaa222fd432755ec4287e28 により、実際に問題が発生し、GCCが表示されましたshared objectsとしてのPIE実行可能ファイル。

バグは5.36のコミット時に修正されました 03084b161cf888b5286dbbcd964c31ccad4f64d9

このバグは特に、file 5.34を含むUbuntu 18.10に存在します。

偶然のため、アセンブリコードをld -pieにリンクすると、それは現れません。

ソースコードの内訳は、この回答の「file 5.36ソースコード分析」セクションに示されています。

Linuxカーネル5.0は、ET_DYNに基づいてASLRを使用できるかどうかを決定します

file "混乱"の根本的な原因は、 PIE実行可能ファイル と共有ライブラリの両方が位置に依存せず、ランダムなメモリ位置に配置できることです。

fs/binfmt_elf.c では、カーネルは次の2種類のELFファイルのみを受け入れます。

/* First of all, some simple consistency checks */
if (interp_elf_ex->e_type != ET_EXEC &&
        interp_elf_ex->e_type != ET_DYN)
        goto out;

次に、ET_DYNの場合のみ、load_biasをゼロ以外の値に設定します。 ELFオフセットを決定するのはload_biasです: LinuxでPIE実行可能ファイルのテキストセクションのアドレスはどのように決定されますか?

/*
 * If we are loading ET_EXEC or we have already performed
 * the ET_DYN load_addr calculations, proceed normally.
 */
if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
        elf_flags |= elf_fixed;
} else if (loc->elf_ex.e_type == ET_DYN) {
        /*
         * This logic is run once for the first LOAD Program
         * Header for ET_DYN binaries to calculate the
         * randomization (load_bias) for all the LOAD
         * Program Headers, and to calculate the entire
         * size of the ELF mapping (total_size). (Note that
         * load_addr_set is set to true later once the
         * initial mapping is performed.)
         *
         * There are effectively two types of ET_DYN
         * binaries: programs (i.e. PIE: ET_DYN with INTERP)
         * and loaders (ET_DYN without INTERP, since they
         * _are_ the ELF interpreter). The loaders must
         * be loaded away from programs since the program
         * may otherwise collide with the loader (especially
         * for ET_EXEC which does not have a randomized
         * position). For example to handle invocations of
         * "./ld.so someprog" to test out a new version of
         * the loader, the subsequent program that the
         * loader loads must avoid the loader itself, so
         * they cannot share the same load range. Sufficient
         * room for the brk must be allocated with the
         * loader as well, since brk must be available with
         * the loader.
         *
         * Therefore, programs are loaded offset from
         * ELF_ET_DYN_BASE and loaders are loaded into the
         * independently randomized mmap region (0 load_bias
         * without MAP_FIXED).
         */
        if (elf_interpreter) {
                load_bias = ELF_ET_DYN_BASE;
                if (current->flags & PF_RANDOMIZE)
                        load_bias += Arch_mmap_rnd();
                elf_flags |= elf_fixed;
        } else
                load_bias = 0;

私はこれを実験的に確認しています: gccとldの位置に依存しない実行可能ファイルの-fPIEオプションとは何ですか?

file 5.36動作の内訳

ソースからfileがどのように機能するかを調べた後。結論は次のとおりです。

  • if Elf32_Ehdr.e_type == ET_EXEC
    • 印刷executable
  • それ以外の場合Elf32_Ehdr.e_type == ET_DYN
    • DT_FLAGS_1動的セクションエントリが存在する場合
      • DF_1_PIEDT_FLAGS_1に設定されている場合:
        • 印刷pie executable
      • そうしないと
        • 印刷shared object
    • そうしないと
      • ファイルがユーザー、グループ、その他によって実行可能である場合
        • 印刷pie executable
      • そうしないと
        • 印刷shared object

そしてこれはそれを確認するいくつかの実験です:

Executable generation        ELF type  DT_FLAGS_1  DF_1_PIE  chdmod +x      file 5.36
---------------------------  --------  ----------  --------  -------------- --------------
gcc -fpie -pie               ET_DYN    y           y         y              pie executable
gcc -fno-pie -no-pie         ET_EXEC   n           n         y              executable
gcc -shared                  ET_DYN    n           n         y              pie executable
gcc -shared                  ET_DYN    n           n         n              shared object
ld                           ET_EXEC   n           n         y              executable
ld -pie --dynamic-linker     ET_DYN    y           y         y              pie executable
ld -pie --no-dynamic-linker  ET_DYN    y           y         y              pie executable

Ubuntu 18.10、GCC 8.2.0、Binutils 2.31.1でテスト済み。

各タイプの実験の完全なテスト例は、次の場所で説明されています。

ELF typeおよびDF_1_PIEは、それぞれ次のように決定されます。

readelf --file-header main.out | grep Type
readelf --dynamic     main.out | grep FLAGS_1

file 5.36ソースコード分析

分析する重要なファイルは magic/Magdir/elf です。

このマジックフォーマットは、固定位置のバイトの値のみに応じてファイルタイプを決定します。

フォーマット自体は次の場所に記載されています。

man 5 magic

したがって、この時点で、次のドキュメントを手元に置いておきます。

ファイルの終わりに向かって、次のように表示されます。

0       string          \177ELF         ELF
!:strength *2
>4      byte            0               invalid class
>4      byte            1               32-bit
>4      byte            2               64-bit
>5      byte            0               invalid byte order
>5      byte            1               LSB
>>0     use             elf-le
>5      byte            2               MSB
>>0     use             \^elf-le

\177ELFは、すべてのELFファイルの先頭にある4つのマジックバイトです。 \1770x7Fの8進数です。

次に、標準のElf32_Ehdr構造体と比較すると、バイト4(5番目のバイト、マジック識別子の後の最初のバイト)がELFクラスを決定していることがわかります。

e_ident[EI_CLASSELFCLASS]

可能な値の一部は次のとおりです。

ELFCLASS32 1
ELFCLASS64 2

fileソースでは、次のようになります。

1 32-bit
2 64-bit

32-bit64-bitは、fileがstdoutに出力する文字列です!

したがって、そのファイルでshared objectを検索すると、次のように導かれます。

0       name            elf-le
>16     leshort         0               no file type,
!:mime  application/octet-stream
>16     leshort         1               relocatable,
!:mime  application/x-object
>16     leshort         2               executable,
!:mime  application/x-executable
>16     leshort         3               ${x?pie executable:shared object},

したがって、このelf-leは、コードの前の部分に含まれるある種の識別子です。

バイト16はまさにELFタイプです。

Elf32_Ehdr.e_type

その値の一部は次のとおりです。

ET_EXEC 2
ET_DYN  3

したがって、ET_EXECは常にexecutableとして出力されます。

ET_DYNには、${xに応じて2つの可能性があります。

  • pie executable
  • shared object

${xの質問:ファイルは実行可能ですか、それともユーザー、グループ、その他のいずれでも実行できませんか?はいの場合はpie executable、それ以外の場合はshared objectを表示します。

この展開は、 src/softmagic.cvarexpand関数で行われます。

static int
varexpand(struct magic_set *ms, char *buf, size_t len, const char *str)
{
    [...]
            case 'x':
                    if (ms->mode & 0111) {
                            ptr = t;
                            l = et - t;
                    } else {
                            ptr = e;
                            l = ee - e;
                    }
                    break;

ただし、もう1つハックがあります。 src/readelf.c 関数dodynamicで、動的セクション(DT_FLAGS_1)のPT_DYNAMICフラグエントリが存在する場合、 st->modeの権限は、DF_1_PIEフラグの有無によって上書きされます。

case DT_FLAGS_1:
        if (xdh_val & DF_1_PIE)
                ms->mode |= 0111;
        else
                ms->mode &= ~0111;
        break;

5.34のバグは、最初のコードが次のように記述されていたことです。

    if (xdh_val == DF_1_PIE)

つまり、DF_1_NOWが原因でGCCがデフォルトで行う別のフラグが設定されている場合、実行可能ファイルはshared objectと表示されていました。

DT_FLAGS_1フラグエントリはELF標準で記述されていないため、Binutils拡張である必要があります。

このフラグはLinuxカーネル5.0またはglibc 2.27では使用されないため、ファイルがPIEであるかどうかを示すだけで情報が得られるようです。