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 }