Izin exec tak terduga dari mmap saat file perakitan dimasukkan dalam proyek

94

Aku membenturkan kepalaku ke dinding dengan ini.

Dalam proyek saya, ketika saya mengalokasikan memori dengan mmappemetaan ( /proc/self/maps) menunjukkan bahwa itu adalah wilayah yang dapat dibaca dan dieksekusi meskipun saya hanya meminta memori yang dapat dibaca.

Setelah melihat ke dalam strace (yang terlihat bagus) dan debugging lainnya, saya dapat mengidentifikasi satu-satunya hal yang tampaknya menghindari masalah aneh ini: menghapus file assembly dari proyek dan hanya menyisakan C. murni (apa ?!)

Jadi di sini adalah contoh aneh saya, saya mengerjakan Ubunbtu 19.04 dan gcc default.

Jika Anda mengkompilasi target yang dapat dieksekusi dengan file ASM (yang kosong) kemudian mmapmengembalikan wilayah yang dapat dibaca dan dieksekusi, jika Anda membangun tanpa itu maka berperilaku dengan benar. Lihat output /proc/self/mapsyang telah saya sisipkan dalam contoh saya.

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

example.s : Apakah file kosong!

Keluaran

Dengan ASM termasuk versi

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

Tanpa ASM termasuk versi

VirtualBox:~/mechanics/build$ gcc example.c -o example && ./example
7f1569296000-7f1569298000 r--p 00000000 00:00 0 
Ben Hirschberg
sumber
5
Ini sangat aneh.
fuz
6
Saya berhasil mereproduksi ini hanya dengan GCC (tanpa CMake), jadi saya mengedit pertanyaan untuk membuat contoh lebih minimal.
Joseph Sible-Reinstate Monica
2
Kemungkinan terkait stackoverflow.com/questions/32730643/...
Sami Kuhmonen
Anda mungkin benar, sebagian dari jawaban dia harus ada di sekitar READ_IMPLIES_EXEC persona
Ben Hirschberg
Kumpulkan file sumber Anda dengan -Wa,--noexecstack.
jww

Jawaban:

90

Linux memiliki domain eksekusi yang disebut READ_IMPLIES_EXEC, yang menyebabkan semua halaman yang dialokasikan dengan PROT_READjuga diberikan PROT_EXEC. Program ini akan menunjukkan kepada Anda apakah itu diaktifkan untuk dirinya sendiri:

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

Jika Anda mengompilasinya bersama dengan .sfile kosong , Anda akan melihat bahwa itu diaktifkan, tetapi tanpa itu, ia akan dinonaktifkan. Nilai awal ini berasal dari meta-informasi ELF dalam biner Anda . Lakukan readelf -Wl example. Anda akan melihat baris ini ketika dikompilasi tanpa .sfile kosong :

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

Tapi yang ini saat kamu mengompilasinya:

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

Catat, RWEbukan hanya RW. Alasan untuk ini adalah bahwa penghubung mengasumsikan bahwa file rakitan Anda memerlukan read-Implies-exec kecuali itu secara eksplisit mengatakan bahwa mereka tidak, dan jika ada bagian dari program Anda memerlukan read-Implies-exec, maka itu diaktifkan untuk seluruh program Anda . File rakitan yang dikompilasi oleh GCC mengatakan bahwa ia tidak memerlukan ini, dengan baris ini (Anda akan melihat ini jika Anda mengompilasinya -S):

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

Masukkan baris itu example.s, dan itu akan berfungsi untuk memberi tahu linker bahwa itu tidak membutuhkannya juga, dan program Anda kemudian akan berfungsi seperti yang diharapkan.

Joseph Sible-Reinstate Monica
sumber
13
Aduh, itu standar aneh. Saya kira toolchain ada sebelum noexec, dan menjadikan noexec sebagai default bisa merusak barang. Sekarang saya ingin tahu bagaimana assembler lain seperti NASM / YASM membuat .ofile! Tapi bagaimanapun, saya rasa ini adalah mekanisme yang gcc -zexecstackdigunakan, dan mengapa itu membuat tidak hanya stack tetapi semuanya dapat dieksekusi.
Peter Cordes
23
@ Peter - Itu sebabnya proyek seperti Botan, Crypto ++ dan OpenSSL, yang menggunakan assembler, tambahkan -Wa,--noexecstack. Saya pikir itu adalah ujung tajam yang sangat jahat. Kehilangan diam-diam dari nx-stack harus menjadi kerentanan keamanan. Orang-orang Binutil harus memperbaikinya.
jww
14
@jww Ini memang masalah keamanan, aneh bahwa tidak ada yang melaporkannya sebelumnya
Ben Hirschberg
4
+1, tetapi jawaban ini akan jauh lebih baik jika makna / logika garis .note.GNU-stack,"",@progbitsdijelaskan - saat ini buram, setara dengan "string karakter ajaib ini menyebabkan efek ini", tetapi string tersebut jelas terlihat seperti memiliki semacam semantik.
mtraceur
33

Sebagai alternatif untuk memodifikasi file rakitan Anda dengan varian direktif bagian khusus GNU, Anda dapat menambahkan -Wa,--noexecstackke baris perintah Anda untuk membuat file rakitan. Sebagai contoh, lihat bagaimana saya melakukannya di musl's configure:

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

Saya percaya setidaknya beberapa versi dentang dengan integrated-assembler mungkin mengharuskannya untuk dilewatkan sebagai --noexecstack(tanpa -Wa), jadi skrip configure Anda mungkin harus memeriksa keduanya dan melihat mana yang diterima.

Anda juga dapat menggunakan -Wl,-z,noexecstackpada waktu tautan (dalam LDFLAGS) untuk mendapatkan hasil yang sama. Kerugian dari ini adalah tidak membantu jika proyek Anda menghasilkan .afile perpustakaan statis ( ) untuk digunakan oleh perangkat lunak lain, karena Anda kemudian tidak mengontrol opsi waktu tautan ketika digunakan oleh program lain.

R .. GitHub BERHENTI MEMBANTU ICE
sumber
1
Hmm ... Saya tidak tahu bahwa Anda adalah Kaya Felker sebelum membaca posting ini. Mengapa dali nama tampilan Anda tidak?
SS Anne