blob: 757cf44aa8d483d51668b3b0efe5f2efc8200cd2 [file]
// 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
}