3

Assembly x86 (32-bit), call to NR_creat (8) Corrupts Filename Storage

All, I have pulled my hair out trying to determine how the reserved storage I have for the filename is being corrupted by the call to file creat (NR_creat 8). The assembler is nasm and the code is 32-bit compiled and running on an x86_64 box. The routine is a simple bit of code that takes the filename from the program arguments argv[1] and then uses the name to create a file with octal permission of 0644. The file is written to and then the program exits. The file operations work, the problem is that I lose the filename stored in fnbuf upon calling file creat.

The reserved storage named fnbuf is 32-bytes and is filled with argv[1] in a simple mov [fnbuf], ebx operation. The information in fnbuf is fine up until the file is created, thereafter, the information in fnbuf is corrupted and the address is changed. (all other stored information is fine). To retain the filename, I have ended up pushing it onto the stack (which works fine following the file create) I do not understand why the information in fnbuf is corrupted and need help.

The relevant code is as follows along with brief gdb output (the full code follows at the end).

section    .data
    buflen equ 32

section .bss
    fd_out  resb 1
    fd_in   resb 1
    fnbuf   resb buflen

section    .text

        global  _start
_start:                         ; linker entry point

        ; get the file_name from stdin (argv[1])
        add     esp, 8          ; skip over argc and argv[0] on stack (2 ptr = +8)
        pop     ebx             ; pop argv[1] to ebx
        test    ebx, ebx        ; test if null, jump
        jz      noarg           ; (if pop to ecx, use jecxz - no test required)
        mov     [fnbuf], ebx    ; save the filename in fnbuf (FIXME)
        push    ebx             ; save on stack since fnbuf is getting whacked

        ; output fnbuf to stdout (fnbuf is fine here)
        mov     edi, [fnbuf]    ; load string in edi for length calc & output
        call    strprn          ; calc length and output to stdout
        call    newln           ; output newline to stdout

        ; create the file  (fnbuf is corrupted by this call)
        mov     eax, 8          ; system call number (sys_creat)
        mov     ebx, [fnbuf]    ; set ebx to filename (fine here)
        mov     ecx, 0420       ; 644 octal -rw-r--r--
        int     0x80            ; call kernel
        jc      errcf           ; if carry flag non-zero, jmp errcf
        mov     [fd_out], eax   ; save file descriptor in fd_out

        ; write msg to file
        mov     edi, msg        ; msg address to edi for length
        call    strsz           ; calc length of message to write (ret in edx)
        mov     eax, 4          ; system call number (sys_write)
        mov     ebx, [fd_out]   ; file descriptor
        mov     ecx, msg        ; message to write
        int     0x80            ; call kernel

The code is built with the following and provides the following output using [fnbuf] to write the filename to stdout before the file creat call, but afterward, must pop the saved argv[1] from the stack to output the filename following file creat. The use of [fnbuf] works fine as well:

nasm -f elf -o ./obj/filecwr_32.o filecwr_32.asm -g
ld -m elf_i386 -o ./bin/filecwr_32 ./obj/filecwr_32.o

$ ./bin/filecwr_32 newfile.txt
newfile.txt
File write complete.
newfile.txt

$ cat newfile.txt
To whom it may concern, this information was written to the file

Stepping through the program with gdb shows the corruption occurs at the kernel call for file creat:

gdb ./bin/filecwr_32
(gdb) set args newfile.txt
(gdb) break 1
Breakpoint 1 at 0x8048080: file filecwr_32.asm, line 1.
(gdb) run
(gdb) watch fnbuf
Hardware watchpoint 2: fnbuf
(gdb) step
Single stepping until exit from function strsz,
which has no line number information.
0x08048095 in strprn ()
(gdb)
Single stepping until exit from function strprn,
which has no line number information.
newfile.txt0x080480f2 in _start ()
(gdb) x/s fnbuf
0xffffd2fd:     "newfile.txt"
(gdb) step
...
Hardware watchpoint 2: fnbuf

Old value = -11523
New value = -11776
0x08048120 in _start ()
(gdb) x/s fnbuf
0xffffd200:     "\376\336\377\377\023\337\377\377\036\337\377\377<\337\377\377\227\337\377\377"
...
[Inferior 1 (process 30000) exited normally]
(gdb) quit

Looking at the gdb output above, the address reported for fnbuf has changed? It was originally at 0xffffd2fd, but then is reported at 0xffffd200 -- some 253 bytes further down the stack. This is bewildering and where I'm stuck. It is almost like one of the segment addresses is shifter around, but then I would expect the rest of the information to get corrupted as well. The other thoughts I had was that somehow fnbuf was not being explicitly NUL-terminated. I've set it to NUL and the problem and the problem persists. Other than that, I can't think of anything other than a stray x86 execution on x86_64 issue, but that seems like a stretch.

The complete code listing:

section    .data
    msg db 'To whom it may concern, this information was written to the file', 0xa, 0
    msg_done db 'File write complete.', 0xa, 0
    msg_noarg db 'No argument available for filename.', 0xa, 0
    msg_create_fail db 'File create failed.', 0xa, 0

    buflen equ 32
    nwln db 0xa

section .bss
    fd_out  resb 1
    fd_in   resb 1
    flen    resb 1
    fnbuf   resb buflen

