github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/boot/bzimage/kver.go (about)

     1  // Copyright 2021 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  // SPDX-License-Identifier: BSD-3-Clause
     6  //
     7  
     8  package bzimage
     9  
    10  import (
    11  	"bytes"
    12  	"encoding/binary"
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"strconv"
    17  	"strings"
    18  	"time"
    19  )
    20  
    21  /*
    22  values from kernel documentation and libmagic src
    23  
    24  off val
    25  510 0xAA55
    26  514 HdrS
    27  526	(4 bytes) != 0x0000
    28  526 (2 bytes, little endian) + 0x200 -> start of null-terminated version string
    29  */
    30  
    31  const kverMax = 1024 // arbitrary
    32  
    33  var (
    34  	// ErrBootSig is returned when the boot sig is missing.
    35  	ErrBootSig = errors.New("missing 0x55AA boot sig")
    36  	// ErrBadSig is returned when the kernel header sig is missing.
    37  	ErrBadSig = errors.New("missing kernel header sig")
    38  	// ErrBadOff is returned if the version string offset is null.
    39  	ErrBadOff = errors.New("null version string offset")
    40  	// ErrParse is returned on a parse error.
    41  	ErrParse = errors.New("parse error")
    42  )
    43  
    44  // KVer reads the kernel version string. See also: (*BZImage)Kver()
    45  func KVer(k io.ReadSeeker) (string, error) {
    46  	buf := make([]byte, kverMax)
    47  	_, err := k.Seek(0, io.SeekStart)
    48  	if err != nil {
    49  		return "", err
    50  	}
    51  	_, err = k.Read(buf[:530])
    52  	if err != nil {
    53  		return "", err
    54  	}
    55  	if !bytes.Equal(buf[510:512], []byte{0x55, 0xaa}) {
    56  		return "", ErrBootSig
    57  	}
    58  	if string(buf[514:518]) != "HdrS" {
    59  		return "", ErrBadSig
    60  	}
    61  	if bytes.Equal(buf[526:530], []byte{0, 0, 0, 0}) {
    62  		return "", ErrBadOff
    63  	}
    64  	off := int64(binary.LittleEndian.Uint16(buf[526:528])) + 0x200
    65  	_, err = k.Seek(off, io.SeekStart)
    66  	if err != nil {
    67  		return "", err
    68  	}
    69  	if _, err := k.Read(buf[:]); err != nil {
    70  		return "", err
    71  	}
    72  	return nullterm(buf), nil
    73  }
    74  
    75  // KVer reads the kernel version string. See also: KVer() above.
    76  func (bz *BzImage) KVer() (string, error) {
    77  	if bz.Header.Kveraddr == 0 {
    78  		return "", ErrParse
    79  	}
    80  	start := uint64(bz.Header.Kveraddr + 0x200)
    81  	bclen := uint64(len(bz.BootCode))
    82  	hdrlen := uint64(bz.KernelOffset) - bclen
    83  	bcoffs := start - hdrlen
    84  	if bcoffs >= bclen {
    85  		return "", ErrParse
    86  	}
    87  	end := bcoffs + kverMax
    88  	if end > bclen {
    89  		end = bclen
    90  	}
    91  	return nullterm(bz.BootCode[bcoffs:end]), nil
    92  }
    93  
    94  // read c string from buffer
    95  func nullterm(buf []byte) string {
    96  	var i int
    97  	var b byte
    98  	for i, b = range buf {
    99  		if b == 0 {
   100  			break
   101  		}
   102  	}
   103  	return string(buf[:i])
   104  }
   105  
   106  // KInfo struct holds info extracted from the kernel's embedded version string
   107  //
   108  // 2.6.24.111 (bluebat@linux-vm-os64.site) #606 Mon Apr 14 00:06:11 CEST 2014
   109  // 4.19.16-norm_boot (user@host) #300 SMP Fri Jan 25 16:32:19 UTC 2019
   110  //
   111  //	release             (builder)         version
   112  //
   113  // maj.min.patch-localver                #buildnum SMP buildtime
   114  type KInfo struct {
   115  	Release, Version string // uname -r, uname -v respectfully
   116  	Builder          string // user@hostname in parenthesis, shown by `file` but not `uname`
   117  
   118  	// the following are extracted from Release and Version
   119  
   120  	BuildNum        uint64    //#nnn in Version, 300 in example above
   121  	BuildTime       time.Time // from Version
   122  	Maj, Min, Patch uint64    // from Release
   123  	LocalVer        string    // from Release
   124  }
   125  
   126  // Equal compares two KInfo structs and returns
   127  // true if the content is identical.
   128  func (l KInfo) Equal(r KInfo) bool {
   129  	return l.Release == r.Release &&
   130  		l.Builder == r.Builder &&
   131  		l.Version == r.Version &&
   132  		l.BuildNum == r.BuildNum &&
   133  		l.BuildTime.Equal(r.BuildTime) &&
   134  		l.Maj == r.Maj &&
   135  		l.Min == r.Min &&
   136  		l.Patch == r.Patch &&
   137  		l.LocalVer == r.LocalVer
   138  }
   139  
   140  const layout = "Mon Jan 2 15:04:05 MST 2006"
   141  
   142  // ParseDesc parses the output of KVer() or
   143  // BzImage.KVer(), returning a KInfo struct.
   144  func ParseDesc(desc string) (KInfo, error) {
   145  	var ki KInfo
   146  
   147  	// first split at #
   148  	split := strings.Split(desc, "#")
   149  	if len(split) != 2 {
   150  		return KInfo{}, fmt.Errorf("%w: %s: wrong number of '#' chars", ErrParse, desc)
   151  	}
   152  	ki.Version = "#" + split[1]
   153  
   154  	// now split first part into release and builder
   155  	elements := strings.SplitN(split[0], " ", 2)
   156  	if len(elements) > 2 {
   157  		return KInfo{}, fmt.Errorf("%w: %s: wrong number of spaces in release/builder", ErrParse, desc)
   158  	}
   159  	ki.Release = elements[0]
   160  	if len(elements) == 2 {
   161  		// not sure if this is _always_ present
   162  		ki.Builder = strings.Trim(elements[1], " ()")
   163  	}
   164  	// split build number off version
   165  	elements = strings.SplitN(split[1], " ", 2)
   166  	if len(elements) != 2 {
   167  		return KInfo{}, fmt.Errorf("%w: %s: wrong number of spaces in build/version", ErrParse, desc)
   168  	}
   169  	i, err := strconv.ParseUint(elements[0], 10, 64)
   170  	if err != nil {
   171  		return KInfo{}, fmt.Errorf("%s: bad uint %s: %w", desc, elements[0], err)
   172  	}
   173  	ki.BuildNum = i
   174  	// remove SMP if present
   175  	t := strings.TrimSpace(strings.TrimPrefix(elements[1], "SMP"))
   176  	// parse remainder as time, using reference time
   177  	ki.BuildTime, err = time.Parse(layout, t)
   178  	if err != nil {
   179  		return KInfo{}, fmt.Errorf("%s: bad time %s: %w", desc, t, err)
   180  	}
   181  	elements = strings.Split(ki.Release, ".")
   182  	if len(elements) < 3 {
   183  		return KInfo{}, fmt.Errorf("%w: %s: wrong number of dots in release %s", ErrParse, desc, ki.Release)
   184  	}
   185  	ki.Maj, err = strconv.ParseUint(elements[0], 10, 64)
   186  	if err != nil {
   187  		return KInfo{}, fmt.Errorf("%s: bad uint %s: %w", desc, elements[0], err)
   188  	}
   189  	ki.Min, err = strconv.ParseUint(elements[1], 10, 64)
   190  	if err != nil {
   191  		return KInfo{}, fmt.Errorf("%s: bad uint %s: %w", desc, elements[1], err)
   192  	}
   193  	elem := strings.SplitN(elements[2], "-", 2)
   194  	ki.Patch, err = strconv.ParseUint(elem[0], 10, 64)
   195  	if err != nil {
   196  		return KInfo{}, fmt.Errorf("%s: bad uint %s: %w", desc, elem[0], err)
   197  	}
   198  
   199  	elements = strings.SplitN(elements[len(elements)-1], "-", 2)
   200  	if len(elements) > 1 {
   201  		ki.LocalVer = elements[1]
   202  	}
   203  	return ki, nil
   204  }