blob: 7ceab35ba908749fc4147b449a89cbfa1e558e08 [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 main
import (
"bytes"
"encoding/binary"
"fmt"
"strings"
"cos.googlesource.com/cos/tools.git/src/pkg/cosboot"
"cos.googlesource.com/cos/tools.git/src/pkg/tools/partutil"
)
const (
allowListVar = "KERNEL_CMDLINE_ALLOWLIST"
cmdlineExtraBinPath = "/efi/boot/cmdline_extra.bin"
// This is the largest size bitmap (in bits) that pickargs currently supports.
// Can be adjusted later if necessary.
bitfieldLength = 1024
)
var (
readEFIFileFunc = cosboot.ReadEFIFile
writeEFIFileFunc = cosboot.WriteEFIFile // Use function pointers for unit testing
)
func readAllowlist(disk string) ([]string, string, error) {
data, err := readEFIFileFunc(disk, partutil.GRUBCfgEFIPath)
if err != nil {
return nil, "", fmt.Errorf("failed to read grub.cfg: %v", err)
}
content := string(data)
lines := strings.Split(content, "\n")
var allowlist []string
var foundVar bool
for _, line := range lines {
if strings.HasPrefix(line, allowListVar+"=") {
foundVar = true
val := strings.TrimPrefix(line, allowListVar+"=\"")
val = strings.TrimSuffix(val, "\"")
fields := strings.Fields(val)
for _, f := range fields {
allowlist = append(allowlist, f)
}
break
}
}
if !foundVar {
return nil, "", fmt.Errorf("could not find %s in grub.cfg", allowListVar)
}
return allowlist, content, nil
}
func writeAllowlist(disk string, content string, finalAllowlist []string) error {
newAllowlist := strings.Join(finalAllowlist, " ")
newLine := allowListVar + "=\"" + newAllowlist + "\""
lines := strings.Split(content, "\n")
found := false
for i, line := range lines {
if strings.HasPrefix(line, allowListVar+"=") {
lines[i] = newLine
found = true
break
}
}
if !found {
return fmt.Errorf("writeAllowlist: could not find allowlist line to replace")
}
newContent := strings.Join(lines, "\n")
return writeEFIFileFunc(disk, partutil.GRUBCfgEFIPath, []byte(newContent), 0644)
}
func readBitfield(disk string) ([]uint32, error) {
bitfield := make([]uint32, bitfieldLength/32)
data, err := readEFIFileFunc(disk, cmdlineExtraBinPath)
if err != nil {
// We assume reading the file failed because it does not exist yet. In the case - all arguments are disabled by default.
return bitfield, nil
}
maxBytes := bitfieldLength / 8
if len(data) > maxBytes {
return nil, fmt.Errorf("invalid bitfield file: file has %d bytes, expected at most %d", len(data), maxBytes)
}
if len(data) % 4 != 0 {
return nil, fmt.Errorf("invalid bitfield file: file length must be a multiple of 4, got %d", len(data))
}
// The length of the buffer we read data into has to exactly match the length of the file.
// Otherwise, we get an unexpected EOF error.
buffer := make([]uint32, len(data)/4)
err = binary.Read(bytes.NewReader(data), binary.LittleEndian, &buffer)
if err != nil {
return nil, err
}
// Copy the data from the buffer into the bitfield.
for i, b := range buffer {
bitfield[i] = b
}
return bitfield, nil
}
func writeBitfield(disk string, bitfield []uint32) error {
buf := new(bytes.Buffer)
err := binary.Write(buf, binary.LittleEndian, bitfield)
if err != nil {
return err
}
return writeEFIFileFunc(disk, cmdlineExtraBinPath, buf.Bytes(), 0644)
}