| // Copyright 2026 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package cosboot | |
| import ( | |
| "bytes" | |
| "debug/elf" | |
| "debug/pe" | |
| "fmt" | |
| "unicode/utf16" | |
| ) | |
| func peSymbolOffset(peFile *pe.File, vmlinuxPath string, symbolNames []string) (uint32, uint64, string, error) { | |
| elfFile, err := elf.Open(vmlinuxPath) | |
| if err != nil { | |
| return 0, 0, "", fmt.Errorf("failed to open ELF: %v", err) | |
| } | |
| defer elfFile.Close() | |
| symbols, err := elfFile.Symbols() | |
| if err != nil { | |
| return 0, 0, "", fmt.Errorf("failed to read symbols: %v", err) | |
| } | |
| var elfTextVA, elfSymVA uint64 | |
| var symSize uint64 | |
| var foundText bool | |
| var foundSymName string | |
| for _, s := range symbols { | |
| if s.Name == "_text" { | |
| elfTextVA = s.Value | |
| foundText = true | |
| break | |
| } | |
| } | |
| if !foundText { | |
| return 0, 0, "", fmt.Errorf("required symbol _text not found in ELF") | |
| } | |
| for _, name := range symbolNames { | |
| for _, s := range symbols { | |
| if s.Name == name { | |
| elfSymVA = s.Value | |
| symSize = s.Size | |
| foundSymName = name | |
| break | |
| } | |
| } | |
| if foundSymName != "" { | |
| break | |
| } | |
| } | |
| if foundSymName == "" { | |
| return 0, 0, "", fmt.Errorf("none of the required symbols %v found in ELF", symbolNames) | |
| } | |
| var peTextSec *pe.Section | |
| for _, sec := range peFile.Sections { | |
| if sec.Name == ".text" { | |
| peTextSec = sec | |
| break | |
| } | |
| } | |
| if peTextSec == nil { | |
| return 0, 0, "", fmt.Errorf("PE .text section not found") | |
| } | |
| offsetFromText := elfSymVA - elfTextVA | |
| symbolRVA := uint32(offsetFromText) | |
| var targetPeSec *pe.Section | |
| for _, sec := range peFile.Sections { | |
| if symbolRVA >= sec.VirtualAddress && symbolRVA < sec.VirtualAddress+sec.Size { | |
| targetPeSec = sec | |
| break | |
| } | |
| } | |
| if targetPeSec == nil { | |
| return 0, 0, "", fmt.Errorf("PE section for RVA 0x%x not found", symbolRVA) | |
| } | |
| offsetInSection := symbolRVA - targetPeSec.VirtualAddress | |
| fileOffset := targetPeSec.Offset + offsetInSection | |
| fmt.Printf("DEBUG: symbol %s: elfTextVA=0x%x, elfSymVA=0x%x, offsetFromText=0x%x, symbolRVA=0x%x, targetPeSec=%s, sec.VA=0x%x, offsetInSection=0x%x, fileOffset=0x%x, symSize=%d\n", | |
| foundSymName, elfTextVA, elfSymVA, offsetFromText, symbolRVA, targetPeSec.Name, targetPeSec.VirtualAddress, offsetInSection, fileOffset, symSize) | |
| return fileOffset, symSize, foundSymName, nil | |
| } | |
| func readCmdLineFromPE(peImg []byte, vmlinuxPath string) (string, error) { | |
| f, err := pe.NewFile(bytes.NewReader(peImg)) | |
| if err != nil { | |
| return "", fmt.Errorf("failed to parse PE: %v", err) | |
| } | |
| defer f.Close() | |
| fileOffset, symSize, symName, err := peSymbolOffset(f, vmlinuxPath, []string{"builtin_cmdline", "__efistub_builtin_cmdline"}) | |
| if err != nil { | |
| return "", err | |
| } | |
| if int(fileOffset)+int(symSize) > len(peImg) { | |
| return "", fmt.Errorf("read offset out of bounds") | |
| } | |
| cmdlineBytes := peImg[fileOffset : fileOffset+uint32(symSize)] | |
| if symName == "__efistub_builtin_cmdline" { | |
| return readUTF16String(cmdlineBytes), nil | |
| } | |
| n := bytes.IndexByte(cmdlineBytes, 0) | |
| if n >= 0 { | |
| return string(cmdlineBytes[:n]), nil | |
| } | |
| return string(cmdlineBytes), nil | |
| } | |
| func writeCmdLineToPE(peImg []byte, vmlinuxPath string, data []byte) ([]byte, error) { | |
| f, err := pe.NewFile(bytes.NewReader(peImg)) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to parse PE: %v", err) | |
| } | |
| defer f.Close() | |
| fileOffset, symSize, symName, err := peSymbolOffset(f, vmlinuxPath, []string{"builtin_cmdline", "__efistub_builtin_cmdline"}) | |
| if err != nil { | |
| return nil, err | |
| } | |
| var payload []byte | |
| if symName == "__efistub_builtin_cmdline" { | |
| var err error | |
| payload, err = encodeUTF16String(string(data), int(symSize)) | |
| if err != nil { | |
| return nil, err | |
| } | |
| } else { | |
| if len(data) > int(symSize) { | |
| return nil, fmt.Errorf("string too long for buffer: %d bytes > %d bytes", len(data), symSize) | |
| } | |
| payload = make([]byte, symSize) | |
| copy(payload, data) | |
| } | |
| outImg := make([]byte, len(peImg)) | |
| copy(outImg, peImg) | |
| copy(outImg[fileOffset:fileOffset+uint32(symSize)], bytes.Repeat([]byte{0}, int(symSize))) | |
| copy(outImg[fileOffset:], payload) | |
| return outImg, nil | |
| } | |
| func readUTF16String(data []byte) string { | |
| u16s := make([]uint16, len(data)/2) | |
| for i := range u16s { | |
| u16s[i] = uint16(data[2*i]) | uint16(data[2*i+1])<<8 | |
| } | |
| end := 0 | |
| for end < len(u16s) && u16s[end] != 0 { | |
| end++ | |
| } | |
| return string(utf16.Decode(u16s[:end])) | |
| } | |
| func encodeUTF16String(s string, size int) ([]byte, error) { | |
| runes := []rune(s) | |
| u16s := utf16.Encode(runes) | |
| if len(u16s)*2 > size { | |
| return nil, fmt.Errorf("string too long for buffer: %d bytes > %d bytes", len(u16s)*2, size) | |
| } | |
| buf := make([]byte, size) | |
| for i, v := range u16s { | |
| buf[2*i] = byte(v) | |
| buf[2*i+1] = byte(v >> 8) | |
| } | |
| return buf, nil | |
| } |