Почему функция read () syscall блокирует передачу недопустимого указателя буфера?


Вот мой фрагмент кода read(STDIN, NULL, 10), выполненный в Linux-2.6.32.431. Я предполагал, что он вернется сразу же после просмотра исходного кода read () syscall:

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
    struct file *file;
    ssize_t ret = -EBADF;
    int fput_needed;

    file = fget_light(fd, &fput_needed);
    if (file) {
        loff_t pos = file_pos_read(file);
        ret = vfs_read(file, buf, count, &pos);
        file_pos_write(file, pos);
        fput_light(file, fput_needed);
    }

    return ret;
}

И

ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
    ssize_t ret;

    if (!(file->f_mode & FMODE_READ))
        return -EBADF;
    if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
        return -EINVAL;
    if (unlikely(!access_ok(VERIFY_WRITE, buf, count))) //I suppose it should return here
        return -EFAULT;
    ...
}
Однако он был заблокирован. После того, как я ввел несколько символов и нажал return, эта программа потребляла один символ и возвращала, в то время как остальные символы вводились в терминал.

Мой вопрос:

  1. Почему был заблокирован вызов read ()?

  2. Почему оставшиеся символы были введены в терминал.

2 2

2 ответа:

Я считаю, что access_ok не делает именно то, что подразумевает его название.

Из комментариев в arch / x86 / include/asm / uaccess.h :

/**
 * access_ok: - Checks if a user space pointer is valid
 * @type: Type of access: %VERIFY_READ or %VERIFY_WRITE.  Note that
 *        %VERIFY_WRITE is a superset of %VERIFY_READ - if it is safe
 *        to write to a block, it is always safe to read from it.
 * @addr: User space pointer to start of block to check
 * @size: Size of block to check
 *
 * Context: User context only.  This function may sleep.
 *
 * Checks if a pointer to a block of memory in user space is valid.
 *
 * Returns true (nonzero) if the memory block may be valid, false (zero)
 * if it is definitely invalid.
 *
 * Note that, depending on architecture, this function probably just
 * checks that the pointer is in the user space range - after calling
 * this function, memory access functions may still return -EFAULT.
 */

Комментарии кажутся точными; на x86, если вы проследите определение access_ok, вы найдете он просто проверяет (по существу), является ли addr + size > user_addr_max(). В частности, он возвращает "true" для нулевого указателя.

Таким образом, вы должны проследить vfs_read немного дальше, в вызов file->f_op->read(), который предположительно вызывает read функция для драйвера TTY, который предположительно находится там, где он блокирует.

(обратите внимание, что POSIX ничего не гарантирует, когда вы передаете нулевой указатель на read, поэтому я бы не советовал этого делать.)

[обновление]

Для вашего второго вопроса это та же причина, по которой эта последовательность считывает один символ, а затем передает остальные терминалу:

$ head -c 1 > /dev/null
lalala
$ alala
alala: command not found
Все, что я сделал, это ввел "лалала" в команду head. Ваша программа, вероятно, потребляя один символ входного телетайп , завершение работы (сбой), а затем остальная часть входных данных в TTY потребляется оболочкой после завершения работы программы.

Если вы проверяете read на странице руководства вы увидите, что:

EFAULT buf находится вне вашего доступного адресного пространства.

Указатель NULL все еще находится в доступном адресном пространстве всех процессов. Запись или разыменование указателя NULL приводит к неопределенное поведение, но это все еще действительный адрес.

Так что read вызов блокирует, потому что нет входных данных для чтения. Когда есть, процесс будет наиболее скорее всего, авария.