Memory layout

From Nintendo Switch Brew
Revision as of 04:27, 23 August 2017 by Qlutoo (talk | contribs) (→‎2.0.0)
Jump to navigation Jump to search

Userspace

The userspace virtual address space can be either 32 or 36 bits. [2.0.0+] introduced support for 38 bit address spaces.

There are two regions randomized and enforced by the kernel, each one with upper bits random and 2MB-aligned:

The main binary is placed at an address that is provided to the kernel by Loader via SVC#svcCreateProcess.

Typically on 2.0.0+ systems, the main binary region has randomness in bits 37-21.

For the stack mapping region, the userland randomizes a page-offset where to start inside the region. This adds some additional entropy.

Binaries mapped by RO are mapped randomly everywhere in the entire address space. The base address for each NRO has all bits randomized and are 4K-aligned. This means that typically, on 2.0.0+ systems, bits 37-12 of the NRO base address are random.

For all binaries(main area / NROs), the R-- section is always located immediately after R-X. The RW- section is always located immediately after the R-- section. Hence, there's no extra randomization / guard-pages for these sections.

On version 1.0.0, the initial binaries loaded into memory by the kernel always have the upper 32-bits as all-zero, so there are 6 fewer bits of layout randomization.

ASLR Implementation

1.0.0

if (AddressSpaceType == 2) {
  BaseAddr = 0x80000000; // 64-bit
  RandomMax = 0x6400;
}
else {
  BaseAddr = 0x40000000; // 32-bit
  RandomMax = 0x200;
}

if (AddressSpaceType == 4) {
  MapRegionSize = 0;
  HeapRegionSize = 0x80000000;
}
else {
  MapRegionSize = 0x40000000;
  HeapRegionSize = 0x40000000;
}

if (EnableAslr) {
  rnd0 = GetRandomRange(0, RandomMax) << 21;
  rnd1 = GetRandomRange(0, RandomMax) << 21;
}
else {
  rnd0 = rnd1 = 0;
}

this->MapBaseAddr = BaseAddr + min(rnd0, rnd1)
this->HeapRegionBaseAddr = this->MapBaseAddr + MapRegionSize + max(rnd0, rnd1) - min(rnd0, rnd1)

Kernel

For more details, see #Notes. Here comes a summary.

PXN bit is set in the MMU descriptor for userland code pages. This means that userland code pages are not executable in kernel mode (this is equivalent to SMEP on x86).

For userland pages, the kernel has same access as userland (either both are read-only or both are read-write). It does not have SMAP. The previous rule has one exception: pages that are mapped unreadable in usermode are still forced readable from kernelmode.

As of 2.0.0 KASLR is not used.

1.0.0

Cores Virtual Physical Size Attributes Permissions Description
All 0xFFFFFFFE00000000-... 0x80000000 ... 0x60000000000709 RW- Raw DRAM access

2.0.0

Cores Virtual Physical Size Attributes Permissions Description
All 0xFFFFFFF7FFC00000-0xFFFFFFF7FFC62FFF 0x800A0000 0x63000 0x78B R-X Kernel .text
All 0xFFFFFFF7FFC63000-0xFFFFFFF7FFC65FFF 0x80103000 0x3000 0x6000000000078B R-- Kernel .rodata
All 0xFFFFFFF7FFC66000-0xFFFFFFF7FFC6EFFF 0x80106000 0x9000 0x6000000000070B RW- Kernel .data+.bss
All 0xFFFFFFF7FFDC0000-0xFFFFFFF7FFDC0FFF 0x60006000 0x1000 0x60000000000607 RW- Clock and Reset
All 0xFFFFFFF7FFDC2000-0xFFFFFFF7FFDC2FFF 0x7001D000 0x1000 0x60000000000607 RW- MC1
All 0xFFFFFFF7FFDC4000-0xFFFFFFF7FFDC4FFF 0x7001C000 0x1000 0x60000000000607 RW- MC0
All 0xFFFFFFF7FFDC6000-0xFFFFFFF7FFDC6FFF 0x70019000 0x1000 0x60000000000607 RW- MC
All 0xFFFFFFF7FFDC8000-0xFFFFFFF7FFDC8FFF 0x70006000 0x1000 0x60000000000607 RW- UART-A
All 0xFFFFFFF7FFDCA000-0xFFFFFFF7FFDCAFFF 0x80060000 0x2000 0x6000000000070B RW-
All 0xFFFFFFF7FFDCE000-0xFFFFFFF7FFDCEFFF 0x80068000 0x2000 0x6000000000070B RW-
All 0xFFFFFFF7FFDD2000-0xFFFFFFF7FFDD2FFF 0x80070000 0x1000 0x6000000000070B RW-
All 0xFFFFFFF7FFDD4000-0xFFFFFFF7FFDD4FFF 0x80062000 0x2000 0x6000000000070B RW-
All 0xFFFFFFF7FFDD8000-0xFFFFFFF7FFDD8FFF 0x8006A000 0x2000 0x6000000000070B RW-
All 0xFFFFFFF7FFDDC000-0xFFFFFFF7FFDDCFFF 0x80071000 0x1000 0x6000000000070B RW-
All 0xFFFFFFF7FFDDE000-0xFFFFFFF7FFDDEFFF 0x80064000 0x2000 0x6000000000070B RW-
All 0xFFFFFFF7FFDE2000-0xFFFFFFF7FFDE2FFF 0x8006C000 0x2000 0x6000000000070B RW-
All 0xFFFFFFF7FFDE6000-0xFFFFFFF7FFDE6FFF 0x80072000 0x1000 0x6000000000070B RW-
All 0xFFFFFFF7FFDE8000-0xFFFFFFF7FFDE8FFF 0x80066000 0x2000 0x6000000000070B RW-
All 0xFFFFFFF7FFDEC000-0xFFFFFFF7FFDECFFF 0x8006E000 0x2000 0x6000000000070B RW-
All 0xFFFFFFF7FFDF0000-0xFFFFFFF7FFDF0FFF 0x80073000 0x1000 0x6000000000070B RW-
All 0xFFFFFFF7FFDFB000-0xFFFFFFF7FFDFBFFF 0x50041000 0x1000 0x60000000000607 RW- ARM Interrupt Distributor
All 0xFFFFFFF7FFDFD000-0xFFFFFFF7FFDFDFFF 0x50042000 0x1000 0x60000000000607 RW- Interrupt Controller Physical CPU interface
All 0xFFFFFFF800000000-... 0x80000000 ... 0x60000000000709 RW- Raw DRAM access

The rest are are mapped to core-specific physaddrs, each one is 0x1000-bytes. Descriptor ORR-value = 0x6000000000070B.

Vmem Physmem
0xFFFFFFF7FFDF7000 <physaddr from vmem 0xFFFFFFF7FFDF6000> + 0x1000
0xFFFFFFF7FFDF3000 <physaddr from vmem 0xFFFFFFF7FFDF2000> + 0x1000
0xFFFFFFF7FFDF6000 0x800XX000
0xFFFFFFF7FFDF2000 0x800XX000
0xFFFFFFF7FFDFF000 0x800XX000
0xFFFFFFF7FFDF9000 0x800XX000

Secure Monitor

2.0.0

Vmem Physmem Size Descriptor ORR-value Permissions Description
0x7C010000 0x7C010000 0x10000 0x300 TZRAM
0x40020000 0x40020000 0x20000 0x300 iRAM-C
0x1F0080000 0x50041000 0x1000 0x40000000000304 ARM Interrupt Distributor
0x1F0082000 0x50042000 0x2000 0x40000000000304 Interrupt Controller Physical CPU interface
0x1F0085000 0x70006000 0x1000 0x40000000000324 UART-A
0x1F0087000 0x60006000 0x1000 0x40000000000324 Clock and Reset
0x1F0089000 0x7000E000 0x1000 0x40000000000304 RTC
0x1F008B000 0x60005000 0x1000 0x40000000000304 TMR
0x1F008D000 0x6000C000 0x1000 0x40000000000304 System Registers
0x1F008F000 0x70012000 0x2000 0x40000000000304 SE
0x1F0092000 0x700F0000 0x1000 0x40000000000304 SYSCTR0
0x1F0094000 0x70019000 0x1000 0x40000000000304 MC
0x1F0096000 0x7000F000 0x1000 0x40000000000304 FUSE (0x7000F800)
0x1F0098000 0x70000000 0x4000 0x40000000000304 MISC
0x1F009D000 0x60007000 0x1000 0x40000000000304 Flow Controller
0x1F009F000 0x40002000 0x1000 0x40000000000304 iRAM-A
0x1F00A1000 0x7000D000 0x1000 0x40000000000304 I2C5 - SPI 2B-6
0x1F00A3000 0x6000D000 0x1000 0x40000000000304 GPIO-1 - GPIO-8
0x1F00A5000 0x7000C000 0x1000 0x40000000000304 I2C-I2C4
0x1F00A7000 0x6000F000 0x1000 0x40000000000304 Exception vectors
0x1F0180000 0x40020000 0x10000 0x40000000000324 iRAM-C
0x1F0190000 0x40003000 0x1000 0x40000000000324 iRAM-A
0x1F01A0000 0x7C010000 0x10000 0x40000000000380 TZRAM
0x1F01C3000 0x80010000 0x10000 0x40000000000324 EMEM
0x1F01C2000 0x8000F000 0x1000 0x40000000000324 EMEM
0x1F01E0000 0x7C013000 0xB000 0x300 TZRAM (Secure Monitor)
0x1F01F0000 0x7C01E000 0x2000 0x300 TZRAM (Secure Monitor init)
0x1F01F6000 0x7C01E000 0x1000 0x40000000000300 TZRAM
0x1F01F8000 0x7C01F000 0x1000 0x40000000000300 TZRAM
0x1F01FA000 0x7C010000 0x1000 0x300 TZRAM (Secure Monitor exception vectors)
0x1F01FC000 0x7C011000 0x1000 0x40000000000300 TZRAM
0x1F01FE000 0x7C012000 0x1000 0x40000000000300 TZRAM

