3
votes

I am implementing mmap function using system call.(I am implementing mmap manually because of some reasons.)

But I am getting return value -14 (-EFAULT, I checked with GDB) whith this message:

WARN  Nar::Mmap: Memory allocation failed.

Here is function:

void *Mmap(void *Address, size_t Length, int Prot, int Flags, int Fd, off_t Offset) {
    MmapArgument ma;
    ma.Address = (unsigned long)Address;
    ma.Length = (unsigned long)Length;
    ma.Prot = (unsigned long)Prot;
    ma.Flags = (unsigned long)Flags;
    ma.Fd = (unsigned long)Fd;
    ma.Offset = (unsigned long)Offset;
    void *ptr = (void *)CallSystem(SysMmap, (uint64_t)&ma, Unused, Unused, Unused, Unused);
    int errCode = (int)ptr;
    if(errCode < 0) {
        Print("WARN  Nar::Mmap: Memory allocation failed.\n");
        return NULL;
    }
    return ptr;
}

I wrote a macro(To use like malloc() function):

#define Malloc(x) Mmap(0, x, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)

and I used like this:

Malloc(45);

I looked at man page. I couldn't find about EFAULT on mmap man page, but I found something about EFAULT on mmap2 man page.

EFAULT Problem with getting the data from user space.

I think this means something is wrong with passing struct to system call. But I believe nothing is wrong with my struct:

struct MmapArgument {
    unsigned long Address;
    unsigned long Length;
    unsigned long Prot;
    unsigned long Flags;
    unsigned long Fd;
    unsigned long Offset;
};

Maybe something is wrong with handing result value? Openning a file (which doesn't exist) with CallSystem gave me -2(-ENOENT), which is correct.

EDIT: Full source of CallSystem. open, write, close works, but mmap(or old_mmap) not works. All of the arguments were passed well.

section     .text

global CallSystem
CallSystem:
    mov rax, rdi        ;RAX
    mov rbx, rsi        ;RBX

    mov r10, rdx
    mov r11, rcx
    mov rcx, r10        ;RCX
    mov rdx, r11        ;RDX

    mov rsi, r8     ;RSI
    mov rdi, r9     ;RDI

    int 0x80
    mov rdx, 0  ;Upper 64bit
    ret                 ;Return
1
Post source code for CallSystem. - gudok
Please provide minimal but complete example code. Also, remove all macros (they're evil, consider inline functions or constants) and all unnecessary casts. Then, what happens if you run the minimal example and the equivalent using stock mmap() using strace? Oh, and please decide for one of C and C++. - Ulrich Eckhardt
There is no code to convert the format of MmapArgument to the format the system call expects. This code can only work by magic. - David Schwartz
The other substantial bug I see is that you are using int 0x80. That doesn't handle 64-bit addresses, and since one of your pointer parameters seems to be an object on the stack - the address will be incorrect (64-bit code uses stack addresses that can't be represented in 32-bits). You really need to use syscall instead of int 0x80 when developing 64-bit code (this will also involve rearranging the parameters to syscall as well). This is likely why int 0x80 is failing - it is using the wrong address for ma - Michael Petch
With int 0x80 only the lower 32-bits of your 64-bit registers are used, so they are effectively truncated. With sycall the entire 64-bit register is used. - Michael Petch

1 Answers

2
votes

It is unclear why you are calling mmap via your CallSystem function, I'll assume it is a requirement of your assignment.

The main problem with your code is that you are using int 0x80. This will only work if all the addresses passed to int 0x80 can be expressed in a 32-bit integer. That isn't the case in your code. This line:

MmapArgument ma;

places your structure on the stack. In 64-bit code the stack is at the top end of the addressable address space well beyond what can be represented in a 32-bit address. Usually the bottom of the stack is somewhere in the region of 0x00007FFFFFFFFFFF. int 0x80 only works on the bottom half of the 64-bit registers, so effectively stack based addresses get truncated, resulting in an incorrect address. To make proper 64-bit system calls it is preferable to use the syscall instruction

The 64-bit System V ABI has a section on the general mechanism for the syscall interface in section A.2.1 AMD64 Linux Kernel Conventions. It says:

  1. User-level applications use as integer registers for passing the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9. The kernel interface uses %rdi, %rsi, %rdx, %r10, %r8 and %r9.
  2. A system-call is done via the syscall instruction. The kernel destroys registers %rcx and %r11.

We can create a simplified version of your SystemCall code by placing the systemcallnum as the last parameter. As the 7th parameter it will be the first and only value passed on the stack. We can move that value from the stack into RAX to be used as the system call number. The first 6 values are passed in the registers, and with the exception of RCX we can simply keep all the registers as-is. RCX has to be moved to R10 because the 4th parameter differs between a normal function call and the Linux kernel SYSCALL convention.

Some simplified code for demonstration purposes could look like:

global CallSystem

section .text
CallSystem:

    mov rax, [rsp+8]    ; CallSystem 7th arg is 1st val passed on stack
    mov r10, rcx        ; 4th argument passed to syscall in r10
                        ; RDI, RSI, RDX, R8, R9 are passed straight through
                        ; to the sycall because they match the inputs to CallSystem
    syscall
    ret

The C++ could look like:

#include <stdlib.h>
#include <sys/mman.h>
#include <stdint.h>
#include <iostream>

using namespace std;

extern "C" uint64_t CallSystem (uint64_t arg1, uint64_t arg2,
                                uint64_t arg3, uint64_t arg4,
                                uint64_t arg5, uint64_t arg6,
                                uint64_t syscallnum);

int main()
{
        uint64_t addr;
        addr = CallSystem(static_cast<uint64_t>(NULL), 45,
                      PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS,
                      -1, 0, 0x9);
        cout << reinterpret_cast<void *>(addr) << endl;
}

In the case of mmap the syscall is 0x09. That can be found in the file asm/unistd_64.h:

#define __NR_mmap 9

The rest of the arguments are typical of the newer form of mmap. From the manpage:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

If your run strace on your executable (ie strace ./a.out) you should find a line that looks like this if it works:

mmap(NULL, 45, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fed8e7cc000

The return value will differ, but it should match what the demonstration program displays.

You should be able to adapt this code to what you are doing. This should at least be a reasonable starting point.


If you want to pass the syscallnum as the first parameter to CallSystem you will have to modify the assembly code to move all the registers so that they align properly between the function call convention and syscall conventions. I leave that as a simple exercise to the reader. Doing so will yield a lot less efficient code.