github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/boot/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/mvdan/u-root-coreutils/pkg/boot/ibft"
    24  	"github.com/mvdan/u-root-coreutils/pkg/boot/kexec"
    25  	"github.com/mvdan/u-root-coreutils/pkg/boot/multiboot/internal/trampoline"
    26  	"github.com/mvdan/u-root-coreutils/pkg/boot/util"
    27  	"github.com/mvdan/u-root-coreutils/pkg/ubinary"
    28  	"github.com/mvdan/u-root-coreutils/pkg/uio"
    29  )
    30  
    31  const bootloader = "u-root kexec"
    32  
    33  // Module describe a module by a ReaderAt and a `Cmdline`
    34  type Module struct {
    35  	Module  io.ReaderAt
    36  	Cmdline string
    37  }
    38  
    39  // Name returns the first field of the cmdline, if there is one.
    40  func (m Module) Name() string {
    41  	f := strings.Fields(m.Cmdline)
    42  	if len(f) > 0 {
    43  		return f[0]
    44  	}
    45  	return ""
    46  }
    47  
    48  // Modules is a range of module with a Closer interface
    49  type Modules []Module
    50  
    51  // multiboot defines parameters for working with multiboot kernels.
    52  type multiboot struct {
    53  	mem kexec.Memory
    54  
    55  	kernel  io.ReaderAt
    56  	modules []Module
    57  
    58  	cmdLine    string
    59  	bootloader string
    60  
    61  	// trampoline is a path to an executable blob, which contains a trampoline segment.
    62  	// Trampoline sets machine to a specific state defined by multiboot v1 spec.
    63  	// https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Machine-state.
    64  	trampoline string
    65  
    66  	// EntryPoint is a pointer to trampoline.
    67  	entryPoint uintptr
    68  
    69  	info          info
    70  	loadedModules modules
    71  }
    72  
    73  var rangeTypes = map[kexec.RangeType]uint32{
    74  	kexec.RangeRAM:      1,
    75  	kexec.RangeDefault:  2,
    76  	kexec.RangeACPI:     3,
    77  	kexec.RangeNVS:      4,
    78  	kexec.RangeReserved: 2,
    79  }
    80  
    81  var sizeofMemoryMap = uint(binary.Size(MemoryMap{}))
    82  
    83  // MemoryMap represents a reserved range of memory passed via the multiboot Info header.
    84  type MemoryMap struct {
    85  	// Size is the size of the associated structure in bytes.
    86  	Size uint32
    87  	// BaseAddr is the starting address.
    88  	BaseAddr uint64
    89  	// Length is the size of the memory region in bytes.
    90  	Length uint64
    91  	// Type is the variety of address range represented.
    92  	Type uint32
    93  }
    94  
    95  // String returns a readable representation of a MemoryMap entry.
    96  func (m MemoryMap) String() string {
    97  	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)
    98  }
    99  
   100  type memoryMaps []MemoryMap
   101  
   102  // marshal writes out the exact bytes expected by the multiboot info header
   103  // specified in
   104  // https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format.
   105  func (m memoryMaps) marshal() ([]byte, error) {
   106  	buf := bytes.Buffer{}
   107  	err := binary.Write(&buf, ubinary.NativeEndian, m)
   108  	return buf.Bytes(), err
   109  }
   110  
   111  // elems adds esxBootInfo info elements describing the memory map of the system.
   112  func (m memoryMaps) elems() []elem {
   113  	var e []elem
   114  	for _, mm := range m {
   115  		e = append(e, &esxBootInfoMemRange{
   116  			startAddr: mm.BaseAddr,
   117  			length:    mm.Length,
   118  			memType:   mm.Type,
   119  		})
   120  	}
   121  	return e
   122  }
   123  
   124  // String returns a new-line-separated representation of the entire memory map.
   125  func (m memoryMaps) String() string {
   126  	var s []string
   127  	for _, mm := range m {
   128  		s = append(s, mm.String())
   129  	}
   130  	return strings.Join(s, "\n")
   131  }
   132  
   133  // Probe checks if `kernel` is multiboot v1 or esxBootInfo kernel.
   134  // If the `kernel` is gzip'ed, it will decompress it.
   135  // Only Gzip decmpression is supported at present.
   136  func Probe(kernel io.ReaderAt) error {
   137  	r := util.TryGzipFilter(kernel)
   138  	_, err := parseHeader(uio.Reader(r))
   139  	if err == ErrHeaderNotFound {
   140  		_, err = parseMutiHeader(uio.Reader(r))
   141  	}
   142  	return err
   143  }
   144  
   145  // newMB returns a new multiboot instance.
   146  func newMB(kernel io.ReaderAt, cmdLine string, modules []Module) (*multiboot, error) {
   147  	// Trampoline should be a part of current binary.
   148  	p, err := os.Executable()
   149  	if err != nil {
   150  		return nil, fmt.Errorf("cannot find current executable path: %v", err)
   151  	}
   152  	trampoline, err := filepath.EvalSymlinks(p)
   153  	if err != nil {
   154  		return nil, fmt.Errorf("cannot eval symlinks for %v: %v", p, err)
   155  	}
   156  
   157  	return &multiboot{
   158  		kernel:     kernel,
   159  		modules:    modules,
   160  		cmdLine:    cmdLine,
   161  		trampoline: trampoline,
   162  		bootloader: bootloader,
   163  		mem:        kexec.Memory{},
   164  	}, nil
   165  }
   166  
   167  // Load parses and loads a multiboot `kernel` using kexec_load.
   168  //
   169  // debug turns on debug logging.
   170  //
   171  // Load can set up an arbitrary number of modules, and takes care of the
   172  // multiboot info structure, including the memory map.
   173  //
   174  // After Load is called, kexec.Reboot() is ready to be called any time to stop
   175  // Linux and execute the loaded kernel.
   176  func Load(debug bool, kernel io.ReaderAt, cmdline string, modules []Module, ibft *ibft.IBFT) error {
   177  	kernel = util.TryGzipFilter(kernel)
   178  	for i, mod := range modules {
   179  		modules[i].Module = util.TryGzipFilter(mod.Module)
   180  	}
   181  
   182  	m, err := newMB(kernel, cmdline, modules)
   183  	if err != nil {
   184  		return err
   185  	}
   186  	if err := m.load(debug, ibft); err != nil {
   187  		return err
   188  	}
   189  	if err := kexec.Load(m.entryPoint, m.mem.Segments, 0); err != nil {
   190  		return fmt.Errorf("kexec.Load() error: %v", err)
   191  	}
   192  	return nil
   193  }
   194  
   195  // OpenModules open modules as files and fill a range of `Module` struct
   196  //
   197  // Each module is a path followed by optional command-line arguments, e.g.
   198  // []string{"./module arg1 arg2", "./module2 arg3 arg4"}.
   199  func OpenModules(cmds []string) (Modules, error) {
   200  	modules := make([]Module, len(cmds))
   201  	for i, cmd := range cmds {
   202  		modules[i].Cmdline = cmd
   203  		name := strings.Fields(cmd)[0]
   204  		f, err := os.Open(name)
   205  		if err != nil {
   206  			// TODO close already open files
   207  			return nil, fmt.Errorf("error opening module %v: %v", name, err)
   208  		}
   209  		modules[i].Module = f
   210  	}
   211  	return modules, nil
   212  }
   213  
   214  // LazyOpenModules assigns modules to be opened as files.
   215  //
   216  // Each module is a path followed by optional command-line arguments, e.g.
   217  // []string{"./module arg1 arg2", "./module2 arg3 arg4"}.
   218  func LazyOpenModules(cmds []string) Modules {
   219  	modules := make([]Module, 0, len(cmds))
   220  	for _, cmd := range cmds {
   221  		name := strings.Fields(cmd)[0]
   222  		modules = append(modules, Module{
   223  			Cmdline: cmd,
   224  			Module:  uio.NewLazyFile(name),
   225  		})
   226  	}
   227  	return modules
   228  }
   229  
   230  // Close closes all Modules ReaderAt implementing the io.Closer interface
   231  func (m Modules) Close() error {
   232  	// poor error handling inspired from uio.multiCloser
   233  	var allErr error
   234  	for _, mod := range m {
   235  		if c, ok := mod.Module.(io.Closer); ok {
   236  			if err := c.Close(); err != nil {
   237  				allErr = err
   238  			}
   239  		}
   240  	}
   241  	return allErr
   242  }
   243  
   244  // load loads and parses multiboot information from m.kernel.
   245  func (m *multiboot) load(debug bool, ibft *ibft.IBFT) error {
   246  	var err error
   247  	log.Println("Parsing multiboot header")
   248  	// TODO: the kernel is opened like 4 separate times here. Just open it
   249  	// once and pass it around.
   250  
   251  	var header imageType
   252  	multibootHeader, err := parseHeader(uio.Reader(m.kernel))
   253  	if err == nil {
   254  		header = multibootHeader
   255  	} else if err == ErrHeaderNotFound {
   256  		var esxBootInfoHeader *esxBootInfoHeader
   257  		// We don't even need the header at the moment. Just need to
   258  		// know it's there. Everything that matters is in the ELF.
   259  		esxBootInfoHeader, err = parseMutiHeader(uio.Reader(m.kernel))
   260  		header = esxBootInfoHeader
   261  	}
   262  	if err != nil {
   263  		return fmt.Errorf("error parsing headers: %v", err)
   264  	}
   265  	log.Printf("Found %s image", header.name())
   266  
   267  	log.Printf("Getting kernel entry point")
   268  	kernelEntry, err := getEntryPoint(m.kernel)
   269  	if err != nil {
   270  		return fmt.Errorf("error getting kernel entry point: %v", err)
   271  	}
   272  	log.Printf("Kernel entry point at %#x", kernelEntry)
   273  
   274  	log.Printf("Parsing ELF segments")
   275  	if _, err := m.mem.LoadElfSegments(m.kernel); err != nil {
   276  		return fmt.Errorf("error loading ELF segments: %v", err)
   277  	}
   278  
   279  	log.Printf("Parsing memory map")
   280  	if err := m.mem.ParseMemoryMap(); err != nil {
   281  		return fmt.Errorf("error parsing memory map: %v", err)
   282  	}
   283  
   284  	// Insert the iBFT now, since nothing else has been allocated and this
   285  	// is the most restricted allocation we're gonna have to make.
   286  	if ibft != nil {
   287  		ibuf := ibft.Marshal()
   288  
   289  		// The iBFT may sit between 512K and 1M in physical memory. The
   290  		// loaded OS finds it by scanning this region.
   291  		allowedRange := kexec.Range{
   292  			Start: 0x80000,
   293  			Size:  0x80000,
   294  		}
   295  		r, err := m.mem.ReservePhys(uint(len(ibuf)), allowedRange)
   296  		if err != nil {
   297  			return fmt.Errorf("reserving space for the iBFT in %s failed: %v", allowedRange, err)
   298  		}
   299  		log.Printf("iBFT was allocated at %s: %#v", r, ibft)
   300  		m.mem.Segments.Insert(kexec.NewSegment(ibuf, r))
   301  	}
   302  
   303  	log.Printf("Preparing %s info", header.name())
   304  	infoAddr, err := header.addInfo(m)
   305  	if err != nil {
   306  		return fmt.Errorf("error preparing %s info: %v", header.name(), err)
   307  	}
   308  
   309  	log.Printf("Adding trampoline")
   310  	if m.entryPoint, err = m.addTrampoline(header.bootMagic(), infoAddr, kernelEntry); err != nil {
   311  		return fmt.Errorf("error adding trampoline: %v", err)
   312  	}
   313  	log.Printf("Trampoline entry point at %#x", m.entryPoint)
   314  
   315  	if debug {
   316  		info, err := m.description()
   317  		if err != nil {
   318  			log.Printf("%v cannot create debug info: %v", DebugPrefix, err)
   319  		}
   320  		log.Printf("%v %v", DebugPrefix, info)
   321  	}
   322  
   323  	return nil
   324  }
   325  
   326  func getEntryPoint(r io.ReaderAt) (uintptr, error) {
   327  	f, err := elf.NewFile(r)
   328  	if err != nil {
   329  		return 0, err
   330  	}
   331  	return uintptr(f.Entry), err
   332  }
   333  
   334  // addInfo collects and adds multiboot info into the relocations/segments.
   335  //
   336  // addInfo marshals out everything required for
   337  // https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format
   338  // which is a memory map; a list of module structures, pointed to by mods_addr
   339  // and mods_count; and the multiboot info structure itself.
   340  func (h *header) addInfo(m *multiboot) (addr uintptr, err error) {
   341  	iw, err := h.newMultibootInfo(m)
   342  	if err != nil {
   343  		return 0, err
   344  	}
   345  	infoSize, err := iw.size()
   346  	if err != nil {
   347  		return 0, err
   348  	}
   349  
   350  	r, err := m.mem.FindSpace(infoSize, uint(os.Getpagesize()))
   351  	if err != nil {
   352  		return 0, err
   353  	}
   354  
   355  	d, err := iw.marshal(r.Start)
   356  	if err != nil {
   357  		return 0, err
   358  	}
   359  	m.info = iw.info
   360  
   361  	m.mem.Segments.Insert(kexec.NewSegment(d, r))
   362  	return r.Start, nil
   363  }
   364  
   365  // addInfo collects and adds esxBootInfo (without L!) into the segments.
   366  //
   367  // The format is described in the structs in
   368  // https://github.com/vmware/esx-boot/blob/master/include/esxbootinfo.h
   369  //
   370  // It includes a memory map and a list of modules.
   371  func (*esxBootInfoHeader) addInfo(m *multiboot) (addr uintptr, err error) {
   372  	var mi esxBootInfoInfo
   373  
   374  	mi.elems = append(mi.elems, m.memoryMap().elems()...)
   375  	mods, err := m.loadModules()
   376  	if err != nil {
   377  		return 0, err
   378  	}
   379  	mi.elems = append(mi.elems, mods.elems()...)
   380  
   381  	// This marshals the esxBootInfo info with cmdline = 0. We're gonna append
   382  	// the cmdline, so we must know the size of the marshaled stuff first
   383  	// to be able to point to it.
   384  	//
   385  	// TODO: find a better place to put the cmdline so we don't do this
   386  	// bullshit.
   387  	b := mi.marshal()
   388  
   389  	// string + null-terminator
   390  	cmdlineLen := len(m.cmdLine) + 1
   391  
   392  	memRange, err := m.mem.FindSpace(uint(len(b)+cmdlineLen), uint(os.Getpagesize()))
   393  	if err != nil {
   394  		return 0, err
   395  	}
   396  	mi.cmdline = uint64(memRange.Start + uintptr(len(b)))
   397  
   398  	// Re-marshal, now that the cmdline is set.
   399  	b = mi.marshal()
   400  	b = append(b, []byte(m.cmdLine)...)
   401  	b = append(b, 0)
   402  	m.mem.Segments.Insert(kexec.NewSegment(b, memRange))
   403  	return memRange.Start, nil
   404  }
   405  
   406  func (m multiboot) memoryMap() memoryMaps {
   407  	var ret memoryMaps
   408  	for _, r := range m.mem.Phys {
   409  		typ, ok := rangeTypes[r.Type]
   410  		if !ok {
   411  			typ = rangeTypes[kexec.RangeDefault]
   412  		}
   413  		v := MemoryMap{
   414  			// Size is really used for skipping to the next pair.
   415  			Size:     uint32(sizeofMemoryMap) - 4,
   416  			BaseAddr: uint64(r.Start),
   417  			Length:   uint64(r.Size),
   418  			Type:     typ,
   419  		}
   420  		ret = append(ret, v)
   421  	}
   422  	log.Printf("Memory map: %v", ret)
   423  	return ret
   424  }
   425  
   426  // addMmap adds a multiboot-marshaled memory map in memory.
   427  func (m *multiboot) addMmap() (addr uintptr, size uint, err error) {
   428  	mmap := m.memoryMap()
   429  	d, err := mmap.marshal()
   430  	if err != nil {
   431  		return 0, 0, err
   432  	}
   433  	r, err := m.mem.AddKexecSegment(d)
   434  	if err != nil {
   435  		return 0, 0, err
   436  	}
   437  	return r.Start, uint(len(mmap)) * sizeofMemoryMap, nil
   438  }
   439  
   440  func (m multiboot) memoryBoundaries() (lower, upper uint32) {
   441  	const M1 = 1048576
   442  	const K640 = 640 * 1024
   443  	for _, r := range m.mem.Phys {
   444  		if r.Type != kexec.RangeRAM {
   445  			continue
   446  		}
   447  		end := uint32(r.Start) + uint32(r.Size)
   448  		// Lower memory starts at address 0, and upper memory starts at address 1 megabyte.
   449  		// The maximum possible value for lower memory is 640 kilobytes.
   450  		// The value returned for upper memory is maximally the address of the first upper memory hole minus 1 megabyte.
   451  		// It is not guaranteed to be this value.
   452  		if r.Start <= K640 && end > lower {
   453  			lower = end
   454  		}
   455  		if r.Start <= M1 && end > upper+M1 {
   456  			upper = end - M1
   457  		}
   458  	}
   459  	return
   460  }
   461  
   462  func min(a, b uint32) uint32 {
   463  	if a < b {
   464  		return a
   465  	}
   466  	return b
   467  }
   468  
   469  func (h *header) newMultibootInfo(m *multiboot) (*infoWrapper, error) {
   470  	mmapAddr, mmapSize, err := m.addMmap()
   471  	if err != nil {
   472  		return nil, err
   473  	}
   474  	var inf info
   475  	if h.Flags&flagHeaderMemoryInfo != 0 {
   476  		lower, upper := m.memoryBoundaries()
   477  		inf = info{
   478  			Flags:      flagInfoMemMap | flagInfoMemory,
   479  			MemLower:   min(uint32(lower>>10), 0xFFFFFFFF),
   480  			MemUpper:   min(uint32(upper>>10), 0xFFFFFFFF),
   481  			MmapLength: uint32(mmapSize),
   482  			MmapAddr:   uint32(mmapAddr),
   483  		}
   484  	}
   485  
   486  	if len(m.modules) > 0 {
   487  		modAddr, err := m.addMultibootModules()
   488  		if err != nil {
   489  			return nil, err
   490  		}
   491  		inf.Flags |= flagInfoMods
   492  		inf.ModsAddr = uint32(modAddr)
   493  		inf.ModsCount = uint32(len(m.modules))
   494  	}
   495  
   496  	return &infoWrapper{
   497  		info:           inf,
   498  		Cmdline:        m.cmdLine,
   499  		BootLoaderName: m.bootloader,
   500  	}, nil
   501  }
   502  
   503  func (m *multiboot) addTrampoline(magic, infoAddr, kernelEntry uintptr) (entry uintptr, err error) {
   504  	// Trampoline setups the machine registers to desired state
   505  	// and executes the loaded kernel.
   506  	d, err := trampoline.Setup(m.trampoline, magic, infoAddr, kernelEntry)
   507  	if err != nil {
   508  		return 0, err
   509  	}
   510  
   511  	r, err := m.mem.AddKexecSegment(d)
   512  	if err != nil {
   513  		return 0, err
   514  	}
   515  	return r.Start, nil
   516  }