blob: 41a93c1c5c7b60ace7d957d750ce50eca2ef5082 [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"
)
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) {
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 0, nil
}
// We always expect a 4 byte file.
if len(data) != 4 {
return 0, fmt.Errorf("invalid bitfield file: file has %d bytes, expected 4", len(data))
}
var bitfield uint32
err = binary.Read(bytes.NewReader(data), binary.LittleEndian, &bitfield)
if err != nil {
return 0, err
}
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)
}