web-dev-qa-db-fra.com

Pourquoi GCC crée-t-il un objet partagé au lieu d'un binaire exécutable selon le fichier?

J'ai une bibliothèque que je construis. Tous mes objets se compilent et se lient successivement lorsque j'exécute l'un des deux: ar rcs lib/libryftts.a $^

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

dans mon Makefile. Je peux également les installer avec succès dans /usr/local/lib Lorsque je teste le fichier avec nm, toutes les fonctions sont là. Mon problème est que lorsque j'exécute gcc testing/test.c -lryftts -o test && file ./test ou gcc testing/test.c lib/libryftts.a -o test && file ./test ça dit:

test: ELF 64-bit LSB shared object au lieu de test: ELF 64-bit LSB executable comme je m'y attendais. Qu'est-ce que je fais mal?

27
Luke Smith

Qu'est-ce que je fais mal?

Rien.

Il semble que votre GCC soit configuré pour construire -pie binaires par défaut. Ces binaires sont vraiment sont des bibliothèques partagées (de type ET_DYN), sauf qu'ils fonctionnent comme un exécutable normal.

Donc, vous devez simplement exécuter votre binaire, et (si cela fonctionne) ne vous inquiétez pas.

Ou vous pouvez lier votre binaire avec gcc -no-pie ... et cela devrait produire un exécutable nonPIE de type ET_EXEC, pour lequel file dira ELF 64-bit LSB executable.

28
Employed Russian

file 5.36 le dit clairement

file 5.36 l'imprime clairement si l'exécutable est PIE ou non comme indiqué sur: https://unix.stackexchange.com/questions/89211/how-to-test-whether-a- linux-binary-was-compiled-as-position-indépendant-code/435038 # 435038

Par exemple, un exécutable PIE s'affiche comme suit:

main.out: exécutable ELF 64-bit pie tarte, x86-64, version 1 (SYSV), lié dynamiquement, non supprimé

et un non-TARTE comme:

main.out: exécutable LSF 64 bits ELF, x86-64, version 1 (SYSV), lié statiquement, non supprimé

La fonctionnalité a été introduite dans la version 5.33, mais elle n'a fait qu'une simple vérification chmod +x. Avant cela, il venait juste d'imprimer shared object Pour PIE.

Dans 5.34, il était censé commencer à vérifier les métadonnées ELF DF_1_PIE Plus spécialisées, mais en raison d'un bogue dans l'implémentation lors de la validation 9109a696f3289ba00eaa222fd432755ec4287e28 il a en fait cassé les choses et montré les exécutables GCC PIE comme shared objects.

Le bogue a été corrigé dans 5.36 lors de la validation 084b161cf888b5286dbbcd964c31ccad4f64d9 .

Le bogue est présent notamment dans Ubuntu 18.10 qui a file 5.34.

Il ne se manifeste pas lors de la liaison du code Assembly avec ld -pie En raison d'une coïncidence.

La ventilation du code source est indiquée dans la section "file 5.36 analyse du code source" de cette réponse.

Le noyau Linux 5.0 détermine si ASLR peut être utilisé en fonction de ET_DYN

La cause première de la "confusion" file est que les bibliothèques exécutables PIE et partagées sont indépendantes de la position et peuvent être placées dans des emplacements de mémoire aléatoires.

A fs/binfmt_elf.c le noyau accepte uniquement ces deux types de fichiers 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;

Ensuite, ce n'est que pour ET_DYN Que le paramètre load_bias Est différent de zéro. Le load_bias Est alors ce qui détermine le décalage ELF: Comment l'adresse de la section de texte d'un exécutable PIE est-elle déterminée sous Linux?

/*
 * 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;

Je confirme cela expérimentalement à: Quelle est l'option -fPIE pour les exécutables indépendants de la position dans gcc et ld?

file 5.36 répartition des comportements

Après avoir étudié le fonctionnement de file à partir de sa source. Nous conclurons que:

  • si Elf32_Ehdr.e_type == ET_EXEC
    • imprimer executable
  • sinon si Elf32_Ehdr.e_type == ET_DYN
    • si DT_FLAGS_1 l'entrée de section dynamique est présente
      • si DF_1_PIE est défini dans DT_FLAGS_1:
        • imprimer pie executable
      • autre
        • imprimer shared object
    • autre
      • si le fichier est exécutable par un utilisateur, un groupe ou d'autres
        • imprimer pie executable
      • autre
        • imprimer shared object

Et voici quelques expériences qui confirment que:

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

Testé dans Ubuntu 18.10, GCC 8.2.0, Binutils 2.31.1.

L'exemple de test complet pour chaque type d'expérience est décrit à l'adresse suivante:

ELF type Et DF_1_PIE Sont déterminés respectivement avec:

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

file 5.36 analyse du code source

Le fichier clé à analyser est magic/Magdir/elf .

Ce format magique détermine les types de fichiers en fonction uniquement des valeurs d'octets à des positions fixes.

Le format lui-même est documenté à:

man 5 magic

Donc, à ce stade, vous voudrez avoir les documents suivants à portée de main:

Vers la fin du dossier, on voit:

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 Sont les 4 octets magiques au début de chaque fichier ELF. \177 Est l'octal de 0x7F.

Ensuite, en comparant avec la structure Elf32_Ehdr Du standard, nous voyons que l'octet 4 (le 5ème octet, le premier après l'identifiant magique), détermine la classe ELF:

e_ident[EI_CLASSELFCLASS]

et certaines de ses valeurs possibles sont:

ELFCLASS32 1
ELFCLASS64 2

Dans file source alors, nous avons:

1 32-bit
2 64-bit

et 32-bit et 64-bit sont les chaînes que file sort vers stdout!

Alors maintenant, nous recherchons shared object Dans ce fichier, et nous sommes amenés à:

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},

Donc, elf-le Est une sorte d'identifiant qui est inclus dans la partie précédente du code.

L'octet 16 est exactement le type ELF:

Elf32_Ehdr.e_type

et certaines de ses valeurs sont:

ET_EXEC 2
ET_DYN  3

Par conséquent, ET_EXEC Est toujours imprimé sous la forme executable.

ET_DYN A cependant deux possibilités selon ${x:

  • pie executable
  • shared object

${x Demande: le fichier est-il exécutable ou non par un utilisateur, un groupe ou autre? Si oui, affichez pie executable, Sinon shared object.

Cette expansion se fait dans la fonction varexpand dans src/softmagic.c :

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;

Il y a cependant un hack de plus! Dans src/readelf.c fonction dodynamic, si DT_FLAGS_1 Marque l'entrée de la section dynamique (PT_DYNAMIC) Est présente, alors les autorisations dans st->mode sont remplacés par la présence ou l'absence du drapeau DF_1_PIE:

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

Le bogue de 5.34 est que le code initial a été écrit comme suit:

    if (xdh_val == DF_1_PIE)

ce qui signifie que si un autre indicateur a été défini, ce que GCC fait par défaut en raison de DF_1_NOW, l'exécutable s'affiche comme shared object.

L'entrée de drapeaux DT_FLAGS_1 N'est pas décrite dans la norme ELF, elle doit donc être une extension Binutils.

Ce drapeau n'a aucune utilité dans le noyau Linux 5.0 ou la glibc 2.27, donc je semble être purement informatif pour indiquer qu'un fichier est PIE ou non.