Nieoczekiwane zezwolenie na wykonanie z mmap podczas dołączania plików zespołu do projektu

94

Uderzam w to głową w ścianę.

W moim projekcie, gdy przydzielam pamięć za mmappomocą mapowania ( /proc/self/maps) pokazuje, że jest to region możliwy do odczytu i wykonywania, mimo że zażądałem tylko pamięci do odczytu.

Po przejrzeniu strace (który wyglądał dobrze) i innym debugowaniu udało mi się zidentyfikować jedyną rzecz, która wydaje się unikać tego dziwnego problemu: usunięcie plików asemblera z projektu i pozostawienie tylko czystego C. (co ?!)

Oto mój dziwny przykład: pracuję nad Ubunbtu 19.04 i domyślnym gcc.

Jeśli kompilujesz docelowy plik wykonywalny z plikiem ASM (który jest pusty), a następnie mmapzwraca czytelny i wykonywalny region, jeśli kompilujesz bez niego, zachowa się poprawnie. Zobacz dane wyjściowe, /proc/self/mapsktóre osadziłem w moim przykładzie.

przyklad.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;
}

przyklad.s : jest pusty plik!

Wyjścia

Dzięki dołączonej wersji ASM

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

Bez wersji dołączonej do ASM

VirtualBox:~/mechanics/build$ gcc example.c -o example && ./example
7f1569296000-7f1569298000 r--p 00000000 00:00 0 
Ben Hirschberg
źródło
5
To jest naprawdę dziwne.
fuz
6
Udało mi się to odtworzyć za pomocą GCC (bez CMake), więc zredagowałem pytanie, aby uczynić przykład bardziej minimalnym.
Joseph Sible-Reinstate Monica
2
Prawdopodobnie powiązany stackoverflow.com/questions/32730643/…
Sami Kuhmonen
Być może masz rację, część odpowiedzi musi dotyczyć postaci READ_IMPLIES_EXEC
Ben Hirschberg
Zbierz pliki źródłowe za pomocą -Wa,--noexecstack.
jww

Odpowiedzi:

90

Linux ma domenę wykonawczą o nazwie READ_IMPLIES_EXEC, co powoduje, PROT_READże podane są również wszystkie przydzielone strony PROT_EXEC. Ten program pokaże, czy jest to włączone dla siebie:

#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;
}

Jeśli skompilujesz to wraz z pustym .splikiem, zobaczysz, że jest włączony, ale bez niego zostanie wyłączony. Początkowa wartość tego pochodzi z meta-informacji ELF w twoim pliku binarnym . Zrobić readelf -Wl example. Zobaczysz ten wiersz, gdy skompilujesz bez pustego .spliku:

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

Ale ten, kiedy się z nim skompilowałeś:

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

Uwaga RWEzamiast po prostu RW. Powodem tego jest to, że linker zakłada, że ​​pliki zestawu wymagają read-implies-exec, chyba że zostanie wyraźnie powiedziane, że nie, a jeśli jakakolwiek część programu wymaga read-implies-exec, to jest włączona dla całego programu . Pliki zestawu, które kompiluje GCC, informują go, że nie potrzebuje tego w tej linii (zobaczysz to, jeśli kompilujesz -S):

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

Umieść tę linię example.s, a będzie ona służyć do poinformowania linkera, że ​​również jej nie potrzebuje, a twój program będzie wtedy działał zgodnie z oczekiwaniami.

Joseph Sible-Reinstate Monica
źródło
13
Cholera jasna, to dziwne niewykonanie. Wydaje mi się, że łańcuch narzędzi istniał przed noexec, a ustawienie noexec jako domyślnego mogło spowodować uszkodzenie. Teraz jestem ciekawy, jak inne asemblery, takie jak NASM / YASM, tworzą .opliki! Ale w każdym razie wydaje mi się, że jest to mechanizm, który gcc -zexecstackwykorzystuje i dlaczego sprawia, że ​​nie tylko stos, ale wszystko jest wykonywalne.
Peter Cordes,
23
@Peter - Dlatego dodają projekty takie jak Botan, Crypto ++ i OpenSSL, które używają asemblera -Wa,--noexecstack. Myślę, że to bardzo paskudna, ostra krawędź. Cicha utrata stosów NX powinna stanowić lukę w zabezpieczeniach. Ludzie Binutil powinni to naprawić.
jww
14
@jww To rzeczywiście problem z bezpieczeństwem, dziwne, że nikt wcześniej go nie zgłosił
Ben Hirschberg,
4
+1, ale ta odpowiedź byłaby o wiele lepsza, gdyby .note.GNU-stack,"",@progbitswyjaśniono znaczenie / logikę linii - w tej chwili jest nieprzejrzysta, co odpowiada „magicznemu ciągowi znaków powoduje ten efekt”, ale ciąg wygląda na to, że ma jakiś rodzaj semantyka.
mtraceur
33

Alternatywnie do modyfikowania plików asemblacji za pomocą wariantów dyrektywy sekcji specyficznych dla GNU, możesz dodać -Wa,--noexecstackdo wiersza poleceń budowę plików asemblacji. Na przykład zobacz, jak to robię w Musl configure:

https://git.musl-libc.org/cgit/musl/commit/configure?id=adefe830dd376be386df5650a09c313c483adf1a

Uważam, że przynajmniej niektóre wersje clanga ze zintegrowanym asemblerem mogą wymagać przekazania go jako --noexecstack(bez -Wa), więc twój skrypt konfiguracyjny powinien prawdopodobnie sprawdzić oba i zobaczyć, który jest akceptowany.

Możesz również użyć -Wl,-z,noexecstackw czasie połączenia (w LDFLAGS), aby uzyskać ten sam wynik. Wadą tego jest to, że nie pomaga, jeśli twój projekt tworzy statyczne .apliki bibliotek do użytku przez inne oprogramowanie, ponieważ wtedy nie kontrolujesz opcji czasu łącza, gdy są one używane przez inne programy.

R .. GitHub ZATRZYMAJ LÓD
źródło
1
Hmm ... Nie wiedziałem, że jesteś Rich Felker przed przeczytaniem tego postu. Dlaczego Twoja wyświetlana nazwa nie jest wyświetlana?
SS Anne,