mprotect и дескрипторы файлов

У меня есть эта простая программа, в которой я пытаюсь защитить блок памяти, а затем считываю файл в эту память, освобождая его, когда он segfaults.. Сначала я думал, что проблема только в том, что файл является fifo.. но теперь кажется, что даже для нормального файла это не удается,

это код:

#include <errno.h>
#include <string.h>
#include <iostream>
#include <assert.h>
#include <malloc.h>
#include <sys/mman.h>
#include <unistd.h>
#include <map>
#include <algorithm>
#include <unistd.h>
#include <signal.h>
using namespace std;

#define BUFFER_SIZE 8000
#define handle_error(msg) \
    do { cout << __LINE__ << endl ;perror(msg); exit(EXIT_FAILURE); } while (0)

volatile int fault_count = 0;
char* buffer = 0;
int size = 40960;

int my_fault_handler(void* addr, int serious) {
    if (mprotect(buffer, size,
                 PROT_READ | PROT_WRITE) == -1)
         handle_error("mprotect");
    ++fault_count;
    cout << "Segfaulting" << endl;
    return 1;
}


static void handler(int sig, siginfo_t *si, void *unused) {
    my_fault_handler(si ->si_addr, sig);
}
int main (int argc, char *argv[])
{
    long pagesize = sysconf(_SC_PAGESIZE);
    struct sigaction sa;

   sa.sa_flags = SA_SIGINFO | SA_NOCLDWAIT;
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = &handler;
    if (sigaction(SIGSEGV, &sa, NULL) == -1)
        perror("sigaction");

    cerr << "pageSize: " << pagesize << endl;

    buffer = (char*)memalign(pagesize, size);
    if (buffer == NULL)
        handle_error("memalign");
    if (mprotect(buffer, size, PROT_READ) == -1)
        handle_error("mprotect");

    FILE* file = fopen("test", "r");
    cout << "File Open" << endl;
    if (!file) {
        cout << "Failed opening file " << strerror(errno) << endl;
        return 0;
    }

    //*buffer = 0;
    while(fread(buffer, pagesize*2, 1, file)) {
       if (mprotect(buffer, size,
                    PROT_READ) == -1)
            handle_error("mprotect");
    }
    cout << ' ' << strerror(errno) << endl;

    return(0);
}

обратите внимание на //*buffer = 0;, если я сниму отметку с этой строки, программа выдаст ошибку и будет работать правильно.. кто-нибудь знает? errno - плохой адрес.

Спасибо!

ОБНОВЛЕНИЕ: Кажется, здесь был задан аналогичный вопрос: Загрузка MachineCode из файла в память и выполнение в C -- mprotect Failing, где было предложено posix_memalign, я пробовал это, и это не сработало.


person Alon    schedule 23.04.2014    source источник
comment
Можете ли вы точно объяснить, что происходит с вами, когда вы запускаете программу без изменений, и чего вы ожидаете? Для меня программа печатает размер страницы как 4096, за которым следует File Open, за которым следует Success. Это происходит как при неизменной программе, так и после раскомментирования строки *buffer = 0.   -  person user4815162342    schedule 23.04.2014
comment
у вас есть "тестовый" файл? для меня, когда я комментирую *buffer = 0; Я получаю неверный адрес errno, не segfaulting, когда файл читается   -  person Alon    schedule 23.04.2014
comment
не могли бы вы добавить командную строку компилятора, которую вы использовали?   -  person Alon    schedule 23.04.2014
comment
Я создал пустой файл test для тестирования. Вызов компилятора был таким же простым, как g++ a.c. (Мне также пришлось удалить строку #include <sigsegv.h>, так как этого заголовочного файла нет в моей системе.)   -  person user4815162342    schedule 23.04.2014
comment
да, это не нужно, не могли бы вы добавить cout внутри функции segfault, чтобы убедиться, что вы выполняете segfault в обоих сценариях? Я также использовал простой g++   -  person Alon    schedule 23.04.2014
comment
Было бы неплохо обновить тестовый код в вопросе, чтобы было понятнее, почему он терпит неудачу, поэтому мы уверены, что запускаем один и тот же код.   -  person user4815162342    schedule 23.04.2014
comment
есть.. добавил печать с ошибкой сегментации и удалил заголовок.. Я вижу, что это не удалось и для Петеша.   -  person Alon    schedule 23.04.2014


Ответы (1)


Проблема в том, что вы не проверяете ошибку в дескрипторе FILE после короткого чтения.

Система сообщит вам, что первое чтение не удалось и не активировало обработчик ошибок.

Если вы проверили ferror вне цикла (например, неаккуратно):

while(fread(buffer, pagesize*2, 1, file)) {
   if (mprotect(buffer, size,
                PROT_READ) == -1)
        handle_error("mprotect");
}
if (ferror(file) != 0) {
    cout << "Error" << endl;
}

Причина сбоя заключается в том, что базовый read дал сбой и вернул ошибку 14 (EFAULT), что не совсем то, что задокументировано при сбое чтения в этой ситуации (в нем говорится, что Buf points outside the allocated address space.)

Вы можете доверять запуску обработчика сигнала только в случае mprotect, когда рассматриваемый код выполняется в контексте пользователя, большинство вызовов system завершатся ошибкой и вернут EFAULT в случае, если буфер недействителен или не имеет правильных разрешений.

person Petesh    schedule 23.04.2014
comment
да, я распечатал, что ошибка была EFAULT и неверный адрес .. так что ваш ответ, что решения нет? буфер ДОЛЖЕН быть действительным? другого интерфейса нет? - person Alon; 23.04.2014
comment
Если вы полагаетесь на системные вызовы, то у вас нет абсолютно никакой гарантии, что ваш обработчик будет вызван, когда разрешение буфера отличается от необходимого — чаще всего он будет завершаться с ошибкой EFAULT. Если вы читаете во временный, доступный для записи буфер и memcpy по адресу, вы получите сигнал для срабатывания. - person Petesh; 23.04.2014
comment
да, но тогда я получаю дополнительную память всего буфера. - person Alon; 23.04.2014
comment
Да, но это практически единственный способ гарантировать вызов обработчика сигнала. Вы можете проверить короткое чтение, состояние ошибки и EFAULT errno, а затем инициировать изменение защиты вручную и сэкономить на накладных расходах триггера сигнала. - person Petesh; 23.04.2014
comment
Я дам вам +1, но это все равно не ответит, если вы не покажете мне документацию, что нет никаких гарантий для моих обработчиков seg :) - person Alon; 23.04.2014
comment
@Alon Обратите внимание, что использование stdio в первую очередь влечет за собой дополнительную копию из-за собственной буферизации stdio. Если вы замените fread на уровень ОС read во временном буфере, а memcpy этот буфер, вы должны получить производительность, эквивалентную stdio, но с вызовом сигнала, как и ожидалось. - person user4815162342; 23.04.2014
comment
@Кроме того, fread спецификация дает рекомендации, которые вам необходимо проверить ferror в случае неудачного чтения и проверьте errno. Хотя в нем конкретно не упоминается случай EFAULT, вы должны иметь возможность read между строк для ожидаемого поведения. - person Petesh; 23.04.2014