web-dev-qa-db-fra.com

Les adresses absolues 32 bits ne sont plus autorisées sous Linux x86-64?

Linux 64 bits utilise le petit modèle de mémoire par défaut, qui place tout le code et les données statiques sous la limite d'adresse de 2 Go. Cela garantit que vous pouvez utiliser des adresses absolues 32 bits. Les versions plus anciennes de gcc utilisent des adresses absolues 32 bits pour les tableaux statiques afin de sauvegarder une instruction supplémentaire pour le calcul d'adresse relative. Cependant, cela ne fonctionne plus. Si j'essaie de créer une adresse absolue 32 bits dans Assembly, j'obtiens l'erreur de l'éditeur de liens: "la relocalisation R_X86_64_32S contre` .data 'ne peut pas être utilisée lors de la création d'un objet partagé; recompilez avec -fPIC ". Ce message d'erreur est trompeur, bien sûr, car je ne crée pas d'objet partagé et -fPIC n'aide pas. Ce que j'ai découvert jusqu'à présent est le suivant: la version 4.8.5 de gcc utilise des adresses absolues 32 bits pour les tableaux statiques, contrairement à la version 6.3.0 de gcc. la version 5 ne l'est probablement pas non plus. L'éditeur de liens dans binutils 2.24 autorise les adresses absolues 32 bits, contrairement au verson 2.28.

La conséquence de ce changement est que les anciennes bibliothèques doivent être recompilées et que le code d'assembly hérité est cassé.

Maintenant, je veux demander: Quand ce changement a-t-il été fait? Est-ce documenté quelque part? Et existe-t-il une option de l'éditeur de liens qui lui permet d'accepter des adresses absolues 32 bits?

26
A Fog

Votre distribution a configuré gcc avec --enable-default-pie, Donc elle crée des exécutables indépendants de la position par défaut, (permettant l'ASLR de l'exécutable ainsi que des bibliothèques). La plupart des distributions font cela, ces jours-ci.

En fait, vous êtes créer un objet partagé: les exécutables PIE sont en quelque sorte un hack utilisant un objet partagé avec un point d'entrée. L'éditeur de liens dynamique a déjà pris en charge cela, et ASLR est agréable pour la sécurité, donc c'était le moyen le plus simple d'implémenter ASLR pour les exécutables.

La relocalisation absolue 32 bits n'est pas autorisée dans un objet partagé ELF; cela les empêcherait d'être chargés en dehors du faible 2 Go (pour les adresses 32 bits à extension de signe). Les adresses absolues 64 bits sont autorisées, mais en général, vous ne le souhaitez que pour les tables de saut ou d'autres données statiques, pas dans le cadre d'instructions.1

La partie recompile with -fPIC Du message d'erreur est fausse pour asm manuscrite; il est écrit pour le cas de personnes qui compilent avec gcc -c et essaient ensuite de se connecter avec gcc -shared -o foo.so *.o, avec un gcc où -fPIE est pas le défaut. Le message d'erreur devrait probablement changer car de nombreuses personnes rencontrent cette erreur lors de la liaison d'un asm manuscrit.


Comment utiliser l'adressage relatif au RIP: principes de base

Utilisez toujours l'adressage relatif au RIP pour les cas simples où il n'y a pas d'inconvénient. Voir aussi la note de bas de page 1 ci-dessous et cette réponse pour la syntaxe . N'envisagez d'utiliser l'adressage absolu 32 bits que lorsque cela est réellement utile pour la taille du code au lieu d'être nuisible. par exemple. NASM default rel en haut de votre fichier.

AT&T foo(%rip) ou en GAZ .intel_syntax noprefix Utilisez [rip + foo].


Désactivez le mode PIE pour faire fonctionner l'adressage absolu 32 bits

Utilisez gcc -fno-pie -no-pie Pour remplacer cela par l'ancien comportement. -no-pie Est l'option de l'éditeur de liens, -fno-pie Est l'option code-gen . Avec seulement -fno-pie, Gcc créera du code comme mov eax, offset .LC0 Qui ne sera pas lié au -pie Encore activé.

