3

I am trying to create an x86_64 assembly program that displays "SIGTERM received" whenever the SIGTERM signal is sent. My application is using Linux syscalls directly:

%define sys_write        0x01
%define sys_rt_sigaction 0x0d
%define sys_pause        0x22
%define sys_exit         0x3c

%define SIGTERM 0x0f

%define STDOUT 0x01


; Definition of sigaction struct for sys_rt_sigaction
struc sigaction
    .sa_handler  resq 1
    .sa_flags    resq 1
    .sa_restorer resq 1
    .sa_mask     resq 1
endstruc


section .data

    ; Message shown when a syscall fails
    error_msg     db  'syscall error', 0x0a
    error_msg_len equ $ - error_msg

    ; Message shown when SIGTERM is received
    sigterm_msg     db  'SIGTERM received', 0x0a
    sigterm_msg_len equ $ - sigterm_msg


section .bss

    act resb sigaction_size
    val resd 1


section .text
global _start

_start:

    ; Initialize act
    lea rax, [handler]
    mov [act + sigaction.sa_handler], rax

    ; Set the handler
    mov rax, sys_rt_sigaction
    mov rdi, SIGTERM
    lea rsi, [act]
    mov rdx, 0x00
    mov r10, 0x08
    syscall

    ; Ensure the syscall succeeded
    cmp rax, 0
    jne error

    ; Pause until a signal is received
    mov rax, sys_pause
    syscall

    ; Upon success, jump to exit
    jmp exit

error:

    ; Display an error message
    mov rax, sys_write
    mov rdi, STDOUT
    mov rsi, error_msg
    mov rdx, error_msg_len
    syscall

    ; Set the return value to one
    mov dword [val], 0x01

exit:

    ; Terminate the application gracefully
    mov rax, sys_exit
    mov rdi, [val]
    syscall

handler:

    ; Display a message
    mov rax, sys_write
    mov rdi, STDOUT
    mov rsi, sigterm_msg
    mov rdx, sigterm_msg_len
    syscall

    ret

When I run the application, it hangs (as expected) at the sys_pause syscall but when I send the SIGTERM signal, it crashes with a segmentation fault.

So I loaded the application into GDB to figure out what was happening:

(gdb) break _start
Breakpoint 1 at 0x4000b0
(gdb) run
Starting program: [...] 

Breakpoint 1, 0x00000000004000b0 in _start ()
(gdb) info proc
process 9639
(gdb) continue
Continuing.

The GDB session hung and I then opened another terminal and ran kill SIGTERM 9639. This resulted in the following output:

Program received signal SIGTERM, Terminated.
0x00000000004000ec in _start ()

I then ran:

(gdb) disas _start
Dump of assembler code for function _start:
   0x00000000004000b0 <+0>:     lea    0x400123,%rax
   0x00000000004000b8 <+8>:     mov    %rax,0x600160
   0x00000000004000c0 <+16>:    mov    $0xd,%eax
   0x00000000004000c5 <+21>:    mov    $0xf,%edi
   0x00000000004000ca <+26>:    lea    0x600160,%rsi
   0x00000000004000d2 <+34>:    mov    $0x0,%edx
   0x00000000004000d7 <+39>:    mov    $0x8,%r10d
   0x00000000004000dd <+45>:    syscall 
   0x00000000004000df <+47>:    cmp    $0x0,%rax
   0x00000000004000e3 <+51>:    jne    0x4000ee <error>
   0x00000000004000e5 <+53>:    mov    $0x22,%eax
   0x00000000004000ea <+58>:    syscall 
=> 0x00000000004000ec <+60>:    jmp    0x400114 <exit>
End of assembler dump.

Then I continued the application:

(gdb) continue
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x00000000004000ec in _start ()

The signal handler is never invoked and the application has crashed.

What am I doing wrong?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Nathan Osman
  • 71,149
  • 71
  • 256
  • 361
  • 2
    I believe [this](https://stackoverflow.com/a/42356406/547981) applies to you too. PS: don't mess with signals from asm ;) – Jester Sep 28 '17 at 23:03
  • 1
    Did you try running this under `strace` to see system call args and return values? Or in gdb, `print $rax` after `syscall` to see if it's `-EFAULT` or something. See debugging tips at the bottom of the [x86 tag wiki](https://stackoverflow.com/tags/x86/info). – Peter Cordes Sep 28 '17 at 23:29
  • @PeterCordes doesn't work unfortunately. The syscall returns no error, it's only when trying to handle the signal that the kernel faults if the restorer is not set. I consider that a kernel bug. – Jester Sep 28 '17 at 23:31
  • Oh nvm, GDB shows `RIP` pointing to the instruction after `syscall`, but `SIGSEGV` is delivered first. That may be bogus on GDB's part (or on Linux's part for showing GDB the wrong RIP, not in the signal handler or restorer or something). Was half way through editing this into my previous comment when you replied, @jester :P – Peter Cordes Sep 28 '17 at 23:33
  • @Nathan, is this a duplicate of the question Jester linked? It looks like it, just using different asm syntax. – Peter Cordes Sep 28 '17 at 23:35
  • 1
    @PeterCordes I set `sa_restorer` and added the `SA_RESTORER` flag and it invokes the handler now. It still crashes afterwards, however. I'll continue debugging. – Nathan Osman Sep 28 '17 at 23:37