section    .text
            global  _start

    ; szstr computes the length of a string.
    ; edi - string address
    ; edx - contains string length (returned)
    strsz:
            xor     ecx, ecx        ; zero rcx
            not     ecx             ; set rcx = -1 (uses bitwise id: ~x = -x-1)
            xor     al,al           ; zero the al register (initialize to NUL)
            cld                     ; clear the direction flag
            repnz   scasb           ; get the string length (dec ecx through NUL)
            not     ecx             ; rev all bits of negative -> absolute value
            dec     ecx             ; -1 to skip the null-term, ecx contains length
            mov     edx, ecx        ; size returned in edx, ready to call write
            ret

    ; strprn writes a string to the file descriptor.
    ; edi - string address
    ; edx - contains string length
    strprn:
            push    edi             ; push string address onto stack
            call    strsz           ; call strsz to get length
            pop     ecx             ; pop string to ecx esi (source index)
            mov     eax, 0x4        ; write/stdout number in eax (sys_write 4)
            mov     ebx, 0x1        ; set destination index to ebx (stdout 1)
            int     0x80            ; call kernel
            ret

    ; newln writes a newline to the file descriptor.
    newln:
            mov     ecx, nwln       ; set string index in ecx
            mov     ebx, 0x1        ; set destination index to (stdout 1)
            mov     edx, 0x1        ; set length of string in edx
            mov     eax, 0x4        ; mov write syscall number (4) to eax
            int     0x80            ; call kernel
            ret

    ; error function for no argument
    noarg:
            mov     edi, msg_noarg  ; error msg to edi for length calc
            call    strprn          ; calc length and output to stdout
            jmp     exit

    ; error on fail to create file
    errcf:
            mov     edi, msg_create_fail ; error msg to edi for length calc
            call    strprn          ; calc length and output to stdout
            jmp     exit

    _start:                         ; linker entry point

            ; get the file_name from stdin (argv[1])
            add     esp, 8          ; skip over argc and argv[0] on stack (2 ptr = +8)
            pop     ebx             ; pop argv[1] to ebx
            test    ebx, ebx        ; test if null, jump
            jz      noarg           ; (if pop to ecx, use jecxz - no test required)
            mov     [fnbuf], ebx    ; save the filename in fnbuf (FIXME)
            push    ebx             ; save on stack since fnbuf is getting whacked

            ; output fnbuf to stdout (fnbuf is fine here)
            mov     edi, [fnbuf]    ; load string in edi for length calc & output
            call    strprn          ; calc length and output to stdout
            call    newln           ; output newline to stdout

            ; create the file  (fnbuf is corrupted by this call)
            mov     eax, 8          ; system call number (sys_creat)
            mov     ebx, [fnbuf]    ; set ebx to filename (fine here)
            mov     ecx, 0420       ; 644 octal -rw-r--r--
            int     0x80            ; call kernel
            jc      errcf           ; if carry flag non-zero, jmp errcf
            mov     [fd_out], eax   ; save file descriptor in fd_out

            ; write msg to file
            mov     edi, msg        ; msg address to edi for length
            call    strsz           ; calc length of message to write (ret in edx)
            mov     eax, 4          ; system call number (sys_write)
            mov     ebx, [fd_out]   ; file descriptor
            mov     ecx, msg        ; message to write
            int     0x80            ; call kernel

            ; close the file
            mov     eax, 6          ; set eax sys_close
            mov     ebx, [fd_out]   ; file descriptor in ebx 
            int     0x80

            ; print write done to stdout
            mov     edi, msg_done   ; msg_done in ecx
            call    strprn          ; calc length and output to stdout

            ; print file name to stdout
            ; mov     edi, [fnbuf]    ; fnbuf corrupted? Segment smashed?
            pop     edi             ; pop original filename from stack
            push    edi             ; save another copy since fnbuf is messed up
            call    strprn          ; calc length and output to stdout
            call    newln           ; output newline
            jmp     exit

    exit:
            xor     ebx, ebx        ; set exit code to 0
            mov     eax,1           ; system call number (sys_exit)
            int     0x80            ; call kernel

I'm at a loss as to what has caused the corruption of fnbuf. Moreover, what could be affecting that address alone while everthing else seems to be working as intended. Any help will be appreciated.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85

1 Answers1

1

First, you are messsing up a pointer and a buffer:

fnbuf   resb buflen

allocates "buflen" number of bytes (32) which you might want to use as a buffer, but

mov     [fnbuf], ebx    ; save the filename in fnbuf (FIXME)

stores an adress (a pointer) contained in ebx into the first four bytes of fnbuf - it does not copy the filename itself or anything else to the buffer, just the pointer to the filename. Dumping your .bss memory area gives this output afterwards (note that fd_out is the first address of your .bss area):

(gdb) x/32 0x80491ec
0x80491ec <fd_out>: 0x00    0x00    0x00    0xac    0xd2    0xff    0xff    0x00
                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                             Pointer retrieved from EBX

But, the real issue is where you store the file descriptor into fd_out:

mov     [fd_out], eax   ; save file descriptor in fd_out

This writes four (!!) bytes from eax to the memory starting at fd_out. Dumping the same memory afterwards results in

                                          Destroyed!
(gdb) x/32 0x80491ec                        ****
0x80491ec <fd_out>: 0x03    0x00    0x00    0x00    0xd2    0xff    0xff    0x00
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                     Four bytes written by mov

As you see, this mov destroys the first byte of your pointer - it is set to 0x00 which results in the modified value you observed.

Andreas Fester
  • 36,091
  • 7
  • 95
  • 123
  • Andreas, thanks. So for storing `argv[1]` in `fnbuf`, I'll need to use a `rep movsb` with `argv[1]` in `si` and `fnbuf` in `di`? – David C. Rankin Oct 10 '14 at 14:16
  • Essentially yes - do not forget to properly setup ECX and the [direction flag](http://stackoverflow.com/questions/10380076/direction-flag-in-x86) – Andreas Fester Oct 10 '14 at 18:53