github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/securelaunch/policy/policy.go (about)

     1  // Copyright 2019 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package policy locates and parses a JSON policy file.
     6  package policy
     7  
     8  import (
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"log"
    13  	"os"
    14  	"path/filepath"
    15  
    16  	"github.com/mvdan/u-root-coreutils/pkg/cmdline"
    17  	"github.com/mvdan/u-root-coreutils/pkg/mount"
    18  	slaunch "github.com/mvdan/u-root-coreutils/pkg/securelaunch"
    19  	"github.com/mvdan/u-root-coreutils/pkg/securelaunch/eventlog"
    20  	"github.com/mvdan/u-root-coreutils/pkg/securelaunch/launcher"
    21  	"github.com/mvdan/u-root-coreutils/pkg/securelaunch/measurement"
    22  )
    23  
    24  // Policy describes the policy used to drive the security engine.
    25  //
    26  // The policy is stored as a JSON file.
    27  type Policy struct {
    28  	Collectors []measurement.Collector
    29  	Launcher   launcher.Launcher
    30  	EventLog   eventlog.EventLog
    31  }
    32  
    33  // policyBytes is a byte slice to hold a copy of the policy file in memory.
    34  var policyBytes []byte
    35  
    36  // scanKernelCmdLine scans the kernel cmdline for the 'sl_policy' flag.
    37  // When set it provides location of the policy file on disk. It reads it and
    38  // returns the policy file as a byte slice.
    39  //
    40  // The format of sl_policy flag is as follows:
    41  // sl_policy=<block device identifier>:<path>
    42  // e.g., sda:/boot/securelaunch.policy
    43  // e.g., 4qccd342-12zr-4e99-9ze7-1234cb1234c4:/foo/securelaunch.policy
    44  func scanKernelCmdLine() []byte {
    45  	slaunch.Debug("scanKernelCmdLine: scanning kernel cmd line for *sl_policy* flag")
    46  	val, ok := cmdline.Flag("sl_policy")
    47  	if !ok {
    48  		log.Printf("scanKernelCmdLine: sl_policy cmdline flag is not set")
    49  		return nil
    50  	}
    51  
    52  	// val is of type sda:path/to/file or UUID:path/to/file
    53  	mntFilePath, e := slaunch.GetMountedFilePath(val, mount.MS_RDONLY) // false means readonly mount
    54  	if e != nil {
    55  		log.Printf("scanKernelCmdLine: GetMountedFilePath err=%v", e)
    56  		return nil
    57  	}
    58  	slaunch.Debug("scanKernelCmdLine: Reading file=%s", mntFilePath)
    59  
    60  	d, err := os.ReadFile(mntFilePath)
    61  	if err != nil {
    62  		log.Printf("Error reading policy file:mountPath=%s, passed=%s", mntFilePath, val)
    63  		return nil
    64  	}
    65  	return d
    66  }
    67  
    68  // scanBlockDevice scans for the policy file on already mounted block devices.
    69  // It looks in the "/", "/efi", and "/boot" directories. If found, it returns
    70  // the policy file as a byte slice.
    71  //
    72  //	e.g., if /dev/sda1 is mounted on /tmp/sda1, then mountPath would be
    73  //
    74  // /tmp/sda1 and searchPath would be /tmp/sda1/securelaunch.policy,
    75  // /tmp/sda1/efi/securelaunch.policy, and /tmp/sda1/boot/securelaunch.policy
    76  // respectively for each iteration of loop over SearchRoots slice.
    77  func scanBlockDevice(mountPath string) []byte {
    78  	log.Printf("scanBlockDevice")
    79  	// scan for securelaunch.policy under /, /efi, or /boot
    80  	SearchRoots := []string{"/", "/efi", "/boot"}
    81  	for _, c := range SearchRoots {
    82  
    83  		searchPath := filepath.Join(mountPath, c, "securelaunch.policy")
    84  		if _, err := os.Stat(searchPath); os.IsNotExist(err) {
    85  			continue
    86  		}
    87  
    88  		d, err := os.ReadFile(searchPath)
    89  		if err != nil {
    90  			// Policy File not found. Moving on to next search root...
    91  			log.Printf("Error reading policy file %s, continuing", searchPath)
    92  			continue
    93  		}
    94  		log.Printf("policy file found on mountPath=%s, directory =%s", mountPath, c)
    95  		return d // return when first policy file found
    96  	}
    97  
    98  	return nil
    99  }
   100  
   101  // locate searches for the policy file on the kernel cmdline.
   102  // If it's not found on cmdline, it looks for the policy file on each block
   103  // device in the "/", "efi", and "/boot" directories.
   104  func locate() ([]byte, error) {
   105  	d := scanKernelCmdLine()
   106  	if d != nil {
   107  		return d, nil
   108  	}
   109  
   110  	slaunch.Debug("Searching for block devices")
   111  	if err := slaunch.GetBlkInfo(); err != nil {
   112  		return nil, err
   113  	}
   114  
   115  	// devName = sda, mountPath = /tmp/sluinit-FOO/
   116  	for _, device := range slaunch.StorageBlkDevices {
   117  
   118  		devName := device.Name
   119  		mountPath, err := slaunch.MountDevice(device, mount.MS_RDONLY)
   120  		if err != nil {
   121  			log.Printf("failed to mount %s, continuing to next block device", devName)
   122  			continue
   123  		}
   124  
   125  		slaunch.Debug("scanning for policy file under devName=%s, mountPath=%s", devName, mountPath)
   126  		raw := scanBlockDevice(mountPath)
   127  		if raw == nil {
   128  			log.Printf("no policy file found under this device")
   129  			continue
   130  		}
   131  
   132  		slaunch.Debug("policy file found at devName=%s", devName)
   133  		return raw, nil
   134  	}
   135  
   136  	return nil, errors.New("policy file not found anywhere")
   137  }
   138  
   139  // parse accepts a JSON file as input, parses it into a well defined Policy
   140  // structure and returns a pointer to the Policy structure.
   141  func parse(pf []byte) (*Policy, error) {
   142  	p := &Policy{}
   143  	var parse struct {
   144  		Collectors []json.RawMessage `json:"collectors"`
   145  		Attestor   json.RawMessage   `json:"attestor"`
   146  		Launcher   json.RawMessage   `json:"launcher"`
   147  		EventLog   json.RawMessage   `json:"eventlog"`
   148  	}
   149  
   150  	if err := json.Unmarshal(pf, &parse); err != nil {
   151  		log.Printf("parse SL Policy: Unmarshall error for entire policy file!! err=%v", err)
   152  		return nil, err
   153  	}
   154  
   155  	for _, c := range parse.Collectors {
   156  		collector, err := measurement.GetCollector(c)
   157  		if err != nil {
   158  			log.Printf("GetCollector err:c=%s, collector=%v", c, collector)
   159  			return nil, err
   160  		}
   161  		p.Collectors = append(p.Collectors, collector)
   162  	}
   163  
   164  	if len(parse.Launcher) > 0 {
   165  		if err := json.Unmarshal(parse.Launcher, &p.Launcher); err != nil {
   166  			log.Printf("parse policy: Launcher Unmarshall error=%v!!", err)
   167  			return nil, err
   168  		}
   169  	}
   170  
   171  	if len(parse.EventLog) > 0 {
   172  		if err := json.Unmarshal(parse.EventLog, &p.EventLog); err != nil {
   173  			log.Printf("parse policy: EventLog Unmarshall error=%v!!", err)
   174  			return nil, err
   175  		}
   176  	}
   177  	return p, nil
   178  }
   179  
   180  // Measure measures the policy file.
   181  func Measure() error {
   182  	if len(policyBytes) == 0 {
   183  		return fmt.Errorf("policy file not yet loaded or empty")
   184  	}
   185  
   186  	eventDesc := "File Collector: measured securelaunch policy file"
   187  	if err := measurement.HashBytes(policyBytes, eventDesc); err != nil {
   188  		log.Printf("policy: ERR: could not measure policy file: %v", err)
   189  		return err
   190  	}
   191  
   192  	return nil
   193  }
   194  
   195  // Get locates and parses the policy file.
   196  //
   197  // The file is located by the following priority:
   198  //  1. the kernel cmdline `sl_policy` argument.
   199  //  2. a file on any partition on any disk called "securelaunch.policy".
   200  func Get() (*Policy, error) {
   201  	policyBytes, err := locate()
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	policy, err := parse(policyBytes)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	if policy == nil {
   211  		return nil, fmt.Errorf("no policy found")
   212  	}
   213  
   214  	return policy, nil
   215  }