Почему происходит сбой этого кода при включенной рандомизации адресов? - PullRequest
7 голосов
/ 22 июня 2019

Я изучаю ассемблер amd64 и пытаюсь реализовать простой фильтр Unix.По неизвестной причине, даже упрощенной до минимальной версии (код ниже), она вылетает случайным образом.

Я пытался отлаживать эту программу в GNU Debugger (gdb).В конфигурации GDB по умолчанию программа работает нормально, но если я включаю рандомизацию адресов (set disable-randomization off), программа начинает аварийно завершать работу (SIGSEGV).Проблемная инструкция отмечена в листинге:

format ELF64 executable

sys_read                        =       0
sys_write                       =       1
sys_exit                        =       60

entry $
foo:
    label .inbuf   at rbp - 65536
    label .outbuf  at .inbuf - 65536
    label .endvars at .outbuf
    mov rbp, rsp

    mov rax, sys_read
    mov rdi, 0
    lea rsi, [.inbuf]
    mov rdx, 65536
    syscall

    xor ebx, ebx
    cmp eax, ebx
    jl .read_error
    jz .exit

    mov r8, rax  ; r8  - count of valid bytes in input buffer
    xor r9, r9   ; r9  - index of byte in input buffer, that is being processed.
    xor r10, r10 ; r10 - index of next free position in output buffer.

.next_byte:
    cmp r9, r8
    jg .exit
    mov al, [.inbuf + r9]
    mov [.outbuf + r10], al ;; SIGSEGV here in GDB
    inc r10
    inc r9
    jmp .next_byte

.read_error:
    mov rax, sys_exit
    mov rdi, 1
    syscall
.exit:
    mov rax, sys_write
    mov rdi, 1
    lea rsi, [.outbuf]
    mov rdx, r10
    syscall

    mov rax, sys_exit
    xor rdi, rdi
    syscall


Эта программа предназначена для чтения не более 64 КБ из стандартного ввода, сохранения его в буфере в стеке, побайтного копирования копируемых данных ввыходной буфер и записать содержимое выходного буфера в стандартный поток вывода.По сути, он должен вести себя как ограниченная версия cat.

. На моем компьютере он либо работает как задумано, либо аварийно завершает работу с SIGSEGV, с приблизительной скоростью от 1 успешного запуска до 4 сбоев.

Ответы [ 2 ]

5 голосов
/ 22 июня 2019

sub rsp, <size>, чтобы зарезервировать место в стеке перед тем, как прикасаться к нему, если вы используете более 128 байтов ниже RSP.


При сбое посмотрите на память вашего процессакарта.Возможно, вы используете память до уровня ниже RSP, чтобы ядро ​​не увеличивало отображение стека, и, таким образом, это просто обычный доступ к неотображенной странице = ошибка неверной страницы => ядро ​​предоставляет SIGSEGV.

(только для ABIопределяет 128-байтовую красную зону, но на практике единственное, что может заглушить эту память, - это обработчик сигналов (который вы не установили) или GDB, выполняющий print some_func() с использованием стека вашей программы для вызова функции в вашей программе.)

Обычно Linux очень хочет увеличить отображение стека, не затрагивая промежуточные страницы, но, очевидно, проверяет значение RSP.Обычно вы перемещаете RSP вместо того, чтобы просто использовать память намного ниже указателя стека (потому что нет гарантии, что это безопасно).См. Как выделяется стековая память при использовании инструкций «push» или «sub» x86?

Еще один дубликат: Какое исключение может быть сгенерировано при вычитании регистра ESP или RSP?(увеличение стека) , где было достаточно использования sub rsp, 5555555 до касания новой стековой памяти.

Стек ASLR может запускать RSP в разных местах относительно границы страницы , поэтому вы можетепросто сходит с рук иногда. Linux изначально отображает 132 КБ стекового пространства , и это включает пространство для среды и аргументы в стеке при входе в _start.Ваш 128 кБ очень близок к этому, поэтому вполне вероятно, что он иногда работает случайным образом.


И, кстати, есть нулевая причина для фактического копирования памяти в пространстве пользователя, особенно не 1 байт за раз.Просто передайте тот же адрес на write.

Или, по крайней мере, отфильтруйте на месте, если это возможно, чтобы уменьшить объем кэша.

Кроме того, обычный способ загрузки байта - это movzx eax, byte [mem].Используйте mov al, [mem] только если вы хотите объединить со старым значением RAX.На некоторых процессорах mov до al имеет ложную зависимость от старого значения, которое можно разорвать, записав полный регистр.


И, кстати, если ваша программа всегда использует это пространство, вы можете статически распределить его в BSS.Это делает возможным более эффективную индексированную адресацию, если вы решите собрать исполняемый файл, зависящий от позиции (не PIE).

4 голосов
/ 22 июня 2019

Красная зона в amd64 имеет длину всего 128 байт, но вы используете 131072 байт ниже rsp.Переместите указатель стека вниз, чтобы охватить буферы, которые вы хотите сохранить в стеке.

...