( clang peut aussi avoir PIE activé par défaut: utilisez clang -fno-pie -nopie. A patch de juillet 2017 fait de -no-pie Un alias pour -nopie, Pour compat avec gcc, mais clang4.0.1 ne l'a pas.)


Coût des performances de PIE pour le code 64 bits (mineur) ou 32 bits (majeur)

Avec seulement -no-pie, (Mais toujours -fpie) Le code généré par le compilateur (à partir de sources C ou C++) sera légèrement plus lent et plus grand que nécessaire , mais sera toujours lié à un exécutable dépendant de la position qui ne bénéficiera pas de l'ASLR. "Trop de TARTE est mauvais pour les performances" signale un ralentissement moyen de 3% pour x86-64 sur SPEC CPU2006 (I ne pas avoir de copie du papier, alors IDK sur quel matériel il se trouvait: /). Mais en code 32 bits, le ralentissement moyen est de 10%, le pire des cas de 25% (sur SPEC CPU2006).

La pénalité pour les exécutables PIE est principalement pour des choses comme l'indexation de tableaux statiques, comme Agner le décrit dans la question, où l'utilisation d'une adresse statique en 32 bits immédiat ou dans le cadre d'un mode d'adressage [disp32 + index*4] Enregistre les instructions et les registres par rapport à un LEA relatif au RIP pour obtenir une adresse dans un registre. Aussi 5 octets mov r32, imm32 Au lieu de 7 octets lea r64, [rel symbol] Pour obtenir une adresse statique dans un registre est bien pour passer l'adresse d'un littéral de chaîne ou d'autres données statiques à une fonction.

-fPIE Suppose toujours aucune interposition de symbole pour les variables/fonctions globales, contrairement à -fPIC Pour les bibliothèques partagées qui doivent passer par le GOT pour accéder aux globaux (ce qui est une autre raison d'utiliser static pour toutes les variables qui peuvent être limitées à la portée du fichier au lieu de globale). Voir L'état désolé des bibliothèques dynamiques sous Linux .

Ainsi, -fPIE Est beaucoup moins mauvais que -fPIC Pour le code 64 bits, mais toujours mauvais pour 32 bits car l'adressage relatif au RIP n'est pas disponible . Voir quelques exemples sur l'explorateur du compilateur Godbolt . En moyenne, -fPIE Présente un très faible inconvénient performances/taille de code en code 64 bits. Le pire des cas pour une boucle spécifique pourrait n'être que de quelques%. Mais la TARTE 32 bits peut être bien pire.

Aucune de ces options de code-code -f Ne fait la différence lors de la simple liaison ou lors de l'assemblage d'un fichier asm manuscrit .S. gcc -fno-pie -no-pie -O3 main.c nasm_output.o Est un cas où vous souhaitez les deux options.


Vérification de votre configuration GCC

Si votre GCC a été configuré de cette façon, gcc -v |& grep -o -e '[^ ]*pie' Imprime --enable-default-pie. La prise en charge de cette option de configuration a été ajoutée à gcc dans début 2015 . Ubuntu l'a activé en 16.10 et Debian à peu près en même temps dans gcc 6.2.0-7 (Conduisant à des erreurs de construction du noyau: https://lkml.org/lkml/2016/10/21/904 =).

Connexes: Construire des noyaux x86 compressés en tant que PIE a également été affecté par la valeur par défaut modifiée.

Pourquoi Linux ne randomise-t-il pas l'adresse du segment de code exécutable? est une question plus ancienne sur la raison pour laquelle ce n'était pas la valeur par défaut plus tôt, ou n'a été activé que pour quelques paquets sur Ubuntu plus ancien avant d'être activé à travers le conseil d'administration.


Notez que ld lui-même n'a pas changé sa valeur par défaut . Il fonctionne toujours normalement (au moins sur Arch Linux avec binutils 2.28). Le changement est que gcc par défaut passe -pie Comme option de l'éditeur de liens, sauf si vous utilisez explicitement -static Ou -no-pie.

Dans un fichier source NASM, j'ai utilisé a32 mov eax, [abs buf] Pour obtenir une adresse absolue. (Je testais si la méthode à 6 octets pour coder les petites adresses absolues (taille de l'adresse + mov eax, moffs: 67 a1 40 f1 60 00) A un blocage LCP sur les processeurs Intel. Oui . )

nasm -felf64 -Worphan-labels -g -Fdwarf testloop.asm &&
ld -o testloop testloop.o              # works: static executable

