Why when I reserve less memory in assembly .bss section it doesn't segfault until I reserve 0?

30 Views Asked by At

I was making a program that simply gets my CPU name and asks my OS to print it out. I use cpuid operation, and it works well. But at the beginning I have to reserve some memory for the string, 48 bytes for the 3 portions of info I get from eax:ebx:ecx:edx and one more for \n symbol.:

section .bss
cpuname resb 49

So, my code works perfectly fine for me as a beginner:

section .text
        global _start

_start:
        xor edi, edi ; using edi for memory address shift because every step I use 16 bytes (4 bytes*4 registers)
regcpy:        
        mov eax, 0x80000002 ; specific number for cpuid command to get my CPU name
        mov esi, edi
        shr esi, 4          ; I want to do it three times
        add eax, esi        ; up to 0x80000004 as it was described for cpuid
        cpuid
        mov [cpuname + edi], eax
        mov [cpuname + 4 + edi], ebx
        mov [cpuname + 8 + edi], ecx
        mov [cpuname + 12 + edi], edx
        add edi, 16
        cmp edi, 48         ; three iterations overall: 16, 32, 48 and when 48
        jne regcpy          ; I don't repeat it and proceed the program downwards

        mov [cpuname + 48], byte 10 ; adding \n because otherwise it's ugly

        mov rax, 1
        mov rdi, 1
        mov rsi, cpuname
        mov rdx, 49         ; asking the OS to print ASCII symbols with addresses
        syscall             ; from 'cpuname' byte up to 'cpuname+48' byte out

        mov rax, 60
        xor rbx, rbx
        syscall             ; 60 = 0x3c it's sys_exit for x86_64

The thing I don't understand happens if I try to reserve less memory in .bss section:

section .bss
cpuname resb 40

or

section .bss
cpuname resb 2

compiles and works fine, I get my output: my CPU's full name when it seems there should be a segmentation fault. But when I reserve 0,

section .bss
cpuname resb 0

it segfaults successfully. I have a vague assumption that when I use resb [number >= 0], my OS anyway reserves at least some specific amount of bytes so it doesn't segfault unless the "length" of my writing attempts reaches some specific number. Like if even I write resb 1, Linux reserves like 64, 128 or...I don't know, I'm just trying to guess. Or I'm completely wrong, aren't I? But then...when I try to do something like that in C, even 1 byte overflow leads to segfault? Please enlighten me why I can't get my program segfaulted when I want to.

I ran the program with resb 1 20000 times in a row and didn't notice even a single segfault.

UPD: YEAAAAAH, I did that, taking into account 4096 page size it all worked out:

section .bss
blank   resb 4080 ; 4080+40 > 4096
cpuname resb 1
1

There are 1 best solutions below

4
Nate Eldredge On

Virtual memory operates on pages, which on x86 are 4 Kib. A segfault occurs when you access a page which has not been mapped into your process's address space.

So accessing an "invalid" address only results in a segfault if it is not on the same page as any valid memory. Your program might be linked in such a way that the .bss section is page-aligned, so that it starts on a fresh page, and its size is an integer number of pages. Thus, if the size is nonzero, at least one page will be allocated, and accesses within 4096 bytes will not fault. If the size is zero, it might allocate 0 pages, so that the start of the .bss section is on an unmapped page, and all accesses fault. This behavior is not guaranteed, however; in some cases .bss might share a page with other program data, and then the relationship between the size and the addresses which fault would be different.

Note that regardless of whether a segfault occurs, it is still wrong to access memory locations that have not been specifically allocated for the purpose. If you write to such an address, you may be inadvertently overwriting some other data within your program. If you read it, even if it doesn't fault, the value you read is useless if you don't know what it means or who put it there.