├── LICENSE ├── Makefile ├── README.md ├── cmd ├── amd64.go ├── common.go ├── linux.go ├── mem.go ├── uefi.go └── windows.go ├── go.mod ├── go.sum ├── main.go ├── shell ├── cmd.go └── terminal.go ├── uapi └── entry.go └── uefi ├── README.md ├── config.go ├── console.go ├── desc.go ├── error.go ├── exit.go ├── file.go ├── fs.go ├── graphics.go ├── guid.go ├── image.go ├── mem.go ├── pages.go ├── path.go ├── protocol.go ├── reset.go ├── uefi.go ├── uefi.s ├── wdog.go └── x64 ├── README.md ├── console.go ├── mem.go ├── x64.go └── x64.s /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 WithSecure Corporation. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of WithSecure Corporation nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) WithSecure Corporation 2 | # 3 | # Use of this source code is governed by the license 4 | # that can be found in the LICENSE file. 5 | 6 | BUILD_TAGS = linkcpuinit,linkramsize,linkramstart,linkprintk 7 | SHELL = /bin/bash 8 | APP ?= go-boot 9 | CONSOLE ?= text 10 | 11 | IMAGE_BASE := 10000000 12 | TEXT_START := $(shell echo $$((16#$(IMAGE_BASE) + 16#10000))) 13 | GOFLAGS := -tags ${BUILD_TAGS} -trimpath -ldflags "-s -w -E cpuinit -T $(TEXT_START) -R 0x1000 -X 'main.Console=${CONSOLE}'" 14 | GOENV := GOOS=tamago GOARCH=amd64 15 | 16 | OVMFCODE ?= OVMF_CODE.fd 17 | OVMFVARS ?= OVMF_VARS.fd 18 | LOG ?= qemu.log 19 | 20 | QEMU ?= qemu-system-x86_64 \ 21 | -enable-kvm -cpu host,invtsc=on -m 8G \ 22 | -drive file=fat:rw:$(CURDIR) \ 23 | -drive if=pflash,format=raw,readonly,file=$(OVMFCODE) \ 24 | -drive if=pflash,format=raw,file=$(OVMFVARS) \ 25 | -global isa-debugcon.iobase=0x402 \ 26 | -serial stdio -vga virtio \ 27 | # -debugcon file:$(LOG) 28 | 29 | .PHONY: clean 30 | 31 | #### primary targets #### 32 | 33 | all: $(APP).efi 34 | 35 | elf: $(APP) 36 | 37 | efi: $(APP).efi 38 | 39 | qemu: $(APP).efi 40 | @if [ "${QEMU}" == "" ]; then \ 41 | echo 'qemu not available for this target'; \ 42 | exit 1; \ 43 | fi 44 | $(QEMU) 45 | 46 | qemu-gdb: GOFLAGS := $(GOFLAGS:-w=) 47 | qemu-gdb: GOFLAGS := $(GOFLAGS:-s=) 48 | qemu-gdb: $(APP).efi 49 | $(QEMU) -S -s 50 | 51 | #### utilities #### 52 | 53 | check_tamago: 54 | @if [ "${TAMAGO}" == "" ] || [ ! -f "${TAMAGO}" ]; then \ 55 | echo 'You need to set the TAMAGO variable to a compiled version of https://github.com/usbarmory/tamago-go'; \ 56 | exit 1; \ 57 | fi 58 | 59 | clean: 60 | @rm -fr $(APP) $(APP).efi 61 | 62 | #### dependencies #### 63 | 64 | $(APP): check_tamago 65 | $(GOENV) $(TAMAGO) build $(GOFLAGS) -o ${APP} 66 | 67 | $(APP).efi: $(APP) 68 | objcopy \ 69 | --strip-debug \ 70 | --target efi-app-x86_64 \ 71 | --subsystem=efi-app \ 72 | --image-base 0x$(IMAGE_BASE) \ 73 | --stack=0x10000 \ 74 | ${APP} ${APP}.efi 75 | printf '\x26\x02' | dd of=${APP}.efi bs=1 seek=150 count=2 conv=notrunc,fsync # adjust Characteristics 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | The [go-boot](https://github.com/usbarmory/go-boot) project is a 5 | [TamaGo](https://github.com/usbarmory/tamago) unikernel implementing a UEFI 6 | Shell and OS loader for AMD64 platforms, allowing UEFI API interaction and OS 7 | loading. 8 | 9 | The OS loading functionality supports launching of: 10 | * `.` EFI application images 11 | * `l` Linux kernels, with configuration parsed from Linux Userspace API (UAPI) [boot loader entries](https://uapi-group.org/specifications/specs/boot_loader_specification/) 12 | * `w` Windows UEFI boot manager 13 | 14 | Authors 15 | ======= 16 | 17 | Andrea Barisani 18 | andrea@inversepath.com 19 | 20 | Operation 21 | ========= 22 | 23 | The default operation is to present an UEFI shell and its help, the ⏎ shortcut 24 | boots the Arch Linux default UAPI entry at `\loader\entries\arch.conf`. 25 | 26 | ``` 27 | Shell> go-boot.efi 28 | 29 | initializing EFI services 30 | initializing console (com1) 31 | 32 | go-boot • tamago/amd64 (go1.24.1) • UEFI x64 33 | 34 | . # load and start EFI image 35 | build # build information 36 | cat # show file contents 37 | clear # clear screen 38 | cpuid # show CPU capabilities 39 | date (time in RFC339 format)? # show/change runtime date and time 40 | exit,quit # exit application 41 | halt,shutdown # shutdown system 42 | info # runtime information 43 | linux,l,\r (loader entry path)? # boot Linux kernel image 44 | log # show runtime logs 45 | lspci # list PCI devices 46 | memmap (e820)? # show UEFI memory map 47 | mode # set screen mode 48 | peek # memory display (use with caution) 49 | poke # memory write (use with caution) 50 | protocol # locate UEFI protocol 51 | reset (cold|warm)? # reset system 52 | stack # goroutine stack trace (current) 53 | stackall # goroutine stack trace (all) 54 | stat # show file information 55 | uefi # UEFI information 56 | uptime # show system running time 57 | windows,win,w # launch Windows UEFI boot manager 58 | 59 | 60 | > uefi 61 | UEFI Revision ......: 2.70 62 | Firmware Vendor ....: Lenovo 63 | Firmware Revision ..: 0x1560 64 | Runtime Services ..: 0x90e2eb98 65 | Boot Services ......: 0x6bd17690 66 | Frame Buffer .......: 1920x1200 @ 0x4000000000 67 | Configuration Tables: 0x8f426018 68 | ee4e5898-3914-4259-9d6e-dc7bd79403cf (0x8db6dc98) 69 | dcfa911d-26eb-469f-a220-38b7dc461220 (0x8b037018) 70 | ... 71 | 72 | > memmap 73 | Type Start End Pages Attributes 74 | 02 0000000090000000 0000000090000fff 0000000000000001 000000000000000f 75 | ... 76 | 77 | > linux \loader\entries\arch.conf 78 | loading boot loader entry \loader\entries\arch.conf 79 | go-boot exiting EFI boot services and jumping to kernel 80 | Linux version 6.13.6-arch1-1 (linux@archlinux) (gcc (GCC) 14.2.1 20250207, GNU ld (GNU Binutils) 2.44) 81 | ... 82 | ``` 83 | 84 | Package documentation 85 | ===================== 86 | 87 | [![Go Reference](https://pkg.go.dev/badge/github.com/usbarmory/go-boot.svg)](https://pkg.go.dev/github.com/usbarmory/go-boot) 88 | 89 | Hardware Compatibility List 90 | =========================== 91 | 92 | The list of supported hardware is available in the 93 | project wiki [HCL](https://github.com/usbarmory/go-boot/wiki#hardware-compatibility-list). 94 | 95 | The list provides test `IMAGE_BASE` values to pass while _Compiling_. 96 | 97 | Compiling 98 | ========= 99 | 100 | Build the [TamaGo compiler](https://github.com/usbarmory/tamago-go) 101 | (or use the [latest binary release](https://github.com/usbarmory/tamago-go/releases/latest)): 102 | 103 | ``` 104 | wget https://github.com/usbarmory/tamago-go/archive/refs/tags/latest.zip 105 | unzip latest.zip 106 | cd tamago-go-latest/src && ./all.bash 107 | cd ../bin && export TAMAGO=`pwd`/go 108 | ``` 109 | 110 | The `CONSOLE` environment variable must be set to either `com1` or `text` 111 | (default) to configure the output console to serial port or UEFI console. 112 | 113 | The `IMAGE_BASE` environment variable must be set within a memory range (in 114 | hex) available in the target UEFI environment for the unikernel allocation, the 115 | [HCL](https://github.com/usbarmory/go-boot/wiki#hardware-compatibility-list) or 116 | `memmap` command from an [UEFI Shell](https://github.com/pbatard/UEFI-Shell) 117 | can provide such value, when empty a common default value is set. 118 | 119 | Build the `go-boot.efi` application executable: 120 | 121 | ``` 122 | git clone https://github.com/usbarmory/go-boot && cd go-boot 123 | make efi IMAGE_BASE=10000000 CONSOLE=text 124 | ``` 125 | 126 | Executing as UEFI application 127 | ============================= 128 | 129 | The `go-boot.efi` application executable, built after _Compiling_, can be 130 | loaded from an [UEFI Shell](https://github.com/pbatard/UEFI-Shell) 131 | or boot manager, the following example shows an entry for 132 | [systemd-boot](https://www.freedesktop.org/wiki/Software/systemd/systemd-boot/): 133 | 134 | ``` 135 | # /boot/loader/entries/go-boot.conf 136 | title Go Boot 137 | efi /EFI/Linux/go-boot.efi 138 | ``` 139 | 140 | UEFI boot manager entry 141 | ======================= 142 | 143 | The following example shows creation of an EFI boot entry using 144 | [efibootmgr](https://github.com/rhboot/efibootmgr): 145 | 146 | ``` 147 | efibootmgr -C -L "go-boot" -d $DISK -p $PART -l '\EFI\go-boot.efi' 148 | ``` 149 | 150 | Emulated hardware with QEMU 151 | =========================== 152 | 153 | QEMU supported targets can be executed under emulation, using the 154 | [Open Virtual Machine Firmware](https://github.com/tianocore/tianocore.github.io/wiki/OVMF) 155 | as follows: 156 | 157 | ``` 158 | make qemu CONSOLE=com1 OVMFCODE= OVMFVARS= 159 | ``` 160 | 161 | The emulation run will provide an interactive console. 162 | 163 | An emulated target can be [debugged with GDB](https://retrage.github.io/2019/12/05/debugging-ovmf-en.html/) 164 | using `make qemu-gdb`, this will make qemu waiting for a GDB connection that 165 | can be launched as follows: 166 | 167 | ``` 168 | gdb -ex "target remote 127.0.0.1:1234" 169 | ``` 170 | 171 | Breakpoints can be set in the usual way: 172 | 173 | ``` 174 | b cpuinit 175 | continue 176 | ``` 177 | 178 | License 179 | ======= 180 | 181 | go-boot | https://github.com/usbarmory/go-boot 182 | Copyright (c) WithSecure Corporation 183 | 184 | These source files are distributed under the BSD-style license found in the 185 | [LICENSE](https://github.com/usbarmory/go-boot/blob/main/LICENSE) file. 186 | -------------------------------------------------------------------------------- /cmd/amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "regexp" 12 | "runtime" 13 | "strconv" 14 | 15 | "github.com/usbarmory/tamago/amd64" 16 | "github.com/usbarmory/tamago/soc/intel/pci" 17 | 18 | "github.com/usbarmory/go-boot/shell" 19 | "github.com/usbarmory/go-boot/uefi/x64" 20 | ) 21 | 22 | func init() { 23 | shell.Add(shell.Cmd{ 24 | Name: "info", 25 | Help: "device information", 26 | Fn: infoCmd, 27 | }) 28 | 29 | shell.Add(shell.Cmd{ 30 | Name: "cpuid", 31 | Args: 2, 32 | Pattern: regexp.MustCompile(`^cpuid\s+([[:xdigit:]]+) ([[:xdigit:]]+)$`), 33 | Syntax: " ", 34 | Help: "show CPU capabilities", 35 | Fn: cpuidCmd, 36 | }) 37 | 38 | shell.Add(shell.Cmd{ 39 | Name: "lspci", 40 | Help: "list PCI devices", 41 | Fn: lspciCmd, 42 | }) 43 | } 44 | 45 | func mem(start uint, size int, w []byte) (b []byte) { 46 | return memCopy(start, size, w) 47 | } 48 | 49 | func infoCmd(_ *shell.Interface, _ []string) (string, error) { 50 | var res bytes.Buffer 51 | 52 | ramStart, ramEnd := runtime.MemRegion() 53 | textStart, textEnd := runtime.TextRegion() 54 | _, heapStart := runtime.DataRegion() 55 | 56 | m := &runtime.MemStats{} 57 | runtime.ReadMemStats(m) 58 | 59 | fmt.Fprintf(&res, "Runtime ......: %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH) 60 | fmt.Fprintf(&res, "RAM ..........: %#08x-%#08x (%d MiB)\n", ramStart, ramEnd, (ramEnd-ramStart)/(1024*1024)) 61 | fmt.Fprintf(&res, "Text .........: %#08x-%#08x\n", textStart, textEnd) 62 | fmt.Fprintf(&res, "Heap .........: %#08x-%#08x Alloc:%d MiB Sys:%d MiB\n", heapStart, ramEnd, m.HeapAlloc/(1024*1024), m.HeapSys/(1024*1024)) 63 | fmt.Fprintf(&res, "CPU ..........: %s\n", x64.AMD64.Name()) 64 | fmt.Fprintf(&res, "Cores ........: %d\n", amd64.NumCPU()) 65 | fmt.Fprintf(&res, "Frequency ....: %v GHz\n", float32(x64.AMD64.Freq())/1e9) 66 | 67 | return res.String(), nil 68 | } 69 | 70 | func cpuidCmd(_ *shell.Interface, arg []string) (string, error) { 71 | var res bytes.Buffer 72 | 73 | leaf, err := strconv.ParseUint(arg[0], 16, 32) 74 | 75 | if err != nil { 76 | return "", fmt.Errorf("invalid leaf, %v", err) 77 | } 78 | 79 | subleaf, err := strconv.ParseUint(arg[1], 10, 32) 80 | 81 | if err != nil { 82 | return "", fmt.Errorf("invalid subleaf, %v", err) 83 | } 84 | 85 | eax, ebx, ecx, edx := x64.AMD64.CPUID(uint32(leaf), uint32(subleaf)) 86 | 87 | fmt.Fprintf(&res, "EAX EBX ECX EDX\n") 88 | fmt.Fprintf(&res, "%08x %08x %08x %08x\n", eax, ebx, ecx, edx) 89 | 90 | return res.String(), nil 91 | } 92 | 93 | func lspciCmd(_ *shell.Interface, arg []string) (string, error) { 94 | var res bytes.Buffer 95 | 96 | fmt.Fprintf(&res, "Bus Vendor Device Bar0\n") 97 | 98 | for i := 0; i < 256; i++ { 99 | for _, d := range pci.Devices(i) { 100 | fmt.Fprintf(&res, "%03d %04x %04x %#016x\n", i, d.Vendor, d.Device, d.BaseAddress(0)) 101 | } 102 | } 103 | 104 | return res.String(), nil 105 | } 106 | 107 | func date(epoch int64) { 108 | x64.AMD64.SetTimer(epoch) 109 | } 110 | 111 | func uptime() (ns int64) { 112 | return int64(float64(x64.AMD64.TimerFn()) * x64.AMD64.TimerMultiplier) 113 | } 114 | -------------------------------------------------------------------------------- /cmd/common.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "io" 12 | "os" 13 | "regexp" 14 | "runtime/debug" 15 | "runtime/pprof" 16 | "time" 17 | 18 | "github.com/hako/durafmt" 19 | 20 | "github.com/usbarmory/go-boot/shell" 21 | ) 22 | 23 | const testDiversifier = "\xde\xad\xbe\xef" 24 | const LogPath = "/go-boot.log" 25 | 26 | func init() { 27 | shell.Add(shell.Cmd{ 28 | Name: "build", 29 | Help: "build information", 30 | Fn: buildInfoCmd, 31 | }) 32 | 33 | shell.Add(shell.Cmd{ 34 | Name: "log", 35 | Help: "show runtime logs", 36 | Fn: logCmd, 37 | }) 38 | 39 | shell.Add(shell.Cmd{ 40 | Name: "exit,quit", 41 | Args: 1, 42 | Pattern: regexp.MustCompile(`^(exit|quit)$`), 43 | Help: "exit application", 44 | Fn: exitCmd, 45 | }) 46 | 47 | shell.Add(shell.Cmd{ 48 | Name: "stack", 49 | Help: "goroutine stack trace (current)", 50 | Fn: stackCmd, 51 | }) 52 | 53 | shell.Add(shell.Cmd{ 54 | Name: "stackall", 55 | Help: "goroutine stack trace (all)", 56 | Fn: stackallCmd, 57 | }) 58 | 59 | shell.Add(shell.Cmd{ 60 | Name: "date", 61 | Args: 1, 62 | Pattern: regexp.MustCompile(`^date(.*)`), 63 | Syntax: "(time in RFC339 format)?", 64 | Help: "show/change runtime date and time", 65 | Fn: dateCmd, 66 | }) 67 | 68 | shell.Add(shell.Cmd{ 69 | Name: "uptime", 70 | Help: "show system running time", 71 | Fn: uptimeCmd, 72 | }) 73 | } 74 | 75 | func buildInfoCmd(_ *shell.Interface, _ []string) (string, error) { 76 | res := new(bytes.Buffer) 77 | 78 | if bi, ok := debug.ReadBuildInfo(); ok { 79 | res.WriteString(bi.String()) 80 | } 81 | 82 | return res.String(), nil 83 | } 84 | 85 | func logCmd(_ *shell.Interface, _ []string) (string, error) { 86 | res, err := os.ReadFile(LogPath) 87 | return string(res), err 88 | } 89 | 90 | func exitCmd(_ *shell.Interface, _ []string) (res string, err error) { 91 | return "", io.EOF 92 | } 93 | 94 | func stackCmd(_ *shell.Interface, _ []string) (string, error) { 95 | return string(debug.Stack()), nil 96 | } 97 | 98 | func stackallCmd(_ *shell.Interface, _ []string) (string, error) { 99 | buf := new(bytes.Buffer) 100 | pprof.Lookup("goroutine").WriteTo(buf, 1) 101 | 102 | return buf.String(), nil 103 | } 104 | 105 | func dateCmd(_ *shell.Interface, arg []string) (res string, err error) { 106 | if len(arg[0]) > 1 { 107 | t, err := time.Parse(time.RFC3339, arg[0][1:]) 108 | 109 | if err != nil { 110 | return "", err 111 | } 112 | 113 | date(t.UnixNano()) 114 | } 115 | 116 | return fmt.Sprintf("%s", time.Now().Format(time.RFC3339)), nil 117 | } 118 | 119 | func uptimeCmd(_ *shell.Interface, _ []string) (string, error) { 120 | ns := uptime() 121 | return fmt.Sprintf("%s", durafmt.Parse(time.Duration(ns)*time.Nanosecond)), nil 122 | } 123 | -------------------------------------------------------------------------------- /cmd/linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "log" 12 | "regexp" 13 | "strings" 14 | 15 | "github.com/u-root/u-root/pkg/boot/bzimage" 16 | 17 | "github.com/usbarmory/armory-boot/exec" 18 | "github.com/usbarmory/go-boot/shell" 19 | "github.com/usbarmory/go-boot/uapi" 20 | "github.com/usbarmory/go-boot/uefi" 21 | "github.com/usbarmory/go-boot/uefi/x64" 22 | "github.com/usbarmory/tamago/dma" 23 | ) 24 | 25 | const ( 26 | // avoid initial DMA region 27 | minLoadAddr = 0x01000000 28 | paramsSize = 0x1000 29 | exitRetries = 3 30 | ) 31 | 32 | // LinuxDefaultEntry represents the default path for the UAPI Type #1 Boot 33 | // Loader Entry. 34 | const LinuxDefaultEntry = `\loader\entries\arch.conf` 35 | 36 | func init() { 37 | shell.Add(shell.Cmd{ 38 | Name: "linux,l,\\r", 39 | Args: 1, 40 | Pattern: regexp.MustCompile(`^(?:linux|l|)(?: (\S+))?$`), 41 | Syntax: "(loader entry path)?", 42 | Help: "boot Linux kernel image", 43 | Fn: linuxCmd, 44 | }) 45 | } 46 | 47 | func reserveMemory(m *uefi.MemoryMap, image *exec.LinuxImage) (err error) { 48 | size := len(image.BzImage.KernelCode) + len(image.InitialRamDisk) 49 | 50 | // Convert UEFI Memory Map to E820 as it reflects availability after 51 | // exiting EFI Boot Services. 52 | image.Memory = m.E820() 53 | 54 | // find unallocated UEFI memory for kernel and ramdisk loading 55 | for _, entry := range image.Memory { 56 | if entry.MemType != bzimage.RAM || 57 | int(entry.Size) < size { 58 | continue 59 | } 60 | 61 | // shift above minLoadAddr as required and recheck size 62 | if entry.Addr < minLoadAddr { 63 | off := minLoadAddr - entry.Addr 64 | entry.Addr += off 65 | entry.Size -= off 66 | 67 | if int(entry.Size) < size { 68 | continue 69 | } 70 | } 71 | 72 | // opportunistic size increase 73 | size = int(entry.Size) 74 | 75 | // reserve unallocated UEFI memory for our runtime DMA 76 | if image.Region, err = dma.NewRegion(uint(entry.Addr), size, false); err != nil { 77 | // skip our own runtime pages 78 | continue 79 | } 80 | 81 | log.Printf("reserving memory %#x - %#x", entry.Addr, entry.Addr+entry.Size) 82 | image.Region.Reserve(size, 0) 83 | 84 | break 85 | } 86 | 87 | if image.Region == nil { 88 | return errors.New("could not find memory for kernel loading") 89 | } 90 | 91 | // enforce required alignment on kernel and ramdisk offsets 92 | align := int(image.BzImage.Header.Kernelalignment) 93 | base := int(image.Region.Start()) 94 | 95 | image.InitialRamDiskOffset = 0 96 | image.InitialRamDiskOffset += -(base + image.InitialRamDiskOffset) & (align - 1) 97 | 98 | image.KernelOffset = image.InitialRamDiskOffset + len(image.InitialRamDisk) 99 | image.KernelOffset += -(base + image.KernelOffset) & (align - 1) 100 | 101 | // place boot parameters at the far end 102 | image.CmdLineOffset = size - int(image.BzImage.Header.CmdLineSize) 103 | image.ParamsOffset = image.CmdLineOffset - paramsSize 104 | 105 | return 106 | } 107 | 108 | func efiInfo(memoryMap *uefi.MemoryMap) (efi *exec.EFI, err error) { 109 | return &exec.EFI{ 110 | LoaderSignature: exec.EFI64LoaderSignature, 111 | SystemTable: uint32(x64.UEFI.Address()), 112 | SystemTableHigh: uint32(x64.UEFI.Address() >> 32), 113 | MemoryMapHigh: uint32(memoryMap.Address() >> 32), 114 | MemoryMapSize: uint32(memoryMap.MapSize), 115 | MemoryMap: uint32(memoryMap.Address()), 116 | MemoryDescSize: uint32(memoryMap.DescriptorSize), 117 | MemoryDescVersion: 1, // 118 | }, nil 119 | } 120 | 121 | func screenInfo() (screen *exec.Screen, err error) { 122 | var gop *uefi.GraphicsOutput 123 | var mode *uefi.ProtocolMode 124 | var info *uefi.ModeInformation 125 | 126 | if gop, err = x64.UEFI.Boot.GetGraphicsOutput(); err != nil { 127 | return 128 | } 129 | 130 | if mode, err = gop.GetMode(); err != nil { 131 | return 132 | } 133 | 134 | if info, err = mode.GetInfo(); err != nil { 135 | return 136 | } 137 | 138 | // values for efib selection 139 | screen = &exec.Screen{ 140 | OrigVideoIsVGA: exec.VideoTypeEFI, 141 | LfbWidth: uint16(info.HorizontalResolution), 142 | LfbHeight: uint16(info.VerticalResolution), 143 | LfbBase: uint32(mode.FrameBufferBase), 144 | LfbSize: uint32(mode.FrameBufferSize), 145 | LfbLineLength: uint16(info.HorizontalResolution * 4), 146 | ExtLfbBase: uint32(mode.FrameBufferBase >> 32), 147 | } 148 | 149 | if screen.ExtLfbBase > 0 { 150 | screen.Capabilities = exec.Video64BitBase 151 | } 152 | 153 | return 154 | } 155 | 156 | func boot(image *exec.LinuxImage) (err error) { 157 | var memoryMap *uefi.MemoryMap 158 | 159 | log.Print("go-boot exiting EFI boot services and jumping to kernel") 160 | 161 | // fill screen_info 162 | if image.Screen, err = screenInfo(); err != nil { 163 | log.Printf("could not detect screen information, %v\n", err) 164 | } 165 | 166 | for i := 0; i < exitRetries; i++ { 167 | // own all available memory 168 | if memoryMap, err = x64.UEFI.Boot.ExitBootServices(); err != nil { 169 | log.Print("go-boot exiting EFI boot services (retrying)") 170 | continue 171 | } 172 | break 173 | } 174 | 175 | if err != nil { 176 | return fmt.Errorf("could not exit EFI boot services, %v\n", err) 177 | } 178 | 179 | // silence EFI Simple Text console 180 | x64.Console.Out = 0 181 | x64.UEFI.Console.Out = 0 182 | 183 | // parse kernel image 184 | if err = image.Parse(); err != nil { 185 | return 186 | } 187 | 188 | // reserve runtime memory for kernel loading 189 | if err = reserveMemory(memoryMap, image); err != nil { 190 | return 191 | } 192 | 193 | // release in case of error 194 | defer image.Region.Release(image.Region.Start()) 195 | 196 | // fill EFI information in boot parameters 197 | if image.EFI, err = efiInfo(memoryMap); err != nil { 198 | return 199 | } 200 | 201 | // load kernel in reserved memory 202 | if err = image.Load(); err != nil { 203 | return fmt.Errorf("could not load kernel, %v", err) 204 | } 205 | 206 | log.Printf("booting kernel@%#x", image.Entry()) 207 | return image.Boot(nil) 208 | } 209 | 210 | func linuxCmd(_ *shell.Interface, arg []string) (res string, err error) { 211 | var entry *uapi.Entry 212 | 213 | path := strings.TrimSpace(arg[0]) 214 | 215 | if len(path) == 0 { 216 | path = LinuxDefaultEntry 217 | } 218 | 219 | if x64.UEFI.Boot == nil { 220 | return "", errors.New("EFI Boot Services unavailable") 221 | } 222 | 223 | root, err := x64.UEFI.Root() 224 | 225 | if err != nil { 226 | return "", fmt.Errorf("could not open root volume, %v", err) 227 | } 228 | 229 | log.Printf("loading boot loader entry %s", path) 230 | 231 | if entry, err = uapi.LoadEntry(root, path); err != nil { 232 | return "", fmt.Errorf("error loading entry, %v", err) 233 | } 234 | 235 | if len(entry.Linux) == 0 { 236 | return "", errors.New("empty kernel entry") 237 | } 238 | 239 | image := &exec.LinuxImage{ 240 | Kernel: entry.Linux, 241 | InitialRamDisk: entry.Initrd, 242 | CmdLine: entry.Options, 243 | } 244 | 245 | return "", boot(image) 246 | } 247 | -------------------------------------------------------------------------------- /cmd/mem.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "encoding/binary" 10 | "encoding/hex" 11 | "fmt" 12 | "regexp" 13 | "strconv" 14 | 15 | "github.com/usbarmory/go-boot/shell" 16 | "github.com/usbarmory/tamago/dma" 17 | ) 18 | 19 | const maxBufferSize = 102400 20 | 21 | func init() { 22 | shell.Add(shell.Cmd{ 23 | Name: "peek", 24 | Args: 2, 25 | Pattern: regexp.MustCompile(`^peek ([[:xdigit:]]+) (\d+)$`), 26 | Syntax: " ", 27 | Help: "memory display (use with caution)", 28 | Fn: memReadCmd, 29 | }) 30 | 31 | shell.Add(shell.Cmd{ 32 | Name: "poke", 33 | Args: 2, 34 | Pattern: regexp.MustCompile(`^poke ([[:xdigit:]]+) ([[:xdigit:]]+)$`), 35 | Syntax: " ", 36 | Help: "memory write (use with caution)", 37 | Fn: memWriteCmd, 38 | }) 39 | } 40 | 41 | func memCopy(start uint, size int, w []byte) (b []byte) { 42 | mem, err := dma.NewRegion(start, size, true) 43 | 44 | if err != nil { 45 | panic("could not allocate memory copy DMA") 46 | } 47 | 48 | start, buf := mem.Reserve(size, 0) 49 | defer mem.Release(start) 50 | 51 | if len(w) > 0 { 52 | copy(buf, w) 53 | } else { 54 | b = make([]byte, size) 55 | copy(b, buf) 56 | } 57 | 58 | return 59 | } 60 | 61 | func memReadCmd(_ *shell.Interface, arg []string) (res string, err error) { 62 | addr, err := strconv.ParseUint(arg[0], 16, 64) 63 | 64 | if err != nil { 65 | return "", fmt.Errorf("invalid address, %v", err) 66 | } 67 | 68 | size, err := strconv.ParseUint(arg[1], 10, 64) 69 | 70 | if err != nil { 71 | return "", fmt.Errorf("invalid size, %v", err) 72 | } 73 | 74 | if (addr%8) != 0 || (size%8) != 0 { 75 | return "", fmt.Errorf("only 64-bit aligned accesses are supported") 76 | } 77 | 78 | if size > maxBufferSize { 79 | return "", fmt.Errorf("size argument must be <= %d", maxBufferSize) 80 | } 81 | 82 | return hex.Dump(mem(uint(addr), int(size), nil)), nil 83 | } 84 | 85 | func memWriteCmd(_ *shell.Interface, arg []string) (res string, err error) { 86 | addr, err := strconv.ParseUint(arg[0], 16, 64) 87 | 88 | if err != nil { 89 | return "", fmt.Errorf("invalid address, %v", err) 90 | } 91 | 92 | val, err := strconv.ParseUint(arg[1], 16, 64) 93 | 94 | if err != nil { 95 | return "", fmt.Errorf("invalid data, %v", err) 96 | } 97 | 98 | buf := make([]byte, 8) 99 | binary.BigEndian.PutUint32(buf, uint32(val)) 100 | 101 | mem(uint(addr), 8, buf) 102 | 103 | return 104 | } 105 | -------------------------------------------------------------------------------- /cmd/uefi.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "bytes" 10 | "crypto/sha256" 11 | "encoding/binary" 12 | "fmt" 13 | "io/fs" 14 | "log" 15 | "regexp" 16 | "strconv" 17 | "unicode/utf16" 18 | 19 | "github.com/usbarmory/go-boot/shell" 20 | "github.com/usbarmory/go-boot/uefi" 21 | "github.com/usbarmory/go-boot/uefi/x64" 22 | ) 23 | 24 | const maxVendorSize = 64 25 | 26 | func init() { 27 | shell.Add(shell.Cmd{ 28 | Name: "uefi", 29 | Help: "UEFI information", 30 | Fn: uefiCmd, 31 | }) 32 | 33 | shell.Add(shell.Cmd{ 34 | Name: ".", 35 | Args: 1, 36 | Pattern: regexp.MustCompile(`^\. (.*)$`), 37 | Syntax: "", 38 | Help: "load and start EFI image", 39 | Fn: launchCmd, 40 | }) 41 | 42 | shell.Add(shell.Cmd{ 43 | Name: "protocol", 44 | Args: 1, 45 | Pattern: regexp.MustCompile(`^protocol ([[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12})$`), 46 | Syntax: "", 47 | Help: "locate UEFI protocol", 48 | Fn: locateCmd, 49 | }) 50 | 51 | shell.Add(shell.Cmd{ 52 | Name: "cat", 53 | Args: 1, 54 | Pattern: regexp.MustCompile(`^cat (.*)`), 55 | Syntax: "", 56 | Help: "show file contents", 57 | Fn: catCmd, 58 | }) 59 | 60 | shell.Add(shell.Cmd{ 61 | Name: "clear", 62 | Help: "clear screen", 63 | Fn: clearCmd, 64 | }) 65 | 66 | shell.Add(shell.Cmd{ 67 | Name: "mode", 68 | Args: 1, 69 | Pattern: regexp.MustCompile(`^mode (\d+)$`), 70 | Syntax: "", 71 | Help: "set screen mode", 72 | Fn: modeCmd, 73 | }) 74 | 75 | shell.Add(shell.Cmd{ 76 | Name: "stat", 77 | Args: 1, 78 | Pattern: regexp.MustCompile(`^stat (.*)`), 79 | Syntax: "", 80 | Help: "show file information", 81 | Fn: statCmd, 82 | }) 83 | 84 | shell.Add(shell.Cmd{ 85 | Name: "memmap", 86 | Args: 1, 87 | Pattern: regexp.MustCompile(`^memmap( e820)?$`), 88 | Help: "show UEFI memory map", 89 | Syntax: "(e820)?", 90 | Fn: memmapCmd, 91 | }) 92 | 93 | shell.Add(shell.Cmd{ 94 | Name: "reset", 95 | Args: 1, 96 | Pattern: regexp.MustCompile(`^reset(?: (cold|warm))?$`), 97 | Help: "reset system", 98 | Syntax: "(cold|warm)?", 99 | Fn: resetCmd, 100 | }) 101 | 102 | shell.Add(shell.Cmd{ 103 | Name: "halt,shutdown", 104 | Args: 1, 105 | Pattern: regexp.MustCompile(`^(halt|shutdown)$`), 106 | Help: "shutdown system", 107 | Fn: shutdownCmd, 108 | }) 109 | } 110 | 111 | func uefiCmd(_ *shell.Interface, _ []string) (res string, err error) { 112 | var buf bytes.Buffer 113 | var s []uint16 114 | 115 | t := x64.UEFI.SystemTable 116 | b := mem(uint(t.FirmwareVendor), maxVendorSize, nil) 117 | 118 | for i := 0; i < maxVendorSize; i += 2 { 119 | if b[i] == 0x00 && b[i+1] == 0 { 120 | break 121 | } 122 | 123 | s = append(s, binary.LittleEndian.Uint16(b[i:i+2])) 124 | } 125 | 126 | fmt.Fprintf(&buf, "UEFI Revision ......: %s\n", t.Revision()) 127 | fmt.Fprintf(&buf, "Firmware Vendor ....: %s\n", string(utf16.Decode(s))) 128 | fmt.Fprintf(&buf, "Firmware Revision ..: %#x\n", t.FirmwareRevision) 129 | fmt.Fprintf(&buf, "Runtime Services ..: %#x\n", t.RuntimeServices) 130 | fmt.Fprintf(&buf, "Boot Services ......: %#x\n", t.BootServices) 131 | 132 | if s, err := screenInfo(); err == nil { 133 | fmt.Fprintf(&buf, "Frame Buffer .......: %dx%d @ %#x\n", 134 | s.LfbWidth, s.LfbHeight, 135 | uint64(s.ExtLfbBase)<<32|uint64(s.LfbBase)) 136 | } 137 | 138 | fmt.Fprintf(&buf, "Configuration Tables: %#x\n", t.ConfigurationTable) 139 | 140 | if c, err := t.ConfigurationTables(); err == nil { 141 | for _, t := range c { 142 | fmt.Fprintf(&buf, " %s (%#x)\n", t.RegistryFormat(), t.VendorTable) 143 | } 144 | } 145 | 146 | return buf.String(), err 147 | } 148 | 149 | func launchCmd(_ *shell.Interface, arg []string) (res string, err error) { 150 | root, err := x64.UEFI.Root() 151 | 152 | if err != nil { 153 | return "", fmt.Errorf("could not open root volume, %v", err) 154 | } 155 | 156 | log.Printf("loading EFI image %s", arg[0]) 157 | h, err := x64.UEFI.Boot.LoadImage(0, root, arg[0]) 158 | 159 | if err != nil { 160 | return "", fmt.Errorf("could not load image, %v", err) 161 | } 162 | 163 | log.Printf("starting EFI image %#x", h) 164 | return "", x64.UEFI.Boot.StartImage(h) 165 | } 166 | 167 | func locateCmd(_ *shell.Interface, arg []string) (res string, err error) { 168 | addr, err := x64.UEFI.Boot.LocateProtocol(uefi.GUID(arg[0])) 169 | return fmt.Sprintf("%s: %#08x", arg[0], addr), err 170 | } 171 | 172 | func catCmd(_ *shell.Interface, arg []string) (res string, err error) { 173 | root, err := x64.UEFI.Root() 174 | 175 | if err != nil { 176 | return "", fmt.Errorf("could not open root volume, %v", err) 177 | } 178 | 179 | buf, err := fs.ReadFile(root, arg[0]) 180 | 181 | if err != nil { 182 | return "", fmt.Errorf("could not read file, %v", err) 183 | } 184 | 185 | return string(buf), nil 186 | } 187 | 188 | func clearCmd(_ *shell.Interface, _ []string) (string, error) { 189 | return "", x64.UEFI.Console.ClearScreen() 190 | } 191 | 192 | func modeCmd(_ *shell.Interface, arg []string) (string, error) { 193 | mode, err := strconv.ParseUint(arg[0], 16, 64) 194 | 195 | if err != nil { 196 | return "", fmt.Errorf("invalid mode, %v", err) 197 | } 198 | 199 | defer log.Printf("switched to EFI Console mode %d", mode) 200 | 201 | return "", x64.UEFI.Console.SetMode(mode) 202 | } 203 | 204 | func statCmd(_ *shell.Interface, arg []string) (res string, err error) { 205 | root, err := x64.UEFI.Root() 206 | 207 | if err != nil { 208 | return "", fmt.Errorf("could not open root volume, %v", err) 209 | } 210 | 211 | f, err := root.Open(arg[0]) 212 | 213 | if err != nil { 214 | return "", fmt.Errorf("could not open file, %v", err) 215 | } 216 | 217 | defer f.Close() 218 | 219 | stat, err := f.Stat() 220 | 221 | if err != nil { 222 | return 223 | } 224 | 225 | buf := make([]byte, stat.Size()) 226 | 227 | if _, err = f.Read(buf); err != nil { 228 | return 229 | } 230 | 231 | return fmt.Sprintf("Size:%d ModTime:%s IsDir:%v Sys:%#x Sum256:%x", 232 | stat.Size(), 233 | stat.ModTime(), 234 | stat.IsDir(), 235 | stat.Sys(), 236 | sha256.Sum256(buf), 237 | ), nil 238 | } 239 | 240 | func memmapCmd(_ *shell.Interface, arg []string) (res string, err error) { 241 | var buf bytes.Buffer 242 | var memoryMap *uefi.MemoryMap 243 | 244 | if memoryMap, err = x64.UEFI.Boot.GetMemoryMap(); err != nil { 245 | return 246 | } 247 | 248 | fmt.Fprintf(&buf, "Type Start End Pages ") 249 | 250 | switch { 251 | case arg[0] == "": 252 | fmt.Fprintf(&buf, "Attributes\n") 253 | for _, desc := range memoryMap.Descriptors { 254 | fmt.Fprintf(&buf, "%02d %016x %016x %016x %016x\n", 255 | desc.Type, desc.PhysicalStart, desc.PhysicalEnd()-1, desc.NumberOfPages, desc.Attribute) 256 | } 257 | case arg[0] == " e820": 258 | fmt.Fprintf(&buf, "\n") 259 | for _, desc := range memoryMap.E820() { 260 | fmt.Fprintf(&buf, "%02d %016x %016x %016x\n", 261 | desc.MemType, desc.Addr, desc.Addr+desc.Size-1, desc.Size/4096) 262 | } 263 | } 264 | 265 | return buf.String(), err 266 | } 267 | 268 | func resetCmd(_ *shell.Interface, arg []string) (_ string, err error) { 269 | var resetType int 270 | 271 | switch arg[0] { 272 | case "cold": 273 | resetType = uefi.EfiResetCold 274 | case "warm", "": 275 | resetType = uefi.EfiResetWarm 276 | case "shutdown": 277 | resetType = uefi.EfiResetShutdown 278 | } 279 | 280 | log.Printf("performing system reset type %d", resetType) 281 | err = x64.UEFI.Runtime.ResetSystem(resetType) 282 | 283 | return 284 | } 285 | 286 | func shutdownCmd(_ *shell.Interface, _ []string) (_ string, err error) { 287 | return resetCmd(nil, []string{"shutdown"}) 288 | } 289 | -------------------------------------------------------------------------------- /cmd/windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package cmd 7 | 8 | import ( 9 | "regexp" 10 | 11 | "github.com/usbarmory/go-boot/shell" 12 | ) 13 | 14 | const WindowsBootManager = `\EFI\Microsoft\Boot\bootmgfw.efi` 15 | 16 | func init() { 17 | shell.Add(shell.Cmd{ 18 | Name: "windows,win,w", 19 | Pattern: regexp.MustCompile(`^(?:windows|win|w)$`), 20 | Help: "launch Windows UEFI boot manager", 21 | Fn: winCmd, 22 | }) 23 | } 24 | 25 | func winCmd(_ *shell.Interface, arg []string) (res string, err error) { 26 | return launchCmd(nil, []string{WindowsBootManager}) 27 | } 28 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/usbarmory/go-boot 2 | 3 | go 1.24.2 4 | 5 | require ( 6 | github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b 7 | github.com/u-root/u-root v0.14.1-0.20250211171033-17c75c6542a1 8 | github.com/usbarmory/armory-boot v0.0.0-20250313080757-07776e494cb3 9 | github.com/usbarmory/tamago v0.0.0-20250506193353-852659b3a9d6 10 | golang.org/x/term v0.31.0 11 | ) 12 | 13 | require ( 14 | github.com/dustin/go-humanize v1.0.1 // indirect 15 | github.com/klauspost/compress v1.17.4 // indirect 16 | github.com/pierrec/lz4/v4 v4.1.14 // indirect 17 | github.com/therootcompany/xz v1.0.1 // indirect 18 | github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a // indirect 19 | github.com/ulikunitz/xz v0.5.11 // indirect 20 | golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect 21 | golang.org/x/sys v0.32.0 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 2 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 3 | github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4= 4 | github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= 5 | github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= 6 | github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 7 | github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= 8 | github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 9 | github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= 10 | github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= 11 | github.com/u-root/u-root v0.14.1-0.20250211171033-17c75c6542a1 h1:2WqTtC2ktBPigi0RNpHWVvt4zezuc/+n6oSrJtRCs7U= 12 | github.com/u-root/u-root v0.14.1-0.20250211171033-17c75c6542a1/go.mod h1:/TLFGfGie1UL+OW9++jkIxmo/rjYY6XTW9K/KQXq5mQ= 13 | github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a h1:BH1SOPEvehD2kVrndDnGJiUF0TrBpNs+iyYocu6h0og= 14 | github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= 15 | github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= 16 | github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 17 | github.com/usbarmory/armory-boot v0.0.0-20250313080757-07776e494cb3 h1:J74Up0b0QjHwPtXVOU/428zY5C72dQzV07QBod1iTU0= 18 | github.com/usbarmory/armory-boot v0.0.0-20250313080757-07776e494cb3/go.mod h1:sImXzIRRKl04CGGrOGFWH2a89G6/Bjxf62L08mg4bdU= 19 | github.com/usbarmory/tamago v0.0.0-20250506193353-852659b3a9d6 h1:9wO4mLPrgQmCDCvwcqkX1cP0vO4G4qKLoWWI/4n7MXo= 20 | github.com/usbarmory/tamago v0.0.0-20250506193353-852659b3a9d6/go.mod h1:J1fwiuVgBxsV9LY8tD6x+dCZUtNsrN/NEggeLb4yf14= 21 | golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= 22 | golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= 23 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 24 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 25 | golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= 26 | golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 27 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | //go:build amd64 7 | 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "io" 13 | "log" 14 | "os" 15 | "runtime" 16 | 17 | "github.com/usbarmory/go-boot/cmd" 18 | "github.com/usbarmory/go-boot/shell" 19 | "github.com/usbarmory/go-boot/uefi" 20 | "github.com/usbarmory/go-boot/uefi/x64" 21 | ) 22 | 23 | // Build time variable 24 | var Console string 25 | 26 | func init() { 27 | fmt.Printf("initializing console (%s)\n", Console) 28 | 29 | log.SetFlags(0) 30 | 31 | logFile, _ := os.OpenFile(cmd.LogPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 32 | log.SetOutput(io.MultiWriter(os.Stdout, logFile)) 33 | } 34 | 35 | func main() { 36 | banner := fmt.Sprintf("go-boot • %s/%s (%s) • UEFI x64", 37 | runtime.GOOS, runtime.GOARCH, runtime.Version()) 38 | 39 | iface := &shell.Interface{ 40 | Banner: banner, 41 | Console: x64.UEFI.Console, 42 | } 43 | 44 | // disable UEFI watchdog 45 | x64.UEFI.Boot.SetWatchdogTimer(0) 46 | 47 | switch Console { 48 | case "COM1", "com1", "": 49 | iface.ReadWriter = x64.UART0 50 | iface.Start(true) 51 | case "TEXT", "text": 52 | iface.ReadWriter = x64.UEFI.Console 53 | iface.Start(false) 54 | } 55 | 56 | log.Print("exit") 57 | 58 | if err := x64.UEFI.Boot.Exit(0); err != nil { 59 | log.Printf("halting due to exit error, %v", err) 60 | x64.UEFI.Runtime.ResetSystem(uefi.EfiResetShutdown) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /shell/cmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package shell 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "regexp" 12 | "sort" 13 | "text/tabwriter" 14 | 15 | "github.com/usbarmory/go-boot/uefi" 16 | ) 17 | 18 | // CmdFn represents a command handler. 19 | type CmdFn func(c *Interface, arg []string) (res string, err error) 20 | 21 | // Cmd represents a shell command. 22 | type Cmd struct { 23 | // Name is the command name. 24 | Name string 25 | 26 | // Args defines the number of command arguments, meant to be in the 27 | // Pattern capturing brackets. 28 | Args int 29 | 30 | // Pattern defines the command syntax and arguments. 31 | Pattern *regexp.Regexp 32 | 33 | // Syntax defines the Help() command syntax field. 34 | Syntax string 35 | 36 | // Help defines the Help() command description field. 37 | Help string 38 | 39 | // Fn defines the command handler. 40 | Fn CmdFn 41 | } 42 | 43 | var cmds = make(map[string]*Cmd) 44 | 45 | // Add registers a terminal interface command. 46 | func Add(cmd Cmd) { 47 | cmds[cmd.Name] = &cmd 48 | } 49 | 50 | // Help returns a formatted string with instructions for all registered 51 | // commands. 52 | func (c *Interface) Help(_ *Interface, _ []string) (_ string, _ error) { 53 | var help bytes.Buffer 54 | var names []string 55 | 56 | t := tabwriter.NewWriter(&help, 16, 8, 0, ' ', tabwriter.TabIndent) 57 | 58 | for name, _ := range cmds { 59 | names = append(names, name) 60 | } 61 | 62 | sort.Strings(names) 63 | 64 | for _, name := range names { 65 | _, _ = fmt.Fprintf(t, "%s\t%s\t # %s\n", cmds[name].Name, cmds[name].Syntax, cmds[name].Help) 66 | } 67 | 68 | _ = t.Flush() 69 | res := help.String() 70 | 71 | switch { 72 | case c.Terminal != nil: 73 | res = string(c.Terminal.Escape.Cyan) + res + string(c.Terminal.Escape.Reset) 74 | fmt.Fprintln(c.Output, res) 75 | case c.Console != nil: 76 | c.Console.SetAttribute(uefi.EFI_LIGHTCYAN) 77 | fmt.Fprintln(c.Output, res) 78 | c.Console.SetAttribute(uefi.EFI_WHITE) 79 | } 80 | 81 | return 82 | } 83 | -------------------------------------------------------------------------------- /shell/terminal.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | // Package shell implements a terminal console handler for user defined 7 | // commands. 8 | package shell 9 | 10 | import ( 11 | "errors" 12 | "fmt" 13 | "io" 14 | "log" 15 | "os" 16 | "sync" 17 | 18 | "golang.org/x/term" 19 | 20 | "github.com/usbarmory/go-boot/uefi" 21 | ) 22 | 23 | // DefaultPrompt represents the command prompt when none is set for the 24 | // Interface instance. 25 | var DefaultPrompt = "> " 26 | 27 | // Interface represents a terminal interface. 28 | type Interface struct { 29 | // Prompt represents the command prompt 30 | Prompt string 31 | // Banner represents the welcome message 32 | Banner string 33 | 34 | // Log represents the interface log file 35 | Log *os.File 36 | 37 | // ReadWriter represents the terminal connection 38 | ReadWriter io.ReadWriter 39 | 40 | // Output represents the interface output 41 | Output io.Writer 42 | // Terminal represents the VT100 terminal output 43 | Terminal *term.Terminal 44 | // Console represents the UEFI Console 45 | Console *uefi.Console 46 | 47 | once sync.Once 48 | } 49 | 50 | func (c *Interface) handleLine(line string) (err error) { 51 | var match *Cmd 52 | var arg []string 53 | var res string 54 | 55 | for _, cmd := range cmds { 56 | if cmd.Pattern == nil { 57 | if cmd.Name == line { 58 | match = cmd 59 | break 60 | } 61 | } else if m := cmd.Pattern.FindStringSubmatch(line); len(m) > 0 && (len(m)-1 == cmd.Args) { 62 | match = cmd 63 | arg = m[1:] 64 | break 65 | } 66 | } 67 | 68 | if match == nil { 69 | return errors.New("unknown command, type `help`") 70 | } 71 | 72 | if res, err = match.Fn(c, arg); err != nil { 73 | return 74 | } 75 | 76 | fmt.Fprintln(c.Output, res) 77 | 78 | return 79 | } 80 | 81 | func (c *Interface) readLine(t *term.Terminal) error { 82 | switch { 83 | case c.Terminal != nil: 84 | fmt.Fprint(c.Output, string(c.Terminal.Escape.Red)+c.Prompt+string(c.Terminal.Escape.Reset)) 85 | case c.Console != nil: 86 | c.Console.SetAttribute(uefi.EFI_RED) 87 | fmt.Fprint(c.Output, c.Prompt) 88 | c.Console.SetAttribute(uefi.EFI_WHITE) 89 | default: 90 | fmt.Fprint(c.Output, c.Prompt) 91 | } 92 | 93 | s, err := t.ReadLine() 94 | 95 | if err == io.EOF { 96 | return err 97 | } 98 | 99 | if err != nil { 100 | log.Printf("readline error, %v", err) 101 | return nil 102 | } 103 | 104 | if err = c.handleLine(s); err != nil { 105 | if err == io.EOF { 106 | return err 107 | } 108 | 109 | fmt.Fprintf(c.Output, "command error, %v\n", err) 110 | return nil 111 | } 112 | 113 | return nil 114 | } 115 | 116 | // Exec executes an individual command. 117 | func (c *Interface) Exec(cmd []byte) { 118 | if err := c.handleLine(string(cmd)); err != nil { 119 | fmt.Fprintf(c.Output, "command error (%s), %v\n", cmd, err) 120 | } 121 | } 122 | 123 | func (c *Interface) handle(t *term.Terminal) { 124 | if len(c.Prompt) == 0 { 125 | c.Prompt = DefaultPrompt 126 | } 127 | 128 | if c.Terminal != nil { 129 | c.Output = c.Terminal 130 | } else { 131 | c.Output = c.ReadWriter 132 | } 133 | 134 | fmt.Fprintf(t, "\n%s\n\n", c.Banner) 135 | c.Help(nil, nil) 136 | 137 | c.once.Do(func() { 138 | Add(Cmd{ 139 | Name: "help", 140 | Help: "this help", 141 | Fn: c.Help, 142 | }) 143 | }) 144 | 145 | for { 146 | if err := c.readLine(t); err != nil { 147 | return 148 | } 149 | } 150 | } 151 | 152 | // Start handles registered commands over the interface Terminal or ReadWriter, 153 | // the argument specifies whether ReadWriter is VT100 compatible. 154 | func (c *Interface) Start(vt100 bool) { 155 | switch { 156 | case c.Terminal != nil: 157 | c.handle(c.Terminal) 158 | case c.ReadWriter != nil: 159 | t := term.NewTerminal(c.ReadWriter, "") 160 | 161 | if vt100 { 162 | c.Terminal = t 163 | } 164 | 165 | c.handle(t) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /uapi/entry.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | // Package uapi implements Boot Loader Entries parsing 7 | // following the specifications at: 8 | // 9 | // https://uapi-group.org/specifications/specs/boot_loader_specification 10 | package uapi 11 | 12 | import ( 13 | "fmt" 14 | "io/fs" 15 | "strings" 16 | ) 17 | 18 | // Entry represents the contents loaded from a Type #1 Boot Loader Entry. 19 | type Entry struct { 20 | // Title is the human-readable entry title. 21 | Title string 22 | // Linux is the kernel image to execute. 23 | Linux []byte 24 | // Initrd is the ramdisk cpio image, multiple entries are concatenated. 25 | Initrd []byte 26 | // Options is the kernel parameters. 27 | Options string 28 | 29 | parsed string 30 | ignored string 31 | 32 | fsys fs.FS 33 | } 34 | 35 | func (e *Entry) loadKeyValue(v string) ([]byte, error) { 36 | v = strings.ReplaceAll(v, `/`, `\`) 37 | return fs.ReadFile(e.fsys, v) 38 | } 39 | 40 | func (e *Entry) parseKey(line string) (err error) { 41 | kv := strings.SplitN(line, " ", 2) 42 | 43 | if len(kv) < 2 { 44 | return 45 | } 46 | 47 | k := kv[0] 48 | v := strings.Trim(kv[1], "\n\r") 49 | v = strings.TrimSpace(v) 50 | 51 | switch k { 52 | case "title": 53 | e.Title = v 54 | case "linux": 55 | if e.Linux, err = e.loadKeyValue(v); err != nil { 56 | return 57 | } 58 | case "initrd": 59 | var initrd []byte 60 | 61 | if initrd, err = e.loadKeyValue(v); err != nil { 62 | return 63 | } 64 | 65 | e.Initrd = append(e.Initrd, initrd...) 66 | case "options": 67 | e.Options += v 68 | default: 69 | e.ignored += line 70 | return 71 | } 72 | 73 | e.parsed += line 74 | 75 | return 76 | } 77 | 78 | // String returns the successfully parsed entry keys. 79 | func (e *Entry) String() string { 80 | return e.parsed 81 | } 82 | 83 | // Ignored returns the entry keys ignored during parsing. 84 | func (e *Entry) Ignored() string { 85 | return e.ignored 86 | } 87 | 88 | // LoadEntry parses Type #1 Boot Loader Specification Entries from the argument 89 | // file and loads each key contents from the argument file system. 90 | func LoadEntry(fsys fs.FS, path string) (e *Entry, err error) { 91 | e = &Entry{ 92 | fsys: fsys, 93 | } 94 | 95 | entry, err := fs.ReadFile(fsys, path) 96 | 97 | if err != nil { 98 | return nil, fmt.Errorf("error reading entry file, %v", err) 99 | } 100 | 101 | for line := range strings.Lines(string(entry)) { 102 | if err = e.parseKey(line); err != nil { 103 | return nil, fmt.Errorf("error parsing entry line, %v line:%s\n", err, line) 104 | } 105 | } 106 | 107 | return 108 | } 109 | -------------------------------------------------------------------------------- /uefi/README.md: -------------------------------------------------------------------------------- 1 | TamaGo - bare metal Go - UEFI x64 support 2 | ========================================= 3 | 4 | go-boot | https://github.com/usbarmory/go-boot 5 | 6 | Copyright (c) WithSecure Corporation 7 | 8 | ![TamaGo gopher](https://github.com/usbarmory/tamago/wiki/images/tamago.svg?sanitize=true) 9 | 10 | Authors 11 | ======= 12 | 13 | Andrea Barisani 14 | andrea@inversepath.com 15 | 16 | Introduction 17 | ============ 18 | 19 | TamaGo is a framework that enables compilation and execution of unencumbered Go 20 | applications on bare metal AMD64/ARM/RISC-V processors. 21 | 22 | The [uefi](https://github.com/usbarmory/go-boot/tree/main/uefi) and 23 | [uefi/x64](https://github.com/usbarmory/go-boot/tree/main/uefi/x64) 24 | packages provides support for unikernels running under the Unified Extensible 25 | Firmware Interface [UEFI](https://uefi.org/) on an AMD64 core. 26 | 27 | Documentation 28 | ============= 29 | 30 | [![Go Reference](https://pkg.go.dev/badge/github.com/usbarmory/go-boot/uefi.svg)](https://pkg.go.dev/github.com/usbarmory/go-boot/uefi) 31 | 32 | For more information about TamaGo see its 33 | [repository](https://github.com/usbarmory/tamago) and 34 | [project wiki](https://github.com/usbarmory/tamago/wiki). 35 | 36 | For usage of these packages in the context of an UEFI application see the 37 | [go-boot](https://github.com/usbarmory/go-boot) unikernel project. 38 | 39 | The package API documentation can be found on 40 | [pkg.go.dev](https://pkg.go.dev/github.com/usbarmory/go-boot). 41 | 42 | Compiling 43 | ========= 44 | 45 | Go applications are simply required to import, the relevant board package to 46 | ensure that hardware initialization and runtime support take place: 47 | 48 | ```golang 49 | import ( 50 | _ "github.com/usbarmory/go-boot/uefi/x64" 51 | ) 52 | ``` 53 | 54 | Build the [TamaGo compiler](https://github.com/usbarmory/tamago-go) 55 | (or use the [latest binary release](https://github.com/usbarmory/tamago-go/releases/latest)): 56 | 57 | ``` 58 | wget https://github.com/usbarmory/tamago-go/archive/refs/tags/latest.zip 59 | unzip latest.zip 60 | cd tamago-go-latest/src && ./all.bash 61 | cd ../bin && export TAMAGO=`pwd`/go 62 | ``` 63 | 64 | Go applications can be compiled as usual, using the compiler built in the 65 | previous step, but with the addition of the following flags/variables: 66 | 67 | ``` 68 | GOOS=tamago GOARCH=amd64 ${TAMAGO} build -ldflags "-E cpuinit -T $(TEXT_START) -R 0x1000" main.go 69 | ``` 70 | 71 | The resulting ELF must be converted to a PE32+ executable for EFI for execution 72 | under UEFI: 73 | 74 | ``` 75 | objcopy \ 76 | --strip-debug \ 77 | --target efi-app-x86_64 \ 78 | --subsystem=efi-app \ 79 | --image-base 0x$(IMAGE_BASE) \ 80 | --stack=0x10000 \ 81 | main main.efi 82 | printf '\x26\x02' | dd of=${APP}.efi bs=1 seek=150 count=2 conv=notrunc,fsync # adjust Characteristics 83 | ``` 84 | 85 | An example application, targeting the UEFI environment, 86 | is [go-boot](https://github.com/usbarmory/go-boot). 87 | 88 | License 89 | ======= 90 | 91 | go-boot | https://github.com/usbarmory/go-boot 92 | Copyright (c) WithSecure Corporation 93 | 94 | These source files are distributed under the BSD-style license found in the 95 | [LICENSE](https://github.com/usbarmory/go-boot/blob/main/LICENSE) file. 96 | 97 | The TamaGo logo is adapted from the Go gopher designed by Renee French and 98 | licensed under the Creative Commons 3.0 Attributions license. Go Gopher vector 99 | illustration by Hugo Arganda. 100 | -------------------------------------------------------------------------------- /uefi/config.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | import ( 9 | "encoding/binary" 10 | "errors" 11 | "fmt" 12 | 13 | "github.com/usbarmory/tamago/dma" 14 | ) 15 | 16 | // Configuration represents an EFI Configuration Table. 17 | type ConfigurationTable struct { 18 | GUID [16]byte 19 | VendorTable uint64 20 | } 21 | 22 | // RegistryFormat returns the table EFI GUID in registry format. 23 | func (d *ConfigurationTable) RegistryFormat() string { 24 | // https://uefi.org/specs/UEFI/2.10/Apx_A_GUID_and_Time_Formats.html 25 | return fmt.Sprintf("%08x-%04x-%04x-%x-%x", 26 | binary.LittleEndian.Uint32(d.GUID[0:4]), 27 | binary.LittleEndian.Uint16(d.GUID[4:6]), 28 | binary.LittleEndian.Uint16(d.GUID[6:8]), 29 | d.GUID[8:10], 30 | d.GUID[10:]) 31 | } 32 | 33 | // ConfigurationTables returns the EFI Configuration Tables. 34 | func (d *SystemTable) ConfigurationTables() (c []*ConfigurationTable, err error) { 35 | t := &ConfigurationTable{} 36 | 37 | if d.NumberOfTableEntries == 0 || d.ConfigurationTable == 0 { 38 | return nil, errors.New("EFI Configuration Table is invalid") 39 | } 40 | 41 | buf, _ := marshalBinary(t) 42 | entrySize := len(buf) 43 | tableSize := entrySize * int(d.NumberOfTableEntries) 44 | 45 | r, err := dma.NewRegion(uint(d.ConfigurationTable), tableSize, false) 46 | 47 | if err != nil { 48 | return 49 | } 50 | 51 | addr, buf := r.Reserve(tableSize, 0) 52 | defer r.Release(addr) 53 | 54 | for i := 0; i < tableSize; i += entrySize { 55 | if err = unmarshalBinary(buf[i:i+entrySize], t); err != nil { 56 | return 57 | } 58 | 59 | c = append(c, t) 60 | t = &ConfigurationTable{} 61 | } 62 | 63 | return 64 | } 65 | -------------------------------------------------------------------------------- /uefi/console.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "io" 12 | "time" 13 | "unicode/utf16" 14 | ) 15 | 16 | // EFI ConOut offsets 17 | const ( 18 | outputString = 0x08 19 | setMode = 0x20 20 | setAttribute = 0x28 21 | clearScreen = 0x30 22 | ) 23 | 24 | // EFI ConIn offsets 25 | const ( 26 | readKeyStroke = 0x08 27 | ) 28 | 29 | // EFI text attributes 30 | const ( 31 | EFI_BLACK = 0x00 32 | EFI_BLUE = 0x01 33 | EFI_GREEN = 0x02 34 | EFI_CYAN = 0x03 35 | EFI_RED = 0x04 36 | EFI_MAGENTA = 0x05 37 | EFI_BROWN = 0x06 38 | EFI_LIGHTGRAY = 0x07 39 | EFI_BRIGHT = 0x08 40 | EFI_DARKGRAY = 0x08 41 | EFI_LIGHTBLUE = 0x09 42 | EFI_LIGHTGREEN = 0x0a 43 | EFI_LIGHTCYAN = 0x0b 44 | EFI_LIGHTRED = 0x0c 45 | EFI_LIGHTMAGENTA = 0x0d 46 | EFI_YELLOW = 0x0e 47 | EFI_WHITE = 0x0f 48 | ) 49 | 50 | // ASCII control characters 51 | const ( 52 | null = 0x00 53 | bs = 0x08 54 | tab = 0x09 55 | lf = 0x0a 56 | cr = 0x0d 57 | space = 0x20 58 | ) 59 | 60 | // Control Sequence Introducer n D - CUB - Cursor Back 61 | var cub = []byte{0x1b, 0x5b, 0x44, 0x20, 0x1b, 0x5b, 0x44} 62 | 63 | // InputKey represents an EFI Input Key descriptor. 64 | type InputKey struct { 65 | ScanCode uint16 66 | UnicodeChar [2]byte 67 | } 68 | 69 | // Console implements the [io.ReadWriter] interface over EFI Simple Text 70 | // Input/Output protocol. 71 | type Console struct { 72 | io.ReadWriter 73 | 74 | // ForceLine controls whether line feeds (LF) should be supplemented 75 | // with a carriage return (CR). 76 | ForceLine bool 77 | 78 | // ReplaceTabs controls whether Console I/O output should have Tab 79 | // characters replaced with a number of spaces. 80 | ReplaceTabs int 81 | 82 | // In should be set to the EFI SystemTable ConIn address. 83 | In uint64 84 | // Out should be set to the EFI SystemTable ConOut address. 85 | Out uint64 86 | } 87 | 88 | // ClearScreen calls EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.ClearScreen(). 89 | func (c *Console) ClearScreen() error { 90 | if c.Out == 0 { 91 | return nil 92 | } 93 | 94 | status := callService(c.Out+clearScreen, 95 | []uint64{ 96 | c.Out, 97 | }, 98 | ) 99 | 100 | return parseStatus(status) 101 | } 102 | 103 | // SetMode calls EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.SetMode(). 104 | func (c *Console) SetMode(mode uint64) error { 105 | if c.Out == 0 { 106 | return nil 107 | } 108 | 109 | status := callService(c.Out+setMode, 110 | []uint64{ 111 | c.Out, 112 | mode, 113 | }, 114 | ) 115 | 116 | return parseStatus(status) 117 | } 118 | 119 | // SetAttribute calls EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.SetAttribute(). 120 | func (c *Console) SetAttribute(attr uint64) error { 121 | if c.Out == 0 { 122 | return nil 123 | } 124 | 125 | status := callService(c.Out+setAttribute, 126 | []uint64{ 127 | c.Out, 128 | attr, 129 | }, 130 | ) 131 | 132 | return parseStatus(status) 133 | } 134 | 135 | // Input calls EFI_SIMPLE_TEXT_INPUT_PROTOCOL.ReadKeyStroke(). 136 | func (c *Console) Input(k *InputKey) (status uint64) { 137 | if c.In == 0 { 138 | return 139 | } 140 | 141 | return callService(c.In+readKeyStroke, 142 | []uint64{ 143 | c.In, 144 | ptrval(k), 145 | }, 146 | ) 147 | } 148 | 149 | // Output calls EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString(). 150 | func (c *Console) Output(p []byte) (status uint64) { 151 | if p[len(p)-1] != null { 152 | p = append(p, null) 153 | } 154 | 155 | if c.Out == 0 { 156 | return 157 | } 158 | 159 | return callService(c.Out+outputString, 160 | []uint64{ 161 | c.Out, 162 | ptrval(&p[0]), 163 | }, 164 | ) 165 | } 166 | 167 | // Read available data to buffer from console. 168 | func (c *Console) Read(p []byte) (n int, err error) { 169 | k := &InputKey{} 170 | 171 | for n = 0; n < len(p); n += 2 { 172 | status := c.Input(k) 173 | 174 | switch { 175 | case status&0xff == EFI_NOT_READY: 176 | // Compatibility note: 177 | // 178 | // shell.(*Interface).readLine now starves the 179 | // scheduler, however this package currently has no 180 | // need for background goroutines. 181 | // 182 | // In case this becomes undesirable here add: 183 | // runtime.Gosched() 184 | // 185 | // For now we just take an atomic nap as that eases a 186 | // benign HeapAlloc increase due to GC starvation. 187 | time.Sleep(1 * time.Millisecond) 188 | return 189 | case status != EFI_SUCCESS: 190 | return n, parseStatus(status) 191 | case k.ScanCode > 0: 192 | binary.LittleEndian.PutUint16(p[n:], k.ScanCode) 193 | default: 194 | copy(p[n:], k.UnicodeChar[:]) 195 | } 196 | } 197 | 198 | return 199 | } 200 | 201 | // Write data from buffer to console. 202 | func (c *Console) Write(p []byte) (n int, err error) { 203 | var s []byte 204 | 205 | if len(p) == 0 { 206 | return 207 | } 208 | 209 | if len(p) == len(cub) && bytes.Equal(cub, p) { 210 | p = []byte{bs, 0x00} 211 | } 212 | 213 | // we receive an UTF-8 string and can output UTF-16 214 | b := utf16.Encode([]rune(string(p))) 215 | 216 | for _, r := range b { 217 | if r == tab && c.ReplaceTabs > 0 { 218 | for i := 0; i < c.ReplaceTabs; i++ { 219 | s = append(s, []byte{space, 0x00}...) 220 | } 221 | continue 222 | } 223 | 224 | s = append(s, byte(r&0xff)) 225 | s = append(s, byte(r>>8)) 226 | 227 | if r == lf && c.ForceLine { 228 | s = append(s, []byte{cr, 0x00}...) 229 | } 230 | } 231 | 232 | if status := c.Output(s); status != EFI_SUCCESS { 233 | return n, parseStatus(status) 234 | } 235 | 236 | return 237 | } 238 | -------------------------------------------------------------------------------- /uefi/desc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "errors" 12 | "unicode/utf16" 13 | 14 | "github.com/usbarmory/tamago/dma" 15 | ) 16 | 17 | const align = 8 18 | 19 | func toUTF16(s string) (buf []byte) { 20 | for _, r := range utf16.Encode([]rune(s)) { 21 | buf = append(buf, byte(r&0xff)) 22 | buf = append(buf, byte(r>>8)) 23 | } 24 | 25 | return append([]byte(buf), []byte{0x00, 0x00}...) 26 | } 27 | 28 | func marshalBinary(data any) (buf []byte, err error) { 29 | b := new(bytes.Buffer) 30 | err = binary.Write(b, binary.LittleEndian, data) 31 | return b.Bytes(), err 32 | } 33 | 34 | func unmarshalBinary(buf []byte, data any) (err error) { 35 | _, err = binary.Decode(buf, binary.LittleEndian, data) 36 | return 37 | } 38 | 39 | func encode(data any, addr uint64) (err error) { 40 | if addr == 0 { 41 | return errors.New("invalid address") 42 | } 43 | 44 | buf, _ := marshalBinary(data) 45 | n := len(buf) 46 | 47 | r, err := dma.NewRegion(uint(addr), n, false) 48 | 49 | if err != nil { 50 | return 51 | } 52 | 53 | ptr := r.Alloc(buf, 0) 54 | r.Free(ptr) 55 | 56 | return 57 | } 58 | 59 | func decode(data any, addr uint64) (err error) { 60 | if addr == 0 { 61 | return errors.New("invalid address") 62 | } 63 | 64 | t, _ := marshalBinary(data) 65 | n := len(t) + (len(t) % align) 66 | 67 | r, err := dma.NewRegion(uint(addr), n, false) 68 | 69 | if err != nil { 70 | return 71 | } 72 | 73 | ptr, buf := r.Reserve(len(t), 0) 74 | defer r.Release(ptr) 75 | 76 | return unmarshalBinary(buf, data) 77 | } 78 | -------------------------------------------------------------------------------- /uefi/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | import ( 9 | "fmt" 10 | ) 11 | 12 | // EFI Status Codes 13 | const ( 14 | EFI_SUCCESS = iota 15 | EFI_LOAD_ERROR 16 | EFI_INVALID_PARAMETER 17 | EFI_UNSUPPORTED 18 | EFI_BAD_BUFFER_SIZE 19 | EFI_BUFFER_TOO_SMALL 20 | EFI_NOT_READY 21 | EFI_DEVICE_ERROR 22 | EFI_WRITE_PROTECTED 23 | EFI_OUT_OF_RESOURCES 24 | EFI_VOLUME_CORRUPTED 25 | EFI_VOLUME_FULL 26 | EFI_NO_MEDIA 27 | EFI_MEDIA_CHANGED 28 | EFI_NOT_FOUND 29 | EFI_ACCESS_DENIED 30 | EFI_NO_RESPONSE 31 | EFI_NO_MAPPING 32 | EFI_TIMEOUT 33 | EFI_NOT_STARTED 34 | EFI_ALREADY_STARTED 35 | EFI_ABORTED 36 | EFI_ICMP_ERROR 37 | EFI_TFTP_ERROR 38 | EFI_PROTOCOL_ERROR 39 | EFI_INCOMPATIBLE_VERSION 40 | EFI_SECURITY_VIOLATION 41 | EFI_CRC_ERROR 42 | EFI_END_OF_MEDIA 43 | EFI_END_OF_FILE 44 | EFI_INVALID_LANGUAGE 45 | EFI_COMPROMISED_DATA 46 | EFI_IP_ADDRESS_CONFLICT 47 | EFI_HTTP_ERROR 48 | ) 49 | 50 | func parseStatus(status uint64) (err error) { 51 | code := status & 0xff 52 | 53 | if status != EFI_SUCCESS { 54 | err = fmt.Errorf("EFI_STATUS error %#x (%d)", status, code) 55 | } 56 | 57 | return 58 | } 59 | -------------------------------------------------------------------------------- /uefi/exit.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | // EFI Boot Services offsets 9 | const ( 10 | exit = 0xd8 11 | exitBootServices = 0xe8 12 | ) 13 | 14 | // Exit calls EFI_BOOT_SERVICES.Exit(). 15 | func (s *BootServices) Exit(code int) (err error) { 16 | status := callService(s.base+exit, 17 | []uint64{ 18 | uint64(s.imageHandle), 19 | uint64(code), 20 | 0, 21 | 0, 22 | }, 23 | ) 24 | 25 | return parseStatus(status) 26 | } 27 | 28 | // ExitServices calls EFI_BOOT_SERVICES.ExitBootServices(), it is the caller 29 | // responsability to avoid using any EFI Boot Service after this call is 30 | // successful. 31 | func (s *BootServices) ExitBootServices() (memoryMap *MemoryMap, err error) { 32 | if memoryMap, err = s.GetMemoryMap(); err != nil { 33 | return 34 | } 35 | 36 | status := callService(s.base+exitBootServices, 37 | []uint64{ 38 | uint64(s.imageHandle), 39 | memoryMap.MapKey, 40 | }, 41 | ) 42 | 43 | return memoryMap, parseStatus(status) 44 | } 45 | -------------------------------------------------------------------------------- /uefi/file.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "io" 12 | "io/fs" 13 | "time" 14 | ) 15 | 16 | const ( 17 | EFI_FILE_INFO_ID = "09576e92-6d3f-11d2-8e39-00a0c969723b" 18 | 19 | EFI_FILE_PROTOCOL_REVISION = 0x00010000 20 | EFI_FILE_PROTOCOL_REVISION2 = 0x00020000 21 | 22 | EFI_FILE_MODE_READ = 0x0000000000000001 23 | EFI_FILE_MODE_WRITE = 0x0000000000000002 24 | EFI_FILE_MODE_CREATE = 0x8000000000000000 25 | 26 | EFI_FILE_DIRECTORY = 0x0000000000000010 27 | ) 28 | 29 | // fileProtocol represents an EFI File Protocol instance. 30 | type fileProtocol struct { 31 | Revision uint64 32 | Open uint64 33 | Close uint64 34 | Delete uint64 35 | Read uint64 36 | Write uint64 37 | GetPosition uint64 38 | SetPosition uint64 39 | GetInfo uint64 40 | SetInfo uint64 41 | Flush uint64 42 | } 43 | 44 | // efiTime represents an EFI_TIME instance. 45 | type efiTime struct { 46 | Year uint16 47 | Month uint8 48 | Day uint8 49 | Hour uint8 50 | Minute uint8 51 | Second uint8 52 | _ uint8 53 | Nanosecond uint8 54 | TimeZone int16 55 | Daylight uint8 56 | _ uint32 57 | } 58 | 59 | // fileInfo represents an EFI_FILE_INFO instance. 60 | type fileInfo struct { 61 | Size uint64 62 | FileSize uint64 63 | PhysicalSize uint64 64 | CreateTime efiTime 65 | LastAccessTime efiTime 66 | ModificationTime efiTime 67 | Attribute uint64 68 | } 69 | 70 | // open calls EFI_FILE_PROTOCOL.Open(). 71 | func (f *fileProtocol) open(handle uint64, name string, mode uint64) (o *fileProtocol, addr uint64, err error) { 72 | fileName := toUTF16(name) 73 | 74 | status := callService(ptrval(&f.Open), 75 | []uint64{ 76 | handle, 77 | ptrval(&addr), 78 | ptrval(&fileName[0]), 79 | mode, 80 | 0, 81 | }, 82 | ) 83 | 84 | if err = parseStatus(status); err != nil { 85 | return 86 | } 87 | 88 | o = &fileProtocol{} 89 | 90 | if err = decode(o, addr); err != nil { 91 | return 92 | } 93 | 94 | if o.Revision != EFI_FILE_PROTOCOL_REVISION && o.Revision != EFI_FILE_PROTOCOL_REVISION2 { 95 | return nil, 0, fmt.Errorf("invalid protocol revision (%x)", f) 96 | } 97 | 98 | return 99 | } 100 | 101 | // close calls EFI_FILE_PROTOCOL.Close(). 102 | func (f *fileProtocol) close(handle uint64) (err error) { 103 | status := callService(ptrval(&f.Close), 104 | []uint64{ 105 | handle, 106 | }, 107 | ) 108 | 109 | return parseStatus(status) 110 | } 111 | 112 | // read calls EFI_FILE_PROTOCOL.Read(). 113 | func (f *fileProtocol) read(handle uint64, buf []byte) (n int, err error) { 114 | size := uint64(len(buf)) 115 | 116 | status := callService(ptrval(&f.Read), 117 | []uint64{ 118 | handle, 119 | ptrval(&size), 120 | ptrval(&buf[0]), 121 | }, 122 | ) 123 | 124 | if status == EFI_DEVICE_ERROR || size == 0 { 125 | return 0, io.EOF 126 | } 127 | 128 | return int(size), parseStatus(status) 129 | } 130 | 131 | // getInfo calls EFI_FILE SYSTEM_PROTOCOL.GetInfo(). 132 | func (f *fileProtocol) getInfo(handle uint64, guid []byte) (info *fileInfo, err error) { 133 | buf := make([]byte, 8*7+512) 134 | size := uint64(len(buf)) 135 | 136 | status := callService(ptrval(&f.GetInfo), 137 | []uint64{ 138 | handle, 139 | ptrval(&guid[0]), 140 | ptrval(&size), 141 | ptrval(&buf[0]), 142 | }, 143 | ) 144 | 145 | if err = parseStatus(status); err != nil { 146 | return 147 | } 148 | 149 | info = &fileInfo{} 150 | err = unmarshalBinary(buf[0:size], info) 151 | 152 | return 153 | } 154 | 155 | // File implements the [fs.File] interface for the EFI File Protocol. 156 | type File struct { 157 | file *fileProtocol 158 | addr uint64 159 | name string 160 | } 161 | 162 | // FileInfo implements the [fs.FileInfo] interface for the EFI File Protocol. 163 | type FileInfo struct { 164 | info *fileInfo 165 | addr uint64 166 | name string 167 | } 168 | 169 | // Name returns the name of the file as presented to Open. 170 | func (fi *FileInfo) Name() string { 171 | return fi.name 172 | } 173 | 174 | // Size returns the file length in bytes. 175 | func (fi *FileInfo) Size() int64 { 176 | return int64(fi.info.FileSize) 177 | } 178 | 179 | // Mode returns the file mode bits. 180 | func (fi *FileInfo) Mode() fs.FileMode { 181 | if fi.IsDir() { 182 | return fs.ModeDir 183 | } 184 | 185 | return 0 186 | } 187 | 188 | // ModTime returns the file modification time. 189 | func (fi *FileInfo) ModTime() time.Time { 190 | m := fi.info.ModificationTime 191 | tz := time.FixedZone("tz", int(m.TimeZone)) 192 | 193 | return time.Date( 194 | int(m.Year), 195 | time.Month(m.Month), 196 | int(m.Day), 197 | int(m.Hour), 198 | int(m.Minute), 199 | int(m.Second), 200 | int(m.Nanosecond), 201 | tz, 202 | ) 203 | } 204 | 205 | // IsDir reports whether fi describes a directory. 206 | func (fi *FileInfo) IsDir() bool { 207 | return (fi.info.Attribute & EFI_FILE_DIRECTORY) > 0 208 | } 209 | 210 | // Sys returns the underlying data source pointer. 211 | func (fi *FileInfo) Sys() any { 212 | return fi.addr 213 | } 214 | 215 | // Stat returns a FileInfo describing the named file from the file system. 216 | func (f *File) Stat() (fs.FileInfo, error) { 217 | var err error 218 | 219 | fi := &FileInfo{ 220 | name: f.name, 221 | addr: f.addr, 222 | } 223 | 224 | if f.addr == 0 { 225 | return nil, errors.New("invalid file instance") 226 | } 227 | 228 | infoType := GUID(EFI_FILE_INFO_ID).Bytes() 229 | 230 | if fi.info, err = f.file.getInfo(f.addr, infoType); err != nil { 231 | return nil, err 232 | } 233 | 234 | return fs.FileInfo(fi), nil 235 | } 236 | 237 | // Read reads up to len(b) bytes from the File and stores them in b. It returns 238 | // the number of bytes read and any error encountered. At end of file, Read 239 | // returns 0, io.EOF. 240 | func (f *File) Read(b []byte) (n int, err error) { 241 | if f.addr == 0 { 242 | return 0, errors.New("invalid file instance") 243 | } 244 | 245 | return f.file.read(f.addr, b) 246 | } 247 | 248 | // Close closes the File, rendering it unusable for I/O. 249 | func (f *File) Close() (err error) { 250 | if f.addr == 0 { 251 | return errors.New("invalid file instance") 252 | } 253 | 254 | return f.file.close(f.addr) 255 | } 256 | -------------------------------------------------------------------------------- /uefi/fs.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "io/fs" 12 | ) 13 | 14 | const ( 15 | EFI_LOADED_IMAGE_PROTOCOL_GUID = "5b1b31a1-9562-11d2-8e3f-00a0c969723b" 16 | EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID = "09576e91-6d3f-11d2-8e39-00a0c969723b" 17 | EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID = "964e5b22-6459-11d2-8e39-00a0c969723b" 18 | 19 | EFI_LOADED_IMAGE_PROTOCOL_REVISION = 0x00001000 20 | EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION = 0x00010000 21 | ) 22 | 23 | // loadedImage represents an EFI Loaded Image Protocol instance. 24 | type loadedImage struct { 25 | Revision uint32 26 | _ uint32 27 | ParentHandle uint64 28 | SystemTable uint64 29 | DeviceHandle uint64 30 | FilePath uint64 31 | _ uint64 32 | LoadOptionsSize uint32 33 | _ uint32 34 | LoadOptions uint64 35 | ImageBase uint64 36 | ImageSize uint64 37 | ImageCodeType uint64 38 | ImageDataType uint64 39 | Unload uint64 40 | } 41 | 42 | // simpleFileSystem represents an EFI Simple File System Protocol instance. 43 | type simpleFileSystem struct { 44 | Revision uint64 45 | OpenVolume uint64 46 | } 47 | 48 | // openVolume calls EFI_SIMPLE_FILE SYSTEM_PROTOCOL.OpenVolume(). 49 | func (root *simpleFileSystem) openVolume(handle uint64) (f *fileProtocol, addr uint64, err error) { 50 | status := callService(ptrval(&root.OpenVolume), 51 | []uint64{ 52 | handle, 53 | ptrval(&addr), 54 | }, 55 | ) 56 | 57 | if err = parseStatus(status); err != nil { 58 | return 59 | } 60 | 61 | f = &fileProtocol{} 62 | 63 | if err = decode(f, addr); err != nil { 64 | return 65 | } 66 | 67 | if f.Revision != EFI_FILE_PROTOCOL_REVISION && f.Revision != EFI_FILE_PROTOCOL_REVISION2 { 68 | return nil, 0, fmt.Errorf("invalid protocol revision (%x)", f) 69 | } 70 | 71 | return 72 | } 73 | 74 | // FS implements the [fs.FS] interface for an EFI Simple File System. 75 | type FS struct { 76 | image *loadedImage 77 | device uint64 78 | addr uint64 79 | 80 | fs *simpleFileSystem 81 | volume *File 82 | } 83 | 84 | // Open opens the named file [File.Close] must be called to release any 85 | // associated resources. 86 | func (root *FS) Open(name string) (fs.File, error) { 87 | var err error 88 | 89 | f := &File{ 90 | name: name, 91 | } 92 | 93 | if root.volume == nil || root.volume.file == nil || root.volume.addr == 0 { 94 | return nil, errors.New("invalid file system instance") 95 | } 96 | 97 | if f.file, f.addr, err = root.volume.file.open(root.volume.addr, name, EFI_FILE_MODE_READ); err != nil { 98 | return nil, err 99 | } 100 | 101 | return fs.File(f), nil 102 | } 103 | 104 | func (s *BootServices) loadImageHandle(imageHandle uint64) (image *loadedImage, err error) { 105 | var addr uint64 106 | 107 | if addr, err = s.HandleProtocol(imageHandle, EFI_LOADED_IMAGE_PROTOCOL_GUID); err != nil { 108 | return 109 | } 110 | 111 | image = &loadedImage{} 112 | 113 | if err = decode(image, addr); err != nil { 114 | return 115 | } 116 | 117 | if image.Revision != EFI_LOADED_IMAGE_PROTOCOL_REVISION { 118 | return nil, errors.New("invalid protocol revision") 119 | } 120 | 121 | return 122 | } 123 | 124 | // Root returns an EFI Simple File System instance for the current EFI image 125 | // root volume. 126 | func (s *Services) Root() (root *FS, err error) { 127 | root = &FS{ 128 | fs: &simpleFileSystem{}, 129 | volume: &File{}, 130 | } 131 | 132 | if root.image, err = s.Boot.loadImageHandle(s.imageHandle); err != nil { 133 | return 134 | } 135 | 136 | if root.device, err = s.Boot.HandleProtocol(root.image.DeviceHandle, EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID); err != nil { 137 | return 138 | } 139 | 140 | if root.addr, err = s.Boot.HandleProtocol(root.image.DeviceHandle, EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID); err != nil { 141 | return 142 | } 143 | 144 | if err = decode(root.fs, root.addr); err != nil { 145 | return 146 | } 147 | 148 | if root.fs.Revision != EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION { 149 | return nil, errors.New("invalid protocol revision") 150 | } 151 | 152 | if root.volume.file, root.volume.addr, err = root.fs.openVolume(root.addr); err != nil { 153 | return 154 | } 155 | 156 | return 157 | } 158 | -------------------------------------------------------------------------------- /uefi/graphics.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | const EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID = "9042a9de-23dc-4a38-96fb-7aded080516a" 9 | 10 | // EFI Graphics Output Protocol offsets 11 | const ( 12 | blt = 0x10 13 | ) 14 | 15 | type BltOperation int 16 | 17 | // EFI_GRAPHICS_OUTPUT_BLT_OPERATION 18 | const ( 19 | EfiBltVideoFill = iota 20 | EfiBltVideoToBltBuffer 21 | EfiBltBufferToVideo 22 | EfiBltVideoToVideo 23 | EfiGraphicsOutputBltOperationMax 24 | ) 25 | 26 | // ModeInformation represents an EFI Graphics Output Mode Information instance. 27 | type ModeInformation struct { 28 | Version uint32 29 | HorizontalResolution uint32 30 | VerticalResolution uint32 31 | PixelFormat uint32 32 | RedMask uint32 33 | GreenMask uint32 34 | BlueMask uint32 35 | ReservedMask uint32 36 | PixelsPerScanLine uint32 37 | } 38 | 39 | // ProtocolMode represents an EFI Graphics Output Protocol Mode instance. 40 | type ProtocolMode struct { 41 | MaxMode uint32 42 | Mode uint32 43 | Info uint64 44 | SizeOfInfo uint64 45 | FrameBufferBase uint64 46 | FrameBufferSize uint64 47 | } 48 | 49 | // GetInfo returns the EFI Graphics Output Mode information instance. 50 | func (d *ProtocolMode) GetInfo() (mi *ModeInformation, err error) { 51 | mi = &ModeInformation{} 52 | err = decode(mi, d.Info) 53 | return 54 | } 55 | 56 | // GraphicsOutput represents an EFI Graphics Output Protocol instance. 57 | type GraphicsOutput struct { 58 | base uint64 59 | mode uint64 60 | } 61 | 62 | // GetMode returns the EFI Graphics Output Mode instance. 63 | func (gop *GraphicsOutput) GetMode() (pm *ProtocolMode, err error) { 64 | pm = &ProtocolMode{} 65 | err = decode(pm, gop.mode) 66 | return 67 | } 68 | 69 | // Blt calls EFI_GRAPHICS_OUTPUT_PROTCOL.Blt(). 70 | func (gop *GraphicsOutput) Blt(buf []byte, op BltOperation, srcX, srcY, dstX, dstY, width, height, delta uint64) (err error) { 71 | if gop.base == 0 { 72 | return nil 73 | } 74 | 75 | status := callService(gop.base+blt, 76 | []uint64{ 77 | gop.base, 78 | ptrval(&buf[0]), 79 | uint64(op), 80 | srcX, 81 | srcY, 82 | dstX, 83 | dstY, 84 | width, 85 | height, 86 | delta, 87 | }, 88 | ) 89 | 90 | return parseStatus(status) 91 | } 92 | 93 | // GetGraphicsOutput locates and returns the EFI Graphics Output Protocol 94 | // instance. 95 | func (s *BootServices) GetGraphicsOutput() (gop *GraphicsOutput, err error) { 96 | gop = &GraphicsOutput{} 97 | 98 | var data struct { 99 | QueryMode uint64 100 | SetMode uint64 101 | Blt uint64 102 | Mode uint64 103 | } 104 | 105 | if gop.base, err = s.LocateProtocol(EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID); err != nil { 106 | return 107 | } 108 | 109 | if err = decode(&data, gop.base); err != nil { 110 | return 111 | } 112 | 113 | gop.mode = data.Mode 114 | 115 | return 116 | } 117 | -------------------------------------------------------------------------------- /uefi/guid.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | import ( 9 | "encoding/hex" 10 | "regexp" 11 | ) 12 | 13 | var guidPattern = regexp.MustCompile(`^([[:xdigit:]]{8})-([[:xdigit:]]{4})-([[:xdigit:]]{4})-([[:xdigit:]]{4})-([[:xdigit:]]{12})$`) 14 | 15 | // GUID represents an EFI GUID (Globally Unique Identifier). 16 | type GUID string 17 | 18 | // Bytes returns the GUID as byte slice. 19 | func (g GUID) Bytes() (guid []byte) { 20 | var buf []byte 21 | var err error 22 | 23 | m := guidPattern.FindStringSubmatch(string(g)) 24 | 25 | if len(m) != 6 { 26 | return make([]byte, 16) 27 | } 28 | 29 | m = m[1:] 30 | 31 | for i, b := range m { 32 | if buf, err = hex.DecodeString(b); err != nil { 33 | return make([]byte, 16) 34 | } 35 | 36 | switch i { 37 | case 0: 38 | guid = append(guid, buf[3]) 39 | guid = append(guid, buf[2]) 40 | guid = append(guid, buf[1]) 41 | guid = append(guid, buf[0]) 42 | case 1, 2: 43 | guid = append(guid, buf[1]) 44 | guid = append(guid, buf[0]) 45 | default: 46 | guid = append(guid, buf...) 47 | } 48 | } 49 | 50 | return 51 | } 52 | 53 | func (g GUID) ptrval() uint64 { 54 | buf := g.Bytes() 55 | return ptrval(&buf[0]) 56 | } 57 | -------------------------------------------------------------------------------- /uefi/image.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | import ( 9 | "io/fs" 10 | ) 11 | 12 | // EFI Boot Services offsets 13 | const ( 14 | loadImage = 0xc8 15 | startImage = 0xd0 16 | ) 17 | 18 | // LoadImage calls EFI_BOOT_SERVICES.LoadImage(). 19 | func (s *BootServices) LoadImage(boot int, root *FS, name string) (imageHandle uint64, err error) { 20 | buf, err := fs.ReadFile(root, name) 21 | 22 | if err != nil { 23 | return 24 | } 25 | 26 | _, _, devicePath, err := root.FilePath(name) 27 | 28 | if err != nil { 29 | return 30 | } 31 | 32 | status := callService(s.base+loadImage, 33 | []uint64{ 34 | uint64(boot), 35 | s.imageHandle, 36 | ptrval(&devicePath[0]), 37 | ptrval(&buf[0]), 38 | uint64(len(buf)), 39 | ptrval(&imageHandle), 40 | }, 41 | ) 42 | 43 | return imageHandle, parseStatus(status) 44 | } 45 | 46 | // StartImage calls EFI_BOOT_SERVICES.StartImage(). 47 | func (s *BootServices) StartImage(imageHandle uint64) (err error) { 48 | status := callService(s.base+startImage, 49 | []uint64{ 50 | imageHandle, 51 | 0, 52 | 0, 53 | }, 54 | ) 55 | 56 | return parseStatus(status) 57 | } 58 | -------------------------------------------------------------------------------- /uefi/mem.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | import ( 9 | "runtime" 10 | 11 | "github.com/u-root/u-root/pkg/boot/bzimage" 12 | ) 13 | 14 | const ( 15 | // EFI Boot Services offset for GetMemoryMap 16 | getMemoryMap = 0x38 17 | maxEntries = 1000 18 | ) 19 | 20 | // Advanced Configuration and Power Interface Specification (ACPI) 21 | // Version 6.0 - Table 15-312 Address Range Types12 22 | const AddressRangePersistentMemory = 7 23 | 24 | // PageSize represents the EFI page size in bytes 25 | const PageSize = 4096 // 4 KiB 26 | 27 | // MemoryDescriptor represents an EFI Memory Descriptor 28 | type MemoryDescriptor struct { 29 | Type uint32 30 | _ uint32 31 | PhysicalStart uint64 32 | VirtualStart uint64 33 | NumberOfPages uint64 34 | Attribute uint64 35 | _ uint64 36 | } 37 | 38 | // End returns the descriptor physical end address. 39 | func (d *MemoryDescriptor) PhysicalEnd() uint64 { 40 | return d.PhysicalStart + d.NumberOfPages*PageSize 41 | } 42 | 43 | // Size returns the descriptor size. 44 | func (d *MemoryDescriptor) Size() int { 45 | return int(d.NumberOfPages * PageSize) 46 | } 47 | 48 | // E820 converts an EFI Memory Map entry to an x86 E820 one suitable for use 49 | // after exiting EFI Boot Services. 50 | func (d *MemoryDescriptor) E820() bzimage.E820Entry { 51 | e := bzimage.E820Entry{ 52 | Addr: d.PhysicalStart, 53 | Size: d.NumberOfPages * PageSize, 54 | } 55 | 56 | // Unified Extensible Firmware Interface (UEFI) Specification 57 | // Version 2.10 - Table 7.10: Memory Type Usage after ExitBootServices() 58 | switch d.Type { 59 | case EfiLoaderCode, EfiLoaderData, EfiBootServicesCode, EfiBootServicesData, EfiConventionalMemory: 60 | e.MemType = bzimage.RAM 61 | case EfiPersistentMemory: 62 | e.MemType = AddressRangePersistentMemory 63 | case EfiACPIReclaimMemory: 64 | e.MemType = bzimage.ACPI 65 | case EfiACPIMemoryNVS: 66 | e.MemType = bzimage.NVS 67 | default: 68 | e.MemType = bzimage.Reserved 69 | } 70 | 71 | return e 72 | } 73 | 74 | // MemoryMap represents an EFI Memory Map 75 | type MemoryMap struct { 76 | MapSize uint64 77 | Descriptors []*MemoryDescriptor 78 | MapKey uint64 79 | DescriptorSize uint64 80 | DescriptorVersion uint32 81 | 82 | buf []byte 83 | } 84 | 85 | // Address returns the EFI Memory Map pointer. 86 | func (m *MemoryMap) Address() uint64 { 87 | return ptrval(&m.buf[0]) 88 | } 89 | 90 | // E820 converts an EFI Memory Map to an x86 E820 one suitable for use 91 | // after exiting EFI Boot Services. 92 | func (m *MemoryMap) E820() (e820 []bzimage.E820Entry) { 93 | var prev *bzimage.E820Entry 94 | 95 | // When defragging we join unikernel allocation as a single page to 96 | // ease its skipping during boot manager memory reservation. 97 | ramStart, ramEnd := runtime.MemRegion() 98 | 99 | for _, desc := range m.Descriptors { 100 | entry := desc.E820() 101 | 102 | if prev != nil { 103 | // join and isolate runtime.MemRegion() 104 | if (entry.Addr != ramStart && prev.Addr+prev.Size != ramEnd) && 105 | // join adjacent entries 106 | (prev.MemType == entry.MemType && prev.Addr+prev.Size == entry.Addr) { 107 | // increase size of previous entry 108 | e820[len(e820)-1].Size += entry.Size 109 | prev = &e820[len(e820)-1] 110 | continue 111 | } 112 | } 113 | 114 | prev = &entry 115 | e820 = append(e820, entry) 116 | } 117 | 118 | return 119 | } 120 | 121 | // GetMemoryMap calls EFI_BOOT_SERVICES.GetMemoryMap(). 122 | func (s *BootServices) GetMemoryMap() (m *MemoryMap, err error) { 123 | d := &MemoryDescriptor{} 124 | t, _ := marshalBinary(d) 125 | n := len(t) 126 | 127 | m = &MemoryMap{ 128 | MapSize: uint64(n * maxEntries), 129 | DescriptorSize: uint64(n), 130 | buf: make([]byte, n*maxEntries), 131 | } 132 | 133 | status := callService(s.base+getMemoryMap, 134 | []uint64{ 135 | ptrval(&m.MapSize), 136 | ptrval(&m.buf[0]), 137 | ptrval(&m.MapKey), 138 | ptrval(&m.DescriptorSize), 139 | ptrval(&m.DescriptorVersion), 140 | }, 141 | ) 142 | 143 | if err = parseStatus(status); err != nil { 144 | return 145 | } 146 | 147 | for i := 0; i < int(m.MapSize); i += n { 148 | if err = unmarshalBinary(m.buf[i:], d); err != nil { 149 | break 150 | } 151 | 152 | m.Descriptors = append(m.Descriptors, d) 153 | d = &MemoryDescriptor{} 154 | } 155 | 156 | return 157 | } 158 | -------------------------------------------------------------------------------- /uefi/pages.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | // EFI Boot Service offsets 9 | const ( 10 | allocatePages = 0x28 11 | freePages = 0x30 12 | ) 13 | 14 | // EFI_ALLOCATE_TYPE 15 | const ( 16 | AllocateAnyPages = iota 17 | AllocateMaxAddress 18 | AllocateAddress 19 | MaxAllocateType 20 | ) 21 | 22 | // EFI_MEMORY_TYPE 23 | const ( 24 | EfiReservedMemoryType = iota 25 | EfiLoaderCode 26 | EfiLoaderData 27 | EfiBootServicesCode 28 | EfiBootServicesData 29 | EfiRuntimeServicesCode 30 | EfiRuntimeServicesData 31 | EfiConventionalMemory 32 | EfiUnusableMemory 33 | EfiACPIReclaimMemory 34 | EfiACPIMemoryNVS 35 | EfiMemoryMappedIO 36 | EfiMemoryMappedIOPortSpace 37 | EfiPalCode 38 | EfiPersistentMemory 39 | EfiUnacceptedMemoryType 40 | EfiMaxMemoryType 41 | ) 42 | 43 | // AllocatePages calls EFI_BOOT_SERVICES.AllocatePages(). 44 | func (s *BootServices) AllocatePages(allocateType int, memoryType int, size int, physicalAddress uint64) error { 45 | status := callService(s.base+allocatePages, 46 | []uint64{ 47 | uint64(allocateType), 48 | uint64(memoryType), 49 | uint64(size) / PageSize, 50 | ptrval(&physicalAddress), 51 | }, 52 | ) 53 | 54 | return parseStatus(status) 55 | } 56 | 57 | // FreePages calls EFI_BOOT_SERVICES.FreePages(). 58 | func (s *BootServices) FreePages(physicalAddress uint64, size int) error { 59 | status := callService(s.base+freePages, 60 | []uint64{ 61 | physicalAddress, 62 | uint64(size) / PageSize, 63 | }, 64 | ) 65 | 66 | return parseStatus(status) 67 | } 68 | -------------------------------------------------------------------------------- /uefi/path.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "errors" 12 | 13 | "github.com/usbarmory/tamago/dma" 14 | ) 15 | 16 | const ( 17 | bufferSize = (1 << 16) 18 | maxDepth = 16 19 | ) 20 | 21 | // DevicePath represents an EFI Generic Device Path Node structure. 22 | type DevicePathNode struct { 23 | Type uint8 24 | SubType uint8 25 | Length uint16 26 | } 27 | 28 | // Bytes converts the descriptor structure to byte array format. 29 | func (d *DevicePathNode) Bytes() []byte { 30 | buf := new(bytes.Buffer) 31 | 32 | binary.Write(buf, binary.LittleEndian, d.Type) 33 | binary.Write(buf, binary.LittleEndian, d.SubType) 34 | binary.Write(buf, binary.LittleEndian, d.Length) 35 | 36 | return buf.Bytes() 37 | } 38 | 39 | // DevicePath represents an EFI Device Path Protocol node. 40 | type DevicePath struct { 41 | DevicePathNode 42 | Data []byte 43 | } 44 | 45 | // While we could use UEFI functions to perform the same, we prefer to keep 46 | // have control on this parsing tiven that UEFI firmware does not handle 47 | // gracefully invalid pointers (e.g. DoS condition). 48 | func (root *FS) devicePath() (devicePath []*DevicePath, desc []byte, err error) { 49 | addr := uint(root.device) 50 | off := uint(0) 51 | 52 | r, err := dma.NewRegion(uint(addr), bufferSize, false) 53 | 54 | if err != nil { 55 | return 56 | } 57 | 58 | defer r.Release(addr) 59 | _, buf := r.Reserve(bufferSize, 0) 60 | 61 | d := &DevicePath{} 62 | 63 | for i := 0; i <= maxDepth; i++ { 64 | if i == maxDepth { 65 | return nil, nil, errors.New("device path nodes limit exceeded") 66 | } 67 | 68 | node := &DevicePathNode{} 69 | 70 | if err = unmarshalBinary(buf[off:off+4], node); err != nil { 71 | return nil, nil, err 72 | } 73 | 74 | if node.Type == 0x7f && // End of Hardware Device Path 75 | node.SubType == 0xff { // End Entire Device Path 76 | break 77 | } 78 | 79 | if node.Length == 0 || node.Length > 0xff { 80 | return nil, nil, errors.New("invalid length") 81 | } 82 | 83 | off += 4 84 | 85 | d.Type = node.Type 86 | d.SubType = node.SubType 87 | d.Length = node.Length 88 | 89 | dataSize := uint(d.Length - 4) 90 | d.Data = make([]byte, dataSize) 91 | 92 | copy(d.Data, buf[off:off+dataSize]) 93 | off += dataSize 94 | 95 | devicePath = append(devicePath, d) 96 | d = &DevicePath{} 97 | } 98 | 99 | desc = make([]byte, off) 100 | copy(desc, buf) 101 | 102 | return 103 | } 104 | 105 | // FilePath represents an EFI File Path Media Device Path instance. 106 | type FilePath struct { 107 | DevicePathNode 108 | PathName []byte 109 | } 110 | 111 | // Bytes converts the descriptor structure to byte array format. 112 | func (d *FilePath) Bytes() []byte { 113 | return append(d.DevicePathNode.Bytes(), d.PathName...) 114 | } 115 | 116 | // FilePath returns the full EFI Device Path associated with the named file. 117 | func (root *FS) FilePath(name string) (devicePath []*DevicePath, filePath *FilePath, desc []byte, err error) { 118 | pathName := toUTF16(name) 119 | 120 | filePath = &FilePath{ 121 | PathName: pathName, 122 | } 123 | 124 | filePath.Type = 0x04 // Media Device Path 125 | filePath.SubType = 0x04 // File Path 126 | filePath.Length = uint16(4 + len(pathName)) 127 | 128 | if devicePath, desc, err = root.devicePath(); err != nil { 129 | return 130 | } 131 | 132 | devicePathEnd := &DevicePathNode{ 133 | Type: 0x7f, // End of Hardware Device Path 134 | SubType: 0xff, // End Entire Device Path 135 | Length: 4, 136 | } 137 | 138 | desc = append(desc, filePath.Bytes()...) 139 | desc = append(desc, devicePathEnd.Bytes()...) 140 | 141 | return 142 | } 143 | -------------------------------------------------------------------------------- /uefi/protocol.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | // EFI Boot Services offsets 9 | const ( 10 | handleProtocol = 0x098 11 | locateProtocol = 0x140 12 | ) 13 | 14 | // HandleProtocol calls EFI_BOOT_SERVICES.HandleProtocol(). 15 | func (s *BootServices) HandleProtocol(handle uint64, guid GUID) (addr uint64, err error) { 16 | status := callService(s.base+handleProtocol, 17 | []uint64{ 18 | handle, 19 | guid.ptrval(), 20 | ptrval(&addr), 21 | }, 22 | ) 23 | 24 | return addr, parseStatus(status) 25 | } 26 | 27 | // LocateProtocol calls EFI_BOOT_SERVICES.LocateProtocol(). 28 | func (s *BootServices) LocateProtocol(guid GUID) (addr uint64, err error) { 29 | status := callService(s.base+locateProtocol, 30 | []uint64{ 31 | guid.ptrval(), 32 | 0, 33 | ptrval(&addr), 34 | }, 35 | ) 36 | 37 | return addr, parseStatus(status) 38 | } 39 | -------------------------------------------------------------------------------- /uefi/reset.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | // EFI Runtime Services offset for ResetSystem 9 | const resetSystem = 0x68 10 | 11 | // EFI_RESET_SYSTEM 12 | const ( 13 | EfiResetCold = iota 14 | EfiResetWarm 15 | EfiResetShutdown 16 | EfiResetPlatformSpecific 17 | ) 18 | 19 | // ResetSystem calls EFI_RUNTIME_SERVICES.ResetSystem(). 20 | func (s *RuntimeServices) ResetSystem(resetType int) (err error) { 21 | status := callService(s.base+resetSystem, 22 | []uint64{ 23 | uint64(resetType), 24 | EFI_SUCCESS, 25 | 0, 26 | 0, 27 | }, 28 | ) 29 | 30 | return parseStatus(status) 31 | } 32 | -------------------------------------------------------------------------------- /uefi/uefi.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | // Package uefi implements a driver for the Unified Extensible Firmware 7 | // Interface (UEFI) following the specifications at: 8 | // 9 | // https://uefi.org/specs/UEFI/2.10/ 10 | // 11 | // This package is only meant to be used with `GOOS=tamago` as 12 | // supported by the TamaGo framework for bare metal Go, see 13 | // https://github.com/usbarmory/tamago. 14 | package uefi 15 | 16 | import ( 17 | "errors" 18 | "fmt" 19 | "sync" 20 | "unsafe" 21 | ) 22 | 23 | // EFI Table Header Signature 24 | const signature = 0x5453595320494249 // TSYS IBI 25 | 26 | var mux sync.Mutex 27 | 28 | // defined in uefi.s 29 | func callFn(fn uint64, n int, args []uint64) (status uint64) 30 | 31 | // callService calls an UEFI service 32 | func callService(fn uint64, args []uint64) (status uint64) { 33 | mux.Lock() 34 | defer mux.Unlock() 35 | 36 | return callFn(fn, len(args), args) 37 | } 38 | 39 | // This function helps preparing callService arguments, allowing a single call 40 | // for all EFI services. 41 | // 42 | // Obtaining a pointer in this fashion is typically unsafe and tamago/dma 43 | // package would be best to handle this. However, as arguments are prepared 44 | // right before invoking Go assembly, it is considered safe as it is identical 45 | // as having *uint64 as callService prototype. 46 | func ptrval(ptr any) uint64 { 47 | var p unsafe.Pointer 48 | 49 | switch v := ptr.(type) { 50 | case *uint64: 51 | p = unsafe.Pointer(v) 52 | case *uint32: 53 | p = unsafe.Pointer(v) 54 | case *byte: 55 | p = unsafe.Pointer(v) 56 | case *InputKey: 57 | p = unsafe.Pointer(v) 58 | default: 59 | panic("internal error, invalid ptrval") 60 | } 61 | 62 | return uint64(uintptr(p)) 63 | } 64 | 65 | // BootServices represents an EFI Boot Services instance. 66 | type BootServices struct { 67 | base uint64 68 | imageHandle uint64 69 | } 70 | 71 | // RuntimeServices represents an EFI Runtime Services instance. 72 | type RuntimeServices struct { 73 | base uint64 74 | } 75 | 76 | // TableHeader represents the data structure that precedes all of the standard 77 | // EFI table types. 78 | type TableHeader struct { 79 | Signature uint64 80 | Revision uint32 81 | HeaderSize uint32 82 | CRC32 uint32 83 | Reserved uint32 84 | } 85 | 86 | // SystemTable represents the EFI System Table, containing pointers to the 87 | // runtime and boot services tables. 88 | type SystemTable struct { 89 | Header TableHeader 90 | FirmwareVendor uint64 91 | FirmwareRevision uint32 92 | _ uint32 93 | ConsoleInHandle uint64 94 | ConIn uint64 95 | ConsoleOutHandle uint64 96 | ConOut uint64 97 | StandardErrorHandle uint64 98 | StdErr uint64 99 | RuntimeServices uint64 100 | BootServices uint64 101 | NumberOfTableEntries uint64 102 | ConfigurationTable uint64 103 | } 104 | 105 | // Revision returns the EFI Specification revision string. 106 | func (d *SystemTable) Revision() string { 107 | r := d.Header.Revision 108 | 109 | major := r >> 16 110 | minor := r & 0xffff 111 | 112 | return fmt.Sprintf("%d.%d", major, minor) 113 | } 114 | 115 | // Services represents the UEFI services instance. 116 | type Services struct { 117 | // EFI System Table instance 118 | SystemTable *SystemTable 119 | 120 | // UEFI services 121 | Console *Console 122 | Boot *BootServices 123 | Runtime *RuntimeServices 124 | 125 | imageHandle uint64 126 | systemTable uint64 127 | } 128 | 129 | // Init initializes an UEFI services instance using the argument pointers. 130 | func (s *Services) Init(imageHandle uint64, systemTable uint64) (err error) { 131 | s.imageHandle = imageHandle 132 | s.systemTable = systemTable 133 | 134 | s.SystemTable = &SystemTable{} 135 | 136 | if err = decode(s.SystemTable, systemTable); err != nil { 137 | return 138 | } 139 | 140 | if s.SystemTable.Header.Signature != signature { 141 | return errors.New("EFI System Table pointer is invalid") 142 | } 143 | 144 | s.Console = &Console{ 145 | ForceLine: true, 146 | ReplaceTabs: 8, 147 | In: s.SystemTable.ConIn, 148 | Out: s.SystemTable.ConOut, 149 | } 150 | 151 | s.Boot = &BootServices{ 152 | base: s.SystemTable.BootServices, 153 | imageHandle: imageHandle, 154 | } 155 | 156 | s.Runtime = &RuntimeServices{ 157 | base: s.SystemTable.RuntimeServices, 158 | } 159 | 160 | return 161 | } 162 | 163 | // Handle returns the UEFI image handle pointer. 164 | func (s *Services) ImageHandle() uint64 { 165 | return s.imageHandle 166 | } 167 | 168 | // Address returns the EFI System Table pointer. 169 | func (s *Services) Address() uint64 { 170 | return s.systemTable 171 | } 172 | -------------------------------------------------------------------------------- /uefi/uefi.s: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | #include "textflag.h" 7 | 8 | // func callFn(fn uint64, n int, args []uint64) (status uint64) 9 | TEXT ·callFn(SB),$0-48 10 | MOVQ fn+0(FP), DI 11 | MOVQ n+8(FP), R13 12 | MOVQ args+16(FP), R12 13 | 14 | // len(args) 15 | CMPQ R13, $0 16 | JE ret 17 | 18 | // &args[0] 19 | CMPQ R12, $0 20 | JE ret 21 | 22 | MOVQ SP, BX // callee-saved 23 | 24 | // Unified Extensible Firmware Interface (UEFI) Specification 25 | // Version 2.10 - 2.3.4.2 Detailed Calling Conventions 26 | MOVQ (R12), CX // 1st argument 27 | SUBQ $1, R13 28 | CMPQ R13, $0 29 | JE call 30 | 31 | ADDQ $8, R12 32 | MOVQ (R12), DX // 2nd argument 33 | SUBQ $1, R13 34 | CMPQ R13, $0 35 | JE call 36 | 37 | ADDQ $8, R12 38 | MOVQ (R12), R8 // 3rd argument 39 | SUBQ $1, R13 40 | CMPQ R13, $0 41 | JE call 42 | 43 | ADDQ $8, R12 44 | MOVQ (R12), R9 // 4th argument 45 | SUBQ $1, R13 46 | CMPQ R13, $0 47 | JE call 48 | 49 | ANDQ $~15, SP // alignment for x86_64 ABI 50 | 51 | // 5th arguments and above are pushed in reverse order on the stack 52 | 53 | // move to last element 54 | MOVQ R13, R15 55 | ADDQ $1, R15 56 | IMULQ $8, R15 57 | ADDQ R15, R12 58 | 59 | MOVQ R13, R14 60 | ANDQ $1, R14 61 | CMPQ R13, R14 62 | JNE align 63 | PUSHQ $0 // ensure 16-byte alignment 64 | align: 65 | MOVQ R13, R14 66 | push: 67 | SUBQ $8, R12 68 | PUSHQ (R12) 69 | SUBQ $1, R13 70 | CMPQ R13, $0 71 | JNE push 72 | 73 | ADJSP $32 // shadow stack 74 | CALL (DI) 75 | ADJSP $-32 76 | pop: 77 | POPQ CX 78 | SUBQ $1, R14 79 | CMPQ R14, $0 80 | JNE pop 81 | JMP done 82 | 83 | dummy: 84 | // balance PUSH/POP Go assembler error for conditional alignment 85 | POPQ CX 86 | 87 | call: 88 | ANDQ $~15, SP // alignment for x86_64 ABI 89 | ADJSP $32 // shadow stack 90 | CALL (DI) 91 | ADJSP $-32 92 | 93 | done: 94 | MOVQ BX, SP 95 | MOVQ AX, status+40(FP) 96 | ret: 97 | RET 98 | -------------------------------------------------------------------------------- /uefi/wdog.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package uefi 7 | 8 | const ( 9 | // EFI Boot Services offset for SetWatchdogTimer 10 | setWatchdogTimer = 0x100 11 | watchdogCode = 0xba3e5e7a1 12 | ) 13 | 14 | // SetWatchdogTimer calls EFI_BOOT_SERVICES.SetWatchdogTimer() 15 | func (s *BootServices) SetWatchdogTimer(sec int) (err error) { 16 | status := callService(s.base+setWatchdogTimer, 17 | []uint64{ 18 | uint64(sec), 19 | watchdogCode, 20 | 0, 21 | 0, 22 | }, 23 | ) 24 | 25 | return parseStatus(status) 26 | } 27 | -------------------------------------------------------------------------------- /uefi/x64/README.md: -------------------------------------------------------------------------------- 1 | TamaGo - bare metal Go - UEFI x64 support 2 | ========================================= 3 | 4 | go-boot | https://github.com/usbarmory/go-boot 5 | 6 | Copyright (c) WithSecure Corporation 7 | 8 | ![TamaGo gopher](https://github.com/usbarmory/tamago/wiki/images/tamago.svg?sanitize=true) 9 | 10 | Authors 11 | ======= 12 | 13 | Andrea Barisani 14 | andrea@inversepath.com 15 | 16 | Introduction 17 | ============ 18 | 19 | TamaGo is a framework that enables compilation and execution of unencumbered Go 20 | applications on bare metal AMD64/ARM/RISC-V processors. 21 | 22 | The [uefi](https://github.com/usbarmory/go-boot/tree/main/uefi) and 23 | [uefi/x64](https://github.com/usbarmory/go-boot/tree/main/uefi/x64) 24 | packages provides support for unikernels running under the Unified Extensible 25 | Firmware Interface [UEFI](https://uefi.org/) on an AMD64 core. 26 | 27 | Documentation 28 | ============= 29 | 30 | [![Go Reference](https://pkg.go.dev/badge/github.com/usbarmory/go-boot/uefi.svg)](https://pkg.go.dev/github.com/usbarmory/go-boot/uefi) 31 | 32 | For more information about TamaGo see its 33 | [repository](https://github.com/usbarmory/tamago) and 34 | [project wiki](https://github.com/usbarmory/tamago/wiki). 35 | 36 | For usage of these packages in the context of an UEFI application see the 37 | [go-boot](https://github.com/usbarmory/go-boot) unikernel project. 38 | 39 | The package API documentation can be found on 40 | [pkg.go.dev](https://pkg.go.dev/github.com/usbarmory/go-boot). 41 | 42 | Compiling 43 | ========= 44 | 45 | Go applications are simply required to import, the relevant board package to 46 | ensure that hardware initialization and runtime support take place: 47 | 48 | ```golang 49 | import ( 50 | _ "github.com/usbarmory/go-boot/uefi/x64" 51 | ) 52 | ``` 53 | 54 | Build the [TamaGo compiler](https://github.com/usbarmory/tamago-go) 55 | (or use the [latest binary release](https://github.com/usbarmory/tamago-go/releases/latest)): 56 | 57 | ``` 58 | wget https://github.com/usbarmory/tamago-go/archive/refs/tags/latest.zip 59 | unzip latest.zip 60 | cd tamago-go-latest/src && ./all.bash 61 | cd ../bin && export TAMAGO=`pwd`/go 62 | ``` 63 | 64 | Go applications can be compiled as usual, using the compiler built in the 65 | previous step, but with the addition of the following flags/variables: 66 | 67 | ``` 68 | GOOS=tamago GOARCH=amd64 ${TAMAGO} build -ldflags "-E cpuinit -T $(TEXT_START) -R 0x1000" main.go 69 | ``` 70 | 71 | The resulting ELF must be converted to a PE32+ executable for EFI for execution 72 | under UEFI: 73 | 74 | ``` 75 | objcopy \ 76 | --strip-debug \ 77 | --target efi-app-x86_64 \ 78 | --subsystem=efi-app \ 79 | --image-base 0x$(IMAGE_BASE) \ 80 | --stack=0x10000 \ 81 | main main.efi 82 | printf '\x26\x02' | dd of=${APP}.efi bs=1 seek=150 count=2 conv=notrunc,fsync # adjust Characteristics 83 | ``` 84 | 85 | An example application, targeting the UEFI environment, 86 | is [go-boot](https://github.com/usbarmory/go-boot). 87 | 88 | License 89 | ======= 90 | 91 | go-boot | https://github.com/usbarmory/go-boot 92 | Copyright (c) WithSecure Corporation 93 | 94 | These source files are distributed under the BSD-style license found in the 95 | [LICENSE](https://github.com/usbarmory/go-boot/blob/main/LICENSE) file. 96 | 97 | The TamaGo logo is adapted from the Go gopher designed by Renee French and 98 | licensed under the Creative Commons 3.0 Attributions license. Go Gopher vector 99 | illustration by Hugo Arganda. 100 | -------------------------------------------------------------------------------- /uefi/x64/console.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package x64 7 | 8 | import ( 9 | "github.com/usbarmory/go-boot/uefi" 10 | _ "unsafe" 11 | ) 12 | 13 | // Console represents the early UEFI services console for pre UEFI.Init() 14 | // standard output. 15 | var Console = &uefi.Console{ 16 | ForceLine: true, 17 | In: conIn, 18 | Out: conOut, 19 | } 20 | 21 | //go:linkname printk runtime.printk 22 | func printk(c byte) { 23 | if Console.Out == 0 { 24 | return 25 | } 26 | 27 | Console.Output([]byte{c}) 28 | 29 | if c == 0x0a && Console.ForceLine { // LF 30 | Console.Output([]byte{0x0d}) // CR 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /uefi/x64/mem.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package x64 7 | 8 | import ( 9 | "fmt" 10 | "runtime" 11 | _ "unsafe" 12 | 13 | "github.com/usbarmory/go-boot/uefi" 14 | ) 15 | 16 | //go:linkname _unused runtime.ramStart 17 | var _unused uint64 = 0x00100000 // overridden in x64.s 18 | 19 | //go:linkname RamSize runtime.ramSize 20 | var RamSize uint64 = 0x2c000000 // 704MB 21 | 22 | func allocateHeap() { 23 | memoryMap, err := UEFI.Boot.GetMemoryMap() 24 | 25 | if err != nil { 26 | fmt.Printf("WARNING: could not get memory map, %err\n", err) 27 | return 28 | } 29 | 30 | heapStart := uint64(0) 31 | ramStart, ramEnd := runtime.MemRegion() 32 | 33 | // locate runtime heap offset within UEFI memory allocation 34 | for _, desc := range memoryMap.Descriptors { 35 | if heapStart > 0 { 36 | // increase RamSize to cover entire page 37 | ramEnd = heapStart + uint64(desc.Size()) 38 | RamSize = ramEnd - ramStart 39 | break 40 | } 41 | 42 | if desc.Type == uefi.EfiLoaderCode && desc.PhysicalStart == ramStart { 43 | heapStart = desc.PhysicalEnd() 44 | } 45 | } 46 | 47 | if heapStart == 0 { 48 | fmt.Println("WARNING: could not find heap offset") 49 | } 50 | 51 | if err := UEFI.Boot.AllocatePages( 52 | uefi.AllocateAddress, 53 | uefi.EfiLoaderData, 54 | int(ramEnd-heapStart), 55 | heapStart, 56 | ); err != nil { 57 | fmt.Printf("WARNING: could not allocate heap at %x\n", heapStart) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /uefi/x64/x64.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | // Package x64 provides hardware initialization, automatically on import, for 7 | // the Unified Extensible Firmware Interface (UEFI) application environment 8 | // under a single x86_64 core. 9 | // 10 | // This package is only meant to be used with `GOOS=tamago` as 11 | // supported by the TamaGo framework for bare metal Go, see 12 | // https://github.com/usbarmory/tamago. 13 | package x64 14 | 15 | import ( 16 | "fmt" 17 | _ "unsafe" 18 | 19 | "github.com/usbarmory/tamago/amd64" 20 | "github.com/usbarmory/tamago/soc/intel/rtc" 21 | "github.com/usbarmory/tamago/soc/intel/uart" 22 | 23 | "github.com/usbarmory/go-boot/uefi" 24 | ) 25 | 26 | // Peripheral registers 27 | const ( 28 | // Keyboard controller port 29 | KBD_PORT = 0x64 30 | 31 | // Communication port 32 | COM1 = 0x3f8 33 | ) 34 | 35 | // set in x64.s 36 | var ( 37 | imageHandle uint64 38 | systemTable uint64 39 | conIn uint64 40 | conOut uint64 41 | ) 42 | 43 | // Peripheral instances 44 | var ( 45 | // AMD64 core 46 | AMD64 = &amd64.CPU{} 47 | 48 | // Real-Time Clock 49 | RTC = &rtc.RTC{} 50 | 51 | // Serial port 52 | UART0 = &uart.UART{ 53 | Index: 1, 54 | Base: COM1, 55 | } 56 | 57 | // UEFI services 58 | UEFI = &uefi.Services{} 59 | ) 60 | 61 | //go:linkname nanotime1 runtime.nanotime1 62 | func nanotime1() int64 { 63 | return int64(float64(AMD64.TimerFn())*AMD64.TimerMultiplier) + AMD64.TimerOffset 64 | } 65 | 66 | // Init takes care of the lower level initialization triggered early in runtime 67 | // setup. 68 | // 69 | //go:linkname Init runtime.hwinit 70 | func Init() { 71 | // initialize CPU 72 | AMD64.Init() 73 | 74 | // initialize serial console 75 | UART0.Init() 76 | } 77 | 78 | func init() { 79 | if t, err := RTC.Now(); err == nil { 80 | AMD64.SetTimer(t.UnixNano()) 81 | } 82 | 83 | Console.ClearScreen() 84 | 85 | print("initializing EFI services\n") 86 | 87 | if err := UEFI.Init(imageHandle, systemTable); err != nil { 88 | fmt.Printf("could not initialize EFI services, %v\n", err) 89 | } 90 | 91 | // allocate runtime heap in UEFI memory 92 | allocateHeap() 93 | } 94 | -------------------------------------------------------------------------------- /uefi/x64/x64.s: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | #include "go_asm.h" 7 | #include "textflag.h" 8 | 9 | TEXT cpuinit(SB),NOSPLIT|NOFRAME,$0 10 | // Unified Extensible Firmware Interface (UEFI) Specification 11 | // Version 2.10 12 | 13 | // 2.3.4.1 Handoff State 14 | MOVQ CX, ·imageHandle(SB) 15 | MOVQ DX, ·systemTable(SB) 16 | 17 | // 12.3 Simple Text Input Protocol 18 | MOVQ 48(DX), AX // SystemTable_ConIn(DX) 19 | MOVQ AX, ·conIn(SB) // Console.In 20 | 21 | // 12.4 Simple Text Output Protocol 22 | MOVQ 64(DX), AX // SystemTable_ConOut(DX) 23 | MOVQ AX, ·conOut(SB) // Console.Out 24 | 25 | // Enable SSE 26 | CALL sse_enable(SB) 27 | 28 | // ramStart is relocated based on build time variable IMAGE_BASE. 29 | MOVQ $runtime·text(SB), AX 30 | SUBQ $(64*1024), AX 31 | MOVQ AX, runtime·ramStart(SB) 32 | 33 | MOVQ runtime·ramStart(SB), SP 34 | MOVQ runtime·ramSize(SB), AX 35 | MOVQ runtime·ramStackOffset(SB), BX 36 | ADDQ AX, SP 37 | SUBQ BX, SP 38 | 39 | JMP runtime·rt0_amd64_tamago(SB) 40 | --------------------------------------------------------------------------------