gcc -v -nostdlib testloop.o            # doesn't work
...
..../collect2  ... -pie ...
/usr/bin/ld: testloop.o: relocation R_X86_64_32 against `.bss' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Nonrepresentable section on output
collect2: error: ld returned 1 exit status

gcc -v -no-pie -nostdlib testloop.o    # works
gcc -v -static -nostdlib testloop.o    # also works: -static implies -no-pie

GCC peut également créer un "TARTE statique" avec -static-pie; ASLRed par aucune bibliothèque dynamique ou interprète ELF. Ce n'est pas la même chose que -static -pie - ces conflits les uns avec les autres (vous obtenez un non-PIE statique) bien que il pourrait éventuellement être modifié .

connexes: construction d'exécutables statiques/dynamiques avec/sans libc, définition de _start ou main .


Vérifier si un exécutable existant est PIE ou non

file et readelf disent que les PIE sont des "objets partagés", pas des exécutables ELF. EXEC de type ELF ne peut pas être TARTE.

$ gcc -fno-pie  -no-pie -O3 hello.c
$ file a.out
a.out: ELF 64-bit LSB executable, ...

$ gcc -O3 hello.c
$ file a.out
a.out: ELF 64-bit LSB shared object, ...

 ## Or with a more recent version of file:
a.out: ELF 64-bit LSB pie executable, ...

gcc -static-pie apparaît comme LSB pie executable, dynamically linked avec le file actuel. C'est DYN de type ELF, mais readelf n'affiche pas .interp, Et ldd vous dira qu'il est lié statiquement. GDB starti et /proc/maps Confirme que l'exécution commence en haut de son _start, Pas dans un interpréteur ELF.

Cela a également été demandé à: Comment tester si un binaire Linux a été compilé en tant que code indépendant de la position?


Semi-apparenté (mais pas vraiment): une autre fonctionnalité récente de gcc est gcc -fno-plt. Enfin, les appels dans les bibliothèques partagées peuvent être simplement call [rip + symbol@GOTPCREL] (AT&T call *puts@GOTPCREL(%rip)), sans trampoline PLT.

La version NASM de ceci est call [rel puts wrt ..got]
comme alternative à call puts wrt ..plt. Voir Impossible d'appeler la fonction de bibliothèque standard C sur Linux 64 bits à partir du code Assembly (yasm) . Cela fonctionne dans un PIE ou non-PIE, et évite que l'éditeur de liens crée un stub PLT pour vous.

Certaines distributions ont commencé à l'activer. Cela évite également d'avoir besoin de pages de mémoire inscriptibles + exécutables, donc c'est bon pour la sécurité contre l'injection de code. C'est une accélération importante pour les programmes qui font beaucoup d'appels de bibliothèque partagée, par exemple x86-64 clang -O2 -g la compilation de tramp3d passe de 41,6 s à 36,8 s sur n'importe quel matériel l'auteur du patch testé sur . (clang est peut-être le pire des cas pour les appels de bibliothèque partagée, faisant beaucoup d'appels à de petites fonctions de bibliothèque LLVM.)

Il nécessite une liaison précoce au lieu d'une liaison dynamique paresseuse, il est donc plus lent pour les gros programmes qui sortent immédiatement. (par exemple clang --version ou compilation de hello.c). Ce ralentissement pourrait apparemment être réduit avec le lien préalable.

Cela ne supprime cependant pas la surcharge GOT pour les variables externes dans le code PIC de la bibliothèque partagée. (Voir le lien Godbolt ci-dessus).


Notes de bas de page 1

Les adresses absolues 64 bits sont en fait autorisées dans les objets partagés Linux ELF, avec délocalisations de texte pour permettre le chargement à différentes adresses (ASLR et bibliothèques partagées). Cela vous permet d'avoir des tables de sauts dans section .rodata Ou static const int *foo = &bar; Sans initialiseur d'exécution.

Donc mov rdi, qword msg Fonctionne (syntaxe NASM/YASM pour 10 octets mov r64, imm64 , alias syntaxe AT&T movabs, la seule instruction qui peut utiliser un 64- peu immédiat). Mais c'est plus grand et généralement plus lent que lea rdi, [rel msg], ce que vous devez utiliser si vous décidez de ne pas désactiver -pie . Un immédiat 64 bits est plus lent à récupérer à partir du cache uop sur les processeurs de la famille Sandybridge, selon pdf microarch d'Agner Fog . (Oui, la même personne qui a posé cette question. :)

Vous pouvez utiliser default rel De NASM au lieu de le spécifier dans chaque mode d'adressage [rel symbol]. Voir aussi le format Mach-O 64 bits ne prend pas en charge les adresses absolues 32 bits. NASM Accessing Array pour une description plus détaillée de la façon d'éviter l'adressage absolu 32 bits. OS X ne peut pas du tout utiliser les adresses 32 bits, donc l'adressage relatif au RIP est également le meilleur moyen.

Dans le code dépendant de la position (-no-pie), Vous devez utiliser mov edi, msg lorsque vous voulez une adresse dans un registre; 5 $ mov r32, imm32 Est encore plus petit que LEA relatif au RIP, et plus de ports d'exécution peuvent l'exécuter.

39
Peter Cordes