2 Answers2

3

There were two corrections that needed to be made before the application worked correctly.


sa_restorer

Jester pointed me to this answer which mentioned that the kernel requires the sa_restorer member of sigaction to be filled in.

Fixing this required defining SA_RESTORER:

%define SA_RESTORER 0x04000000

...and initializing the sa_restorer and sa_flags members:

mov [act + sigaction.sa_flags], dword SA_RESTORER
lea rax, [restorer]
mov [act + sigaction.sa_restorer], rax

I then added an empty stub for the restorer function:

restorer:

    ret

At this point, the handler was invoked without error but the application was still crashing...


sys_rt_sigreturn

Apparently, the sa_restorer function needs to invoke the sys_rt_sigreturn syscall. This required defining sys_rt_sigreturn:

%define sys_rt_sigreturn 0x0f

The restorer function was then modified:

restorer:

    ; return from the signal handler
    mov rax, sys_rt_sigreturn
    syscall

At this point, the application ran without crashing.

Nathan Osman
  • 71,149
  • 71
  • 256
  • 361
3

Here's the entire working and corrected program by Nathan Osman. Answering here because such an example doesn't seem to exist anywhere else on the internet.

%define sys_write        0x01
%define sys_rt_sigaction 0x0d
%define sys_pause        0x22
%define sys_exit         0x3c
%define sys_rt_sigreturn 0x0f
%define SIGTERM 0x0f
%define SIGINT 0x02
%define STDOUT 0x01
%define SA_RESTORER 0x04000000

; Definition of sigaction struct for sys_rt_sigaction
struc sigaction
    .sa_handler  resq 1
    .sa_flags    resq 1
    .sa_restorer resq 1
    .sa_mask     resq 1
endstruc

section .data
    ; Message shown when a syscall fails
    error_msg     db  'syscall error', 0x0a
    error_msg_len equ $ - error_msg
    ; Message shown when SIGTERM is received
    sigterm_msg     db  'SIGTERM received', 0x0a
    sigterm_msg_len equ $ - sigterm_msg

section .bss
    act resb sigaction_size
    val resd 1

section .text
global _start
_start:
    ; Initialize act
    mov qword [act + sigaction.sa_handler], handler
    mov [act + sigaction.sa_flags], dword SA_RESTORER
    mov qword [act + sigaction.sa_restorer], restorer

    ; Set the handler
    mov rax, sys_rt_sigaction
    ;mov rdi, SIGINT
    mov rdi, SIGTERM
    lea rsi, [act]
    mov rdx, 0x00
    mov r10, 0x08
    syscall

    ; Ensure the syscall succeeded
    cmp rax, 0
    jne error

    ; Pause until a signal is received
    mov rax, sys_pause
    syscall

    ; Upon success, jump to exit
    jmp exit

error:
    ; Display an error message
    mov rax, sys_write
    mov rdi, STDOUT
    mov rsi, error_msg
    mov rdx, error_msg_len
    syscall

    ; Set the return value to one
    mov dword [val], 0x01

exit:
    ; Terminate the application gracefully
    mov rax, sys_exit
    mov rdi, [val]
    syscall

handler:
    ; Display a message
    mov rax, sys_write
    mov rdi, STDOUT
    mov rsi, sigterm_msg
    mov rdx, sigterm_msg_len
    syscall
    ret

restorer:
    ; return from the signal handler
    mov rax, sys_rt_sigreturn
    syscall

For easier testing, you can replace SIGTERM with SIGINT by uncommenting the first list here and commenting the second.

    ;mov rdi, SIGINT
    mov rdi, SIGTERM

Save it to signal.asm and compile and run with

nasm -f elf64 signal.asm -o signal.o && ld signal.o && ./a.out

In the SIGINT version, pressing Control-C to interrupt the program should print message 'SIGTERM received' (its also possible to change that to make it more accurate). In the SIGTERM version, instead run it in gdb

$ gdb ./a.out
[GNU gdb...]
(gdb) r
Starting program: /path/a.out 

And now press Control-C

^C
Program received signal SIGINT, Interrupt.
0x0000000000400107 in _start ()
(gdb) signal SIGTERM
Continuing with signal SIGTERM.
SIGTERM received
[Inferior 1 (process 22742) exited normally]
(gdb)

The 'SIGTERM received' message is printed as expected.

asrp
  • 31
  • 5