Разное поведение read () в зависимости от дескриптора файла, представляющего файл, анонимного канала или сокета при записи в недоступную для записи память - PullRequest
1 голос
/ 01 мая 2019

У меня есть следующий код:

#include <sys/mman.h>
#include <unistd.h>
#include <cstdio>

int main() {
        char *p = (char *)mmap(0, 0x3000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        munmap(p + 0x2000, 0x1000);
        p += 0x800;
        printf("%zd\n", read(0, p, 0x3000));
        return 0;
}

При компиляции и запуске с вводом, который будет записывать память, доступную для записи, я получаю различные варианты поведения в зависимости от способа подачи этого ввода:

$ python3 -c 'print("A"*0x3000)' | ./test
4096
$ python3 -c 'print("A"*0x3000)' > input.bin; ./test < input.bin
6144
$ nc.traditional -l -p 1337 -e ./test &
[1] 25855
$ python3 -c 'print("A"*0x3000)' | nc localhost 1337
-1
[1]+  Done                    nc.traditional -l -p 1337 -e ./test

Я бы ожидал, что вызов read () вернет один и тот же результат во всех случаях, но это не так. Почему у меня разное поведение?

1 Ответ

0 голосов
/ 28 мая 2019

Вывод strace из этой команды показывает, что полные 12288 (0x3000) байтов записываются за один раз:

$ strace python3 -c 'print("A"*0x3000)' | ./test
...
write(1, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 12288) = 12288
...

Использование strace в команде ./test на другом конце канала показывает, чтосчитывается только 4096 (0x1000) байтов:

$ python3 -c 'print("A"*0x3000)' | strace ./test
...
read(0, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 12288) = 4096
...

Если стандартный ввод для ./test происходит из input.bin (который содержит 12288 'A, за которыми следует новая строка), strace показывает, что 6144 (0x1800)читаются байты:

$ strace ./test < input.bin
...
read(0, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 12288) = 6144
...

Я думаю, что объяснение можно найти, только углубившись в код ядра для чтения из канала в функции pipe_read в "fs / pipe.c".Внутренне канал содержит циклический массив struct pipe_buffer, каждый из которых содержит указатель на страницу данных.Функция pipe_read проходит через эти буферы, копируя содержимое для пользователя до тех пор, пока все запрошенные данные не будут прочитаны, в канале ничего не останется (после ожидания большего количества данных от устройства записи в режиме блокировки) или не произойдет ошибка, когдакопирование данных пользователю.Причина, по которой возвращается всего 4096 байт вместо 6144 при возникновении сбоя, по-видимому, связана с этим битом кода:

        written = copy_page_to_iter(buf->page, buf->offset, chars, to);
        if (unlikely(written < chars)) {
            if (!ret)
                ret = -EFAULT;
            break;
        }
        ret += chars;
        buf->offset += chars;
        buf->len -= chars;

(приведенный выше фрагмент взят из версии ядра ядра Linux 4.19.)

Здесь chars - это объем, который нужно скопировать из текущего буфера канала, written - это объем, который был фактически скопирован, а ret - это возвращаемое значение функции, которое обычно является числом прочитанных байтов.,Начальное значение ret равно 0. written обычно устанавливается на chars, если только не произойдет ошибка адреса, в этом случае она будет меньше chars.

Ошибка возникает при копированиивторая страница из трубы.Первая страница была успешно скопирована, поэтому ret будет 4096. При копировании второй страницы в буфере пользователя возникает ошибка адреса после 6144 - 4096 = 2048 (0x800) байтов, поэтому достигается оператор break; для разрывацикла до достижения оператора ret += chars;.ret не установлено на -EFAULT, потому что оно ненулевое.Однако частичное копирование 2048 байтов со второй страницы канала в пользовательский буфер было проигнорировано, поскольку указывается возвращаемое значение функции pipe_buf.(Пропущенные данные не удаляются из канала. Позиция в канале перемещается только после успешного копирования, поэтому последующий вызов pipe_read будет продолжен с той же точки.)

Обратите внимание, что хотяread вернул только 4096, похоже, что он фактически скопировал 6144 байта в пользовательский буфер.Это может быть подтверждено проверкой содержимого буфера с p + 4096 и далее после возврата из вызова read.

Я уверен, что соответствующая часть функции pipe_read может быть переписана для правильного учетачастично скопированная часть, но я не знаю, считают ли разработчики ядра ядра ошибкой, которую стоит исправить.Например, исправление может заменить приведенный выше код чем-то вроде следующего (непроверенного) кода:

        written = copy_page_to_iter(buf->page, buf->offset, chars, to);
        ret += written;
        buf->offset += written;
        buf->len -= written;
        if (unlikely(written < chars)) {
            if (!ret)
                ret = -EFAULT;
            break;
        }

Я почти уверен, что вашей программе ./test удастся прочитать 6144 байта из канала с этимизменить код ядра.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...