| // 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" |
| "encoding/binary" |
| "errors" |
| "fmt" |
| "log" |
| "os/exec" |
| ) |
| |
| func bzImagePayloadSize(img []byte) (int, error) { |
| // See arch/x86/include/uapi/asm/bootparam.h |
| payloadLengthOffset := 0x24c |
| if payloadLengthOffset+4 >= len(img) { |
| return 0, fmt.Errorf("bzImage is too small (%d): cannot find payload_length", len(img)) |
| } |
| return int(binary.LittleEndian.Uint32(img[payloadLengthOffset : payloadLengthOffset+4])), nil |
| } |
| |
| func elfFromBZImageWithOffset(img []byte) ([]byte, int, error) { |
| gzipMagic := []byte{0x1f, 0x8b, 0x08} |
| |
| for searchOffset := 0; searchOffset < len(img); { |
| i := bytes.Index(img[searchOffset:], gzipMagic) |
| if i == -1 { |
| break |
| } |
| i += searchOffset |
| searchOffset = i + 1 |
| |
| // Found a potential gzip header, attempt decompression. |
| var stdout bytes.Buffer |
| var stderr bytes.Buffer |
| buf := append([]byte{}, img[i:]...) |
| cmd := exec.Command("gunzip", "-c", "--quiet") |
| cmd.Stdout = &stdout |
| cmd.Stderr = &stderr |
| cmd.Stdin = bytes.NewBuffer(buf) |
| if err := cmd.Run(); err != nil { |
| // Return code of 2 is a non-fatal warning, ignore those. Do not |
| // "continue" in that case, "continue" in all other cases. |
| var exitErr *exec.ExitError |
| if !(errors.As(err, &exitErr) && exitErr.ExitCode() == 2) { |
| continue |
| } |
| } |
| |
| // Verify if the decompressed content is a valid ELF file. |
| if f, err := elf.NewFile(bytes.NewReader(stdout.Bytes())); err == nil { |
| f.Close() |
| return stdout.Bytes(), i, nil |
| } |
| } |
| |
| return nil, 0, errors.New("could not find a compressed ELF payload in the bzImage") |
| } |
| |
| // elfFromBZImage converts a given bzImage into an ELF. Adapted from |
| // extract-vmlinux in the kernel tree. |
| func elfFromBZImage(img []byte) ([]byte, error) { |
| data, _, err := elfFromBZImageWithOffset(img) |
| if err != nil { |
| return nil, err |
| } |
| return data, nil |
| } |
| |
| // packBZImage packs a given ELF into a given bzImage. |
| func packBZImage(bzImg, elfImg []byte) ([]byte, error) { |
| payloadSize, err := bzImagePayloadSize(bzImg) |
| if err != nil { |
| return nil, err |
| } |
| _, offset, err := elfFromBZImageWithOffset(bzImg) |
| if err != nil { |
| return nil, err |
| } |
| // It's critical that the compressed ELF is as small as possible. Go's stdlib |
| // gzip implementation is much poorer than the cli tool, so use the cli tool. |
| var stdout bytes.Buffer |
| var stderr bytes.Buffer |
| cmd := exec.Command("gzip", "-c", "-9") |
| cmd.Stdout = &stdout |
| cmd.Stderr = &stderr |
| cmd.Stdin = bytes.NewBuffer(elfImg) |
| if err := cmd.Run(); err != nil { |
| log.Printf("Command %q failed with stderr: %v", cmd.String(), string(stderr.Bytes())) |
| return nil, errors.New("cannot gzip ELF") |
| } |
| // newSize is the size of the new compressed vmlinux (different from bzImage). |
| newSize := len(stdout.Bytes()) |
| if newSize > payloadSize { |
| return nil, fmt.Errorf("new bzImage is larger than old bzImage by %d bytes", newSize-payloadSize) |
| } |
| padCount := payloadSize - newSize |
| var padding []byte |
| for i := 0; i < padCount; i++ { |
| padding = append(padding, 0x00) |
| } |
| var compressed []byte |
| compressed = append(compressed, stdout.Bytes()...) |
| // Add padding |
| compressed = append(compressed, padding...) |
| // Add bzImage trailing content |
| compressed = append(compressed, bzImg[offset+payloadSize:]...) |
| return append(bzImg[:offset], compressed...), nil |
| } |