github.com/system-transparency/u-root@v6.0.1-0.20190919065413-ed07a650de4c+incompatible/pkg/multiboot/multiboot.go (about)

     1  // Copyright 2018-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 multiboot implements bootloading multiboot kernels as defined by
     6  // https://www.gnu.org/software/grub/manual/multiboot/multiboot.html.
     7  //
     8  // Package multiboot crafts kexec segments that can be used with the kexec_load
     9  // system call.
    10  package multiboot
    11  
    12  import (
    13  	"bytes"
    14  	"debug/elf"
    15  	"encoding/binary"
    16  	"fmt"
    17  	"io"
    18  	"log"
    19  	"os"
    20  	"path/filepath"
    21  	"strings"
    22  
    23  	"github.com/u-root/u-root/pkg/ibft"
    24  	"github.com/u-root/u-root/pkg/kexec"
    25  	"github.com/u-root/u-root/pkg/multiboot/internal/trampoline"
    26  	"github.com/u-root/u-root/pkg/ubinary"
    27  )
    28  
    29  const bootloader = "u-root kexec"
    30  
    31  // multiboot defines parameters for working with multiboot kernels.
    32  type multiboot struct {
    33  	mem kexec.Memory
    34  
    35  	file    string
    36  	modules []string
    37  
    38  	cmdLine    string
    39  	bootloader string
    40  
    41  	// trampoline is a path to an executable blob, which contains a trampoline segment.
    42  	// Trampoline sets machine to a specific state defined by multiboot v1 spec.
    43  	// https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Machine-state.
    44  	trampoline string
    45  
    46  	header header
    47  
    48  	// infoAddr is a pointer to multiboot info.
    49  	infoAddr uintptr
    50  	// kernelEntry is a pointer to entry point of kernel.
    51  	kernelEntry uintptr
    52  	// EntryPoint is a pointer to trampoline.
    53  	entryPoint uintptr
    54  
    55  	info          info
    56  	loadedModules []module
    57  }
    58  
    59  var (
    60  	rangeTypes = map[kexec.RangeType]uint32{
    61  		kexec.RangeRAM:      1,
    62  		kexec.RangeDefault:  2,
    63  		kexec.RangeACPI:     3,
    64  		kexec.RangeNVS:      4,
    65  		kexec.RangeReserved: 2,
    66  	}
    67  	PageSize = os.Getpagesize()
    68  )
    69  
    70  var sizeofMemoryMap = uint(binary.Size(MemoryMap{}))
    71  
    72  // MemoryMap represents a reserved range of memory passed via the multiboot Info header.
    73  type MemoryMap struct {
    74  	// Size is the size of the associated structure in bytes.
    75  	Size uint32
    76  	// BaseAddr is the starting address.
    77  	BaseAddr uint64
    78  	// Length is the size of the memory region in bytes.
    79  	Length uint64
    80  	// Type is the variety of address range represented.
    81  	Type uint32
    82  }
    83  
    84  // String returns a readable representation of a MemoryMap entry.
    85  func (m MemoryMap) String() string {
    86  	return fmt.Sprintf("[0x%x, 0x%x) (len: 0x%x, size: 0x%x, type: %d)", m.BaseAddr, m.BaseAddr+m.Length, m.Length, m.Size, m.Type)
    87  }
    88  
    89  type memoryMaps []MemoryMap
    90  
    91  // String returns a new-line-separated representation of the entire memory map.
    92  func (m memoryMaps) String() string {
    93  	var s []string
    94  	for _, mm := range m {
    95  		s = append(s, mm.String())
    96  	}
    97  	return strings.Join(s, "\n")
    98  }
    99  
   100  // Probe checks if file is multiboot v1 kernel.
   101  func Probe(file string) error {
   102  	b, err := readFile(file)
   103  	if err != nil {
   104  		return err
   105  	}
   106  	kernel := &kernelReader{buf: b}
   107  	_, err = parseHeader(kernel)
   108  	return err
   109  }
   110  
   111  // newMB returns a new multiboot instance.
   112  func newMB(file, cmdLine string, modules []string) (*multiboot, error) {
   113  	// Trampoline should be a part of current binary.
   114  	p, err := os.Executable()
   115  	if err != nil {
   116  		return nil, fmt.Errorf("Cannot find current executable path: %v", err)
   117  	}
   118  	trampoline, err := filepath.EvalSymlinks(p)
   119  	if err != nil {
   120  		return nil, fmt.Errorf("Cannot eval symlinks for %v: %v", p, err)
   121  	}
   122  
   123  	return &multiboot{
   124  		file:       file,
   125  		modules:    modules,
   126  		cmdLine:    cmdLine,
   127  		trampoline: trampoline,
   128  		bootloader: bootloader,
   129  		mem:        kexec.Memory{},
   130  	}, nil
   131  }
   132  
   133  // Load parses and loads a multiboot kernel `file` using kexec_load.
   134  //
   135  // Each module is a path followed by optional command-line arguments, e.g.
   136  // []string{"./module arg1 arg2", "./module2 arg3 arg4"}.
   137  //
   138  // debug turns on debug logging.
   139  //
   140  // Load can set up an arbitrary number of modules, and takes care of the
   141  // multiboot info structure, including the memory map.
   142  //
   143  // After Load is called, kexec.Reboot() is ready to be called any time to stop
   144  // Linux and execute the loaded kernel.
   145  func Load(debug bool, file, cmdline string, modules []string, ibft *ibft.IBFT) error {
   146  	m, err := newMB(file, cmdline, modules)
   147  	if err != nil {
   148  		return err
   149  	}
   150  	if err := m.load(debug, ibft); err != nil {
   151  		return err
   152  	}
   153  	if err := kexec.Load(m.entryPoint, m.mem.Segments, 0); err != nil {
   154  		return fmt.Errorf("kexec.Load() error: %v", err)
   155  	}
   156  	return nil
   157  }
   158  
   159  // load loads and parses multiboot information from m.file.
   160  func (m *multiboot) load(debug bool, ibft *ibft.IBFT) error {
   161  	log.Printf("Parsing file %v", m.file)
   162  	b, err := readFile(m.file)
   163  	if err != nil {
   164  		return err
   165  	}
   166  	kernel := kernelReader{buf: b}
   167  	log.Println("Parsing multiboot header")
   168  	if m.header, err = parseHeader(&kernel); err != nil {
   169  		return fmt.Errorf("error parsing headers: %v", err)
   170  	}
   171  
   172  	log.Printf("Getting kernel entry point")
   173  	if m.kernelEntry, err = getEntryPoint(kernel); err != nil {
   174  		return fmt.Errorf("error getting kernel entry point: %v", err)
   175  	}
   176  	log.Printf("Kernel entry point at %#x", m.kernelEntry)
   177  
   178  	log.Printf("Parsing ELF segments")
   179  	if err := m.mem.LoadElfSegments(kernel); err != nil {
   180  		return fmt.Errorf("error loading ELF segments: %v", err)
   181  	}
   182  
   183  	log.Printf("Parsing memory map")
   184  	if err := m.mem.ParseMemoryMap(); err != nil {
   185  		return fmt.Errorf("error parsing memory map: %v", err)
   186  	}
   187  
   188  	// Insert the iBFT now, since nothing else has been allocated and this
   189  	// is the most restricted allocation we're gonna have to make.
   190  	if ibft != nil {
   191  		ibuf := ibft.Marshal()
   192  
   193  		// The iBFT may sit between 512K and 1M in physical memory. The
   194  		// loaded OS finds it by scanning this region.
   195  		allowedRange := kexec.Range{
   196  			Start: 0x80000,
   197  			Size:  0x80000,
   198  		}
   199  		r, err := m.mem.ReservePhys(uint(len(ibuf)), allowedRange)
   200  		if err != nil {
   201  			return fmt.Errorf("reserving space for the iBFT in %s failed: %v", allowedRange, err)
   202  		}
   203  		log.Printf("iBFT was allocated at %s: %#v", r, ibft)
   204  		m.mem.Segments.Insert(kexec.NewSegment(ibuf, r))
   205  	}
   206  
   207  	log.Printf("Preparing multiboot info")
   208  	if m.infoAddr, err = m.addInfo(); err != nil {
   209  		return fmt.Errorf("error preparing multiboot info: %v", err)
   210  	}
   211  
   212  	log.Printf("Adding trampoline")
   213  	if m.entryPoint, err = m.addTrampoline(); err != nil {
   214  		return fmt.Errorf("error adding trampoline: %v", err)
   215  	}
   216  	log.Printf("Trampoline entry point at %#x", m.entryPoint)
   217  
   218  	if debug {
   219  		info, err := m.description()
   220  		if err != nil {
   221  			log.Printf("%v cannot create debug info: %v", DebugPrefix, err)
   222  		}
   223  		log.Printf("%v %v", DebugPrefix, info)
   224  	}
   225  
   226  	return nil
   227  }
   228  
   229  func getEntryPoint(r io.ReaderAt) (uintptr, error) {
   230  	f, err := elf.NewFile(r)
   231  	if err != nil {
   232  		return 0, err
   233  	}
   234  	return uintptr(f.Entry), err
   235  }
   236  
   237  func (m *multiboot) addInfo() (addr uintptr, err error) {
   238  	iw, err := m.newMultibootInfo()
   239  	if err != nil {
   240  		return 0, err
   241  	}
   242  	infoSize, err := iw.size()
   243  	if err != nil {
   244  		return 0, err
   245  	}
   246  
   247  	r, err := m.mem.FindSpace(infoSize)
   248  	if err != nil {
   249  		return 0, err
   250  	}
   251  
   252  	d, err := iw.marshal(r.Start)
   253  	if err != nil {
   254  		return 0, err
   255  	}
   256  	m.info = iw.info
   257  
   258  	m.mem.Segments.Insert(kexec.NewSegment(d, r))
   259  	return r.Start, nil
   260  }
   261  
   262  func (m multiboot) memoryMap() memoryMaps {
   263  	var ret memoryMaps
   264  	for _, r := range m.mem.Phys {
   265  		typ, ok := rangeTypes[r.Type]
   266  		if !ok {
   267  			typ = rangeTypes[kexec.RangeDefault]
   268  		}
   269  		v := MemoryMap{
   270  			// Size is really used for skipping to the next pair.
   271  			Size:     uint32(sizeofMemoryMap) - 4,
   272  			BaseAddr: uint64(r.Start),
   273  			Length:   uint64(r.Size),
   274  			Type:     typ,
   275  		}
   276  		ret = append(ret, v)
   277  	}
   278  	return ret
   279  }
   280  
   281  func (m *multiboot) addMmap() (addr uintptr, size uint, err error) {
   282  	mmap := m.memoryMap()
   283  	log.Printf("Memory map:\n%s", mmap)
   284  	d, err := mmap.marshal()
   285  	if err != nil {
   286  		return 0, 0, err
   287  	}
   288  	r, err := m.mem.AddKexecSegment(d)
   289  	if err != nil {
   290  		return 0, 0, err
   291  	}
   292  	return r.Start, uint(len(mmap)) * sizeofMemoryMap, nil
   293  }
   294  
   295  func (m multiboot) memoryBoundaries() (lower, upper uint32) {
   296  	const M1 = 1048576
   297  	const K640 = 640 * 1024
   298  	for _, r := range m.mem.Phys {
   299  		if r.Type != kexec.RangeRAM {
   300  			continue
   301  		}
   302  		end := uint32(r.Start) + uint32(r.Size)
   303  		// Lower memory starts at address 0, and upper memory starts at address 1 megabyte.
   304  		// The maximum possible value for lower memory is 640 kilobytes.
   305  		// The value returned for upper memory is maximally the address of the first upper memory hole minus 1 megabyte.
   306  		// It is not guaranteed to be this value.
   307  		if r.Start <= K640 && end > lower {
   308  			lower = end
   309  		}
   310  		if r.Start <= M1 && end > upper+M1 {
   311  			upper = end - M1
   312  		}
   313  	}
   314  	return
   315  }
   316  
   317  func min(a, b uint32) uint32 {
   318  	if a < b {
   319  		return a
   320  	}
   321  	return b
   322  }
   323  
   324  func (m *multiboot) newMultibootInfo() (*infoWrapper, error) {
   325  	mmapAddr, mmapSize, err := m.addMmap()
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  	var inf info
   330  	if m.header.Flags&flagHeaderMemoryInfo != 0 {
   331  		lower, upper := m.memoryBoundaries()
   332  		inf = info{
   333  			Flags:      flagInfoMemMap | flagInfoMemory,
   334  			MemLower:   min(uint32(lower>>10), 0xFFFFFFFF),
   335  			MemUpper:   min(uint32(upper>>10), 0xFFFFFFFF),
   336  			MmapLength: uint32(mmapSize),
   337  			MmapAddr:   uint32(mmapAddr),
   338  		}
   339  	}
   340  
   341  	if len(m.modules) > 0 {
   342  		modAddr, err := m.addModules()
   343  		if err != nil {
   344  			return nil, err
   345  		}
   346  		inf.Flags |= flagInfoMods
   347  		inf.ModsAddr = uint32(modAddr)
   348  		inf.ModsCount = uint32(len(m.modules))
   349  	}
   350  
   351  	inf.CmdLine = sizeofInfo
   352  	inf.BootLoaderName = sizeofInfo + uint32(len(m.cmdLine)) + 1
   353  	inf.Flags |= flagInfoCmdLine | flagInfoBootLoaderName
   354  	return &infoWrapper{
   355  		info:           inf,
   356  		CmdLine:        m.cmdLine,
   357  		BootLoaderName: m.bootloader,
   358  	}, nil
   359  }
   360  
   361  // marshal writes out the exact bytes expected by the multiboot info header
   362  // specified in
   363  // https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format.
   364  func (m memoryMaps) marshal() ([]byte, error) {
   365  	buf := bytes.Buffer{}
   366  	err := binary.Write(&buf, ubinary.NativeEndian, m)
   367  	return buf.Bytes(), err
   368  }
   369  
   370  func (m *multiboot) addTrampoline() (entry uintptr, err error) {
   371  	// Trampoline setups the machine registers to desired state
   372  	// and executes the loaded kernel.
   373  	d, err := trampoline.Setup(m.trampoline, m.infoAddr, m.kernelEntry)
   374  	if err != nil {
   375  		return 0, err
   376  	}
   377  
   378  	r, err := m.mem.AddKexecSegment(d)
   379  	if err != nil {
   380  		return 0, err
   381  	}
   382  	return r.Start, nil
   383  }