Notes

2.0.0

 Granule size for TTBR0*_EL1 is 4KB.
 TTBR0_EL1 vmem starts at vaddr 0x0.
 vmem end-addr for TTBR1_EL1 is 0xffffffffffffffff. vmem start-addr for TTBR1_EL1 is 0xFFFFFFF000000000.
 T0SZ = 31. Hence, bit-size of the TTBR0*_EL1 vmem region is 33. (0x0000000200000000)
 T1SZ = 28. Hence, bit-size of the TTBR1*_EL1 vmem region is 36. (0x0000001000000000)
 
 Note: ARM config for TTBR0 is presumably configured for userland later.
 
 See arm-doc for "Table D4-25 Translation table entry addresses when using the 4KB translation granule".
 
 See arm-doc for "Overview of VMSAv8-64 address translation using the 4KB translation granule".
 
 See arm-doc for "Table D4-11 TCR.TnSZ values and IA ranges, 4K granule with no concatenation of tables".
 Both TTBR*_EL1 use "Initial lookup level" 1. Therefore, the TTBR*_EL1 tables are level1.
 
 Due to T*SZ, Stage1/Stage2 translation for the initial table(level1) are the same, except Stage2 uses hard-coded T0SZ.
 Basically, the table is accessed as: ((u64*)tablebase)[<IA[y:30]>], where y = (37-T*SZ)+26. That is, starting at bit "y" ending(inclusive) at bit30. For TTBR0*_EL1, y = 32, while for TTBR1_EL1 y = 35.
 Hence, for TTBR0, index=((vaddr>>30) & 0x7), and for TTBR1, index=((vaddr>>30) & 0x3f).

"Vector Base Address Register (EL1)" = 0xfffffff7ffc50800.

The table for TTBR0 only contains the following:

  • Vmem 0x80000000 is mapped to physmem 0x80000000, using a size loaded from a register. This is only done when: "endaddr = 0x7fffffff + size; if(endaddr >= 0x80000001){...}"
    • The size is loaded from: "(u32 *0x70019050 & 0x3fff) << 20;"
    • The value written to the MMU-table descriptor is: "physaddr | val | 0x709;". val is 1<<52 when "tmp>>34" is non-zero and when "if((physaddr & 0x3c0000000) == 0)", otherwise val=0. tmp=size at the start and increased by 0xffffffffc0000000 each loop iteration. physaddr is increased by 0x40000000 each loop iteration.

TTBR1:

  • vmem 0xFFFFFFF800000000 is mapped to physmem 0x80000000. Similar to above, except tmp=0 due to wrap-around, etc. The chunksize used when increasing addr is 0xfffffff840000000, with another +=0x40000000 separate from the addr cmp for the loop.
    • "endaddr = 0x3fffffff + (<size from above> | 0xfffffff800000000); enaddr = (endaddr & 0xffffffffc0000000)-1; if(endaddr >= 0xfffffff800000001){<map mem>}"
  • Initializes level2 pagetable descriptor for vmem 0xFFFFFFF7C0000000. descriptor = 0x3 | physaddr. physaddr is core-specific.
  • Initializes level3 pagetable descriptor for vmem 0xFFFFFFF7FFC00000. descriptor = 0x3 | physaddr. physaddr is core-specific.
  • The content of the pagetable for the following level3 mmutables are not initialized in the main mmutable-init func. descriptor = 0x8007c003(0x3 | <physaddr tablebase>). tablebase=0x8007c000.
    • Initializes level3 pagetable descriptor for vmem 0xFFFFFFF7FEE00000. physaddr = tablebase + (0x1<<12).
    • Initializes level3 pagetable descriptor for vmem 0xFFFFFFF7FF000000. physaddr = tablebase + (0x2<<12).
    • Initializes level3 pagetable descriptor for vmem 0xFFFFFFF7FF200000. physaddr = tablebase + (0x3<<12).
    • Initializes level3 pagetable descriptor for vmem 0xFFFFFFF7FFA00000. physaddr = tablebase + (0x7<<12).
    • Initializes level3 pagetable descriptor for vmem 0xFFFFFFF7FEC00000. physaddr = tablebase.
    • Initializes level3 pagetable descriptor for vmem 0xFFFFFFF7FF400000. physaddr = tablebase + (0x4<<12).
    • Initializes level3 pagetable descriptor for vmem 0xFFFFFFF7FF600000. physaddr = tablebase + (0x5<<12).
    • Initializes level3 pagetable descriptor for vmem 0xFFFFFFF7FF800000. physaddr = tablebase + (0x6<<12).