Как ядро ​​Linux узнает, сколько байтов оно должно прочитать из аргументов пути системного вызова?

Я искал в Google и обнаружил, что ядро ​​Linux использует структуру для переменных.

#define EMBEDDED_LEVELS 2
struct nameidata {
    struct path path;
    struct qstr last;
    struct path root;
    struct inode    *inode; /* path.dentry.d_inode */
    unsigned int    flags;
    unsigned    seq, m_seq;
    int     last_type;
    unsigned    depth;
    int     total_link_count;
    struct saved {
        struct path link;
        struct delayed_call done;
        const char *name;
        unsigned seq;
    } *stack, internal[EMBEDDED_LEVELS];
    struct filename *name;
    struct nameidata *saved;
    struct inode    *link_inode;
    unsigned    root_seq;
    int     dfd;
} __randomize_layout;

например, для вызова execve systeml (находится здесь https://elixir.bootlin.com/linux/latest/source/fs/exec.c)
эта функция передаст указатель имени файла другой функции как pathName и установит nameidata имя структуры к этому pathName

static int __do_execve_file(int fd, struct filename *filename,
                struct user_arg_ptr argv,
                struct user_arg_ptr envp,
                int flags, struct file *file)

мой вопрос здесь в том, как он вычисляет длину параметра, переданного этой функции из стека (например, "/bin/sh")?

(Примечание редактора: const char *pathname аргумент для execve(2) не должен указывать на стековую память. Я думаю, этот вопрос предполагает вариант использования шелл-кода, когда вы создаете путь в стеке пользовательского пространства и передаете на него указатель.)

(Я изучаю сборку, и я застрял в разделе передачи параметров для системных вызовов)


person aidin jalalvandi    schedule 26.12.2019    source источник
comment
Аргумент pathName до execve указывает на C-строку с завершением 0 / неявной длиной. Ядро использует какой-то strlen в этой памяти пользовательского пространства, как если бы вы передали его printf. Вы об этом спрашиваете? Вам не нужно знать внутреннее устройство ядра, чтобы просто использовать системные вызовы, такие как execve или open, которые принимают строки C по указателю.   -  person Peter Cordes    schedule 26.12.2019
comment
Да, я знаю, что мне не нужно знать внутреннее устройство ядра. но я первооткрыватель, и я не пойму, если что-то упущу в моей карьере программиста.   -  person aidin jalalvandi    schedule 26.12.2019
comment
и это не только для случая использования shellcdoe. на самом деле это не так. речь шла о том, как длина параметра будет вычисляться из стека   -  person aidin jalalvandi    schedule 26.12.2019
comment
Какой стек? Аргументы системного вызова передаются в регистры. Ничто неявно использует стек пользовательского пространства.   -  person Peter Cordes    schedule 26.12.2019


Ответы (2)


Linux использует строки с нулевым завершением, которые являются стандартным форматом строки для C. Конец строки помечается нулевым байтом, любые байты за пределами первого нулевого байта в строке не являются частью строки. Примечательно, что это означает, что имена файлов не могут содержать нулевой байт. (По той же причине, по которой в большинстве шелл-кодов не может быть нулевого байта, поскольку они предназначены для использования какого-то типа переполнения строкового буфера.)

На практике ядру часто не требуется знать длину имени файла, и оно использует такие функции, как strcmp, которые сравнивают строки побайтно, останавливаясь либо на первом байте, который сравнивает разные, либо на первом встреченном нулевом байте. Однако при необходимости длину строки можно вычислить с помощью функции типа strlen.

person Ross Ridge    schedule 26.12.2019
comment
Однако аргументы системного вызова должны копироваться в память, принадлежащую ядру, а не запрашиваться повторно из пользовательского пространства. Linux делает большой упор на copy_from_user доступе к памяти пользовательского пространства и, по-видимому, делает это только один раз. - person Peter Cordes; 26.12.2019
comment
strcmp и strlen были бы простыми эксплойтами, я очень надеюсь, что ядро ​​использует вместо них strncmp и strnlen. - person old_timer; 26.12.2019
comment
@PeterCordes Я не уверен, что предлагал иное. Обратите внимание, что Linux использует strncpy_from_user для копирования строк из пользовательского пространства. - person Ross Ridge; 26.12.2019
comment
Я говорил о вашей фразе На практике ядру часто не нужно знать длину имени файла. В целом это так, но этот вопрос касается того, сколько байтов ядро ​​читает из пользовательского пространства для входных данных системного вызова. Итак, strncpy_from_user - это конкретный ответ, если я правильно понимаю вопрос. - person Peter Cordes; 26.12.2019
comment
@PeterCordes Он читает байты, пока не найдет нулевой байт, который отмечает конец. Для этого не нужно знать длину строки. Имя функции, используемой для копирования имени файла, не объясняет, как ядро ​​Linux знает, сколько байтов оно должно прочитать, поэтому strncpy_from_user никоим образом не является ответом. - person Ross Ridge; 26.12.2019
comment
Хорошо, я имел в виду вместе с объяснением того, что делает функция ISO C strncpy, то есть как работают строки C неявной длины, что уже присутствует в вашем ответе. Итак, наиболее конкретный ответ заключается в том, что он копирует байты из пользовательского пространства, начиная с указанного байта из указателя arg, до завершающего 0 включительно. И все это реализовано strncpy_from_user, поэтому, если OP хочет посмотреть больше кода ядра, это имя функции, которую нужно искать. - person Peter Cordes; 26.12.2019
comment
Итак, вы имеете в виду, например, что если мы установим регистр ebx (для аргумента имени файла для execve systemcall), он начнет читать с этого адреса, пока не достигнет нулевого байта (0x90)? Я думал, что мы устанавливаем 4 байта стека (int 32-битная система) в регистре EBX. - person aidin jalalvandi; 26.12.2019
comment
@aidinjalalvandi Он будет читать, пока не найдет первый нулевой байт, то есть 0x00. - person Ross Ridge; 26.12.2019
comment
@RossRidge Да, конечно, я забыл, что 0x90 означает nop, а не нулевой терминатор. Спасибо - person aidin jalalvandi; 26.12.2019

Наконец я нашел свой ответ
вот исходный код, который я нашел

#include <stdio.h>

char hello[] = "HelloA\0AAABB";

int main( void )
{
   __asm__
   (
    "mov $hello , %eax;"
    "push %eax;"
    "call myprint;"
   );
}

void myprint(char input[])
{
    printf("%s" , input);
}

Я обнаружил, что '\ 0' - это терминатор строки. Таким образом, приведенный выше код просто выведет «HelloA». Остальная часть будет проигнорирована.

Еще одна интересная вещь, которую я обнаружил, заключается в том, что когда вы создаете массив символов в C. компилятор добавит в его конец байт для нулевого терминатора.

Итак, если мы создадим что-то вроде этого

char hello[] = "Hello"

На самом деле он компилируется так:

char hello[] = "Hello\0"

и размер вашего приветствия будет 6 вместо 5.

Наконец, при программировании на ассемблере мы должны учитывать нулевой терминатор для параметров, передаваемых в системных вызовах. пока ядро ​​linux написано на языке программирования C, мы должны принимать правила языка программирования C.


Вот результат Gdb char hello []

0x8049597 <hello>:      0x48    0x65    0x6c    0x6c    0x6f    0x41    0x00    0x41
0x804959f <hello+8>:    0x41    0x41    0x42    0x42    0x00    0x77    0x6f    0x72

0x8049597 - это начальный адрес нашей строки («HelloA \ 0AAABB»).
мы помещаем \ 0 после символа A. символ «A» равен шестнадцатеричному числу 0x41 в таблице Ascii. а \ 0 - это 0x00.
Вот почему функция printf просто покажет 6 первых символов нашей строки.

person aidin jalalvandi    schedule 26.12.2019
comment
printf не является системным вызовом. Это функция libc. Кроме того, ваш встроенный asm сломан; он изменяет регистры без использования клобберов, чтобы сообщить компилятору, что вы делаете. Вы даже оставляете ESP измененным, потому что забыли add $4, %esp. Вы могли избежать уничтожения EAX с помощью push $hello - person Peter Cordes; 26.12.2019
comment
@PeterCordes Я никогда не говорил, что printf - это системный вызов, я только что нашел в Интернете этот код о встроенной сборке в c, и я изменил сборку для вызова функции. Я использовал gcc 5.2, и это сработало. - person aidin jalalvandi; 26.12.2019
comment
Вы разместили это как ответ на вопрос о системных вызовах. О, там есть параграф о том, как это применимо к ядру Linux; Я этого не заметил, потому что это как бы похоронено посреди ваших примеров. В любом случае вы можете написать код на C, который обрабатывает строки с точки зрения указателя + явной длины. Например, класс C ++ std::string написан на простом C ++. То же самое и с ISO C fwrite и кодом ядра, который обрабатывает write(fd, buf, len) системный вызов. Просто используйте memcpy вместо strcpy - person Peter Cordes; 26.12.2019