Is it allowed to access memory that spans the zero boundary in x86?

后端 未结 2 1971
小鲜肉
小鲜肉 2020-12-19 12:38

Is it allowed for a single access to span the bounary between 0 and 0xFFFFFF... in x861?

For example given that eax

相关标签:
2条回答
  • 2020-12-19 13:05

    This is not really a new answer but was too big for a comment. This is @prl's code converted so that it should run with the basic gnu-efipackage available on many Linux distros. File wraptest.c:

    #include <efi.h>
    #include <efiapi.h>
    #include <efilib.h>
    #include <inttypes.h>
    #include <stdint.h>
    
    uint8_t *p = (uint8_t *)0xfffffffffffffffcULL;
    
    EFI_STATUS
    EFIAPI
    efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
    {
        uint64_t cr3;
    
        InitializeLib(ImageHandle, SystemTable);
        asm("mov %%cr3, %0" : "=r"(cr3));
        uint64_t *pml4 = (uint64_t *)(cr3 & ~0xfffULL);
    
        Print(L"cr3 %lx\n", cr3);
        Print(L"pml4[0] %lx\n", pml4[0]);
        uint64_t *pdpt = (uint64_t *)(pml4[0] & ~0xfffULL);
        Print(L"pdpt[0] %lx\n", pdpt[0]);
        if (!(pdpt[0] & 1)) {
            uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, \
                              EfiBootServicesData, 1, &pdpt[0]);
            pdpt[0] |= 0x03;
            Print(L"pdpt[0] %lx\n", pdpt[0]);
        }
        uint64_t *pd = (uint64_t *)(pdpt[0] & ~0xfffULL);
        Print(L"pd[0] %lx\n", pd[0]);
        if (!(pd[0] & 1)) {
            uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, \
                              EfiBootServicesData, 1, &pd[0]);
            pd[0] |= 0x03;
            Print(L"pd[0] %lx\n", pd[0]);
        }
        if (!(pd[0] & 0x80)) {
            uint64_t *pt = (uint64_t *)(pd[0] & ~0xfffULL);
            Print(L"pt[0] %lx\n", pt[0]);
            if (!(pt[0] & 1)) {
                uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, \
                                  EfiBootServicesData, 1, &pt[0]);
                pt[0] |= 0x03;
                Print(L"pt[0] %lx\n", pt[0]);
            }
        }
    
        Print(L"[0] = %08x\n", *(uint32_t *)(p+4));
    
        Print(L"pml4[0x1ff] %lx\n", pml4[0x1ff]);
        if (pml4[0x1ff] == 0) {
            uint64_t *pt;
            uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, \
                              EfiBootServicesData, 4, &pt);
            uint64_t x = (uint64_t)pt;
    
            Print(L"pt = %lx\n", pt);
    
            pml4[0x1ff] = x | 0x3;
            pt[0x1ff] = (x + 0x1000) | 0x3;
            pt[0x3ff] = (x + 0x2000) | 0x3;
            pt[0x5ff] = (x + 0x3000) | 0x3;
    
            *(uint32_t *)p = 0xabcdabcd;
            *(uint32_t *)(p + 4) = 0x12341234;
    
            Print(L"[0] = %08x\n", *(uint32_t *)(p+4));
            Print(L"[fffffffffffc] = %08x\n", *(uint32_t *)(x + 0x3ffc));
    
            /* This write should place 0x5678 in the last 16-bit word of memory
             * and 0x5678 at the first 16-bit word in memory. If the wrapping
             * works as expected p[0] should be 0x5678ABCD and
             * p[1] should be 0x12345678 when displayed. */
            *(uint32_t *)(p + 2) = 0x56785678;
    
            Print(L"p[0] = %08x\n", ((uint32_t *)p)[0]);
            Print(L"p[1] = %08x\n", ((uint32_t *)p)[1]);
        }
    
        return 0;
    }
    

    A Makefile that should work on 64-bit Ubuntu and 64-bit Debian could look like this:

    ARCH            ?= $(shell uname -m | sed s,i[3456789]86,ia32,)
    ifneq ($(ARCH),x86_64)
    LIBDIR          = /usr/lib32
    else
    LIBDIR          = /usr/lib
    endif
    
    OBJS            = wraptest.o
    TARGET          = wraptest.efi
    
    EFIINC          = /usr/include/efi
    EFIINCS         = -I$(EFIINC) -I$(EFIINC)/$(ARCH) -I$(EFIINC)/protocol
    LIB             = $(LIBDIR)
    EFILIB          = $(LIBDIR)
    EFI_CRT_OBJS    = $(EFILIB)/crt0-efi-$(ARCH).o
    EFI_LDS         = $(EFILIB)/elf_$(ARCH)_efi.lds
    
    CFLAGS          = $(EFIINCS) -fno-stack-protector -fpic \
                      -fshort-wchar -mno-red-zone -Wall -O3
    ifeq ($(ARCH),x86_64)
      CFLAGS += -DEFI_FUNCTION_WRAPPER
    endif
    
    LDFLAGS         = -nostdlib -znocombreloc -T $(EFI_LDS) -shared \
                      -Bsymbolic -L $(EFILIB) -L $(LIB) $(EFI_CRT_OBJS)
    
    all: $(TARGET)
    
    wraptest.so: $(OBJS)
            ld $(LDFLAGS) $(OBJS) -o $@ -lefi -lgnuefi
    
    %.efi: %.so
            objcopy -j .text -j .sdata -j .data -j .dynamic \
                    -j .dynsym  -j .rel -j .rela -j .reloc \
                    --target=efi-app-$(ARCH) $^ $@
    

    The code as written will only work properly if compiled for x86-64. You can make this EFI application with the command:

    make ARCH=x86_64
    

    The resulting file should be wraptest.efi that can be copied to your EFI System Partition. The make file was based on Roderick Smith's tutorial

    0 讨论(0)
  • 2020-12-19 13:07

    I just tested with this EFI program. (And it worked, as expected.) If you want to reproduce this result, you would need an implementation of efi_printf, or another way to view the result.

    #include <stdint.h>
    #include "efi.h"
    
    uint8_t *p = (uint8_t *)0xfffffffffffffffcULL;
    
    int main()
    {
        uint64_t cr3;
        asm("mov %%cr3, %0" : "=r"(cr3));
        uint64_t *pml4 = (uint64_t *)(cr3 & ~0xfffULL);
    
        efi_printf("cr3 %lx\n", cr3);
        efi_printf("pml4[0] %lx\n", pml4[0]);
        uint64_t *pdpt = (uint64_t *)(pml4[0] & ~0xfffULL);
        efi_printf("pdpt[0] %lx\n", pdpt[0]);
        if (!(pdpt[0] & 1)) {
            pdpt[0] = (uint64_t)efi_alloc_pages(EFI_BOOT_SERVICES_DATA, 1) | 0x03;
            efi_printf("pdpt[0] %lx\n", pdpt[0]);
        }
        uint64_t *pd = (uint64_t *)(pdpt[0] & ~0xfffULL);
        efi_printf("pd[0] %lx\n", pd[0]);
        if (!(pd[0] & 1)) {
            pd[0] = (uint64_t)efi_alloc_pages(EFI_BOOT_SERVICES_DATA, 1) | 0x03;
            efi_printf("pd[0] %lx\n", pd[0]);
        }
        if (!(pd[0] & 0x80)) {
            uint64_t *pt = (uint64_t *)(pd[0] & ~0xfffULL);
            efi_printf("pt[0] %lx\n", pt[0]);
            if (!(pt[0] & 1)) {
                pt[0] = (uint64_t)efi_alloc_pages(EFI_BOOT_SERVICES_DATA, 1) | 0x03;
                efi_printf("pt[0] %lx\n", pt[0]);
            }
        }
    
        efi_printf("[0] = %08x\n", *(uint32_t *)(p+4));
    
        efi_printf("pml4[0x1ff] %lx\n", pml4[0x1ff]);
        if (pml4[0x1ff] == 0) {
    
            uint64_t *pt = (uint64_t *)efi_alloc_pages(EFI_BOOT_SERVICES_DATA, 4);
            uint64_t x = (uint64_t)pt;
    
            efi_printf("pt = %p\n", pt);
    
            pml4[0x1ff] = x | 0x3;
            pt[0x1ff] = x + 0x1000 | 0x3;
            pt[0x3ff] = x + 0x2000 | 0x3;
            pt[0x5ff] = x + 0x3000 | 0x3;
    
            *(uint32_t *)p = 0xabcdabcd;
            *(uint32_t *)(p + 4) = 0x12341234;
    
            efi_printf("[0] = %08x\n", *(uint32_t *)(p+4));
            efi_printf("[fffffffffffc] = %08x\n", *(uint32_t *)(x + 0x3ffc));
    
            *(uint32_t *)(p + 2) = 0x56785678;
    
            efi_printf("p[0] = %08x\n", ((uint32_t *)p)[0]);
            efi_printf("p[1] = %08x\n", ((uint32_t *)p)[1]);
        }
    
        return 0;
    }
    

    If it works as expected, the last 4 lines should be:

    [0] = 12341234
    [fffffffffffc] = ABCDABCD
    p[0] = 5678ABCD
    p[1] = 12345678
    

    A value of 0x56785678 is written starting in the last 16-bit word of memory and should wrap to the first 16-bit word of memory.


    Note: p needed to be a global variable, otherwise GCC changed *(p+4) into ud2

    0 讨论(0)
提交回复
热议问题