github.com/rigado/snapd@v2.42.5-go-mod+incompatible/bootloader/lkenv/lkenv.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package lkenv 21 22 import ( 23 "bytes" 24 "encoding/binary" 25 "fmt" 26 "hash/crc32" 27 "os" 28 29 "github.com/snapcore/snapd/logger" 30 "github.com/snapcore/snapd/osutil" 31 ) 32 33 const SNAP_BOOTSELECT_VERSION = 0x00010001 34 35 // const SNAP_BOOTSELECT_SIGNATURE ('S' | ('B' << 8) | ('s' << 16) | ('e' << 24)) 36 const SNAP_BOOTSELECT_SIGNATURE = 0x53 | 0x42<<8 | 0x73<<16 | 0x65<<24 37 const SNAP_NAME_MAX_LEN = 256 38 39 /* number of available boot partitions */ 40 const SNAP_BOOTIMG_PART_NUM = 2 41 42 /* Default boot image file name to be used from kernel snap */ 43 const BOOTIMG_DEFAULT_NAME = "boot.img" 44 45 // for accessing the Bootimg_matrix 46 const ( 47 MATRIX_ROW_PARTITION = 0 48 MATRIX_ROW_KERNEL = 1 49 ) 50 51 /** 52 * Following structure has to be kept in sync with c structure defined by 53 * include/snappy-boot_v1.h 54 * c headerfile is used by bootloader, this ensures sync of the environment 55 * between snapd and bootloader 56 57 * when this structure needs to be updated, 58 * new version should be introduced instead together with c header file, 59 * which is to be adopted by bootloader 60 * 61 * !!! Support for old version has to be maintained, as it is not guaranteed 62 * all existing bootloader would adopt new version! 63 */ 64 type SnapBootSelect_v1 struct { 65 /* Contains value BOOTSELECT_SIGNATURE defined above */ 66 Signature uint32 67 /* snappy boot select version */ 68 Version uint32 69 70 /* snap_mode, one of: 'empty', "try", "trying" */ 71 Snap_mode [SNAP_NAME_MAX_LEN]byte 72 /* current core snap revision */ 73 Snap_core [SNAP_NAME_MAX_LEN]byte 74 /* try core snap revision */ 75 Snap_try_core [SNAP_NAME_MAX_LEN]byte 76 /* current kernel snap revision */ 77 Snap_kernel [SNAP_NAME_MAX_LEN]byte 78 /* current kernel snap revision */ 79 Snap_try_kernel [SNAP_NAME_MAX_LEN]byte 80 81 /* gadget_mode, one of: 'empty', "try", "trying" */ 82 Gadget_mode [SNAP_NAME_MAX_LEN]byte 83 /* GADGET assets: current gadget assets revision */ 84 Snap_gadget [SNAP_NAME_MAX_LEN]byte 85 /* GADGET assets: try gadget assets revision */ 86 Snap_try_gadget [SNAP_NAME_MAX_LEN]byte 87 88 /** 89 * Reboot reason 90 * optional parameter to signal bootloader alternative reboot reasons 91 * e.g. recovery/factory-reset/boot asset update 92 */ 93 Reboot_reason [SNAP_NAME_MAX_LEN]byte 94 95 /** 96 * Matrix for mapping of boot img partion to installed kernel snap revision 97 * 98 * First column represents boot image partition label (e.g. boot_a,boot_b ) 99 * value are static and should be populated at gadget built time 100 * or latest at image build time. Values are not further altered at run time. 101 * Second column represents name currently installed kernel snap 102 * e.g. pi2-kernel_123.snap 103 * initial value representing initial kernel snap revision 104 * is pupulated at image build time by snapd 105 * 106 * There are two rows in the matrix, representing current and previous kernel revision 107 * following describes how this matrix should be modified at different stages: 108 * - at image build time: 109 * - extracted kernel snap revision name should be filled 110 * into free slow (first row, second row) 111 * - snapd: 112 * - when new kernel snap revision is being installed, snapd cycles through 113 * matrix to find unused 'boot slot' to be used for new kernel snap revision 114 * from free slot, first column represents partition label to which kernel 115 * snap boot image should be extracted. Second column is then populated with 116 * kernel snap revision name. 117 * - snap_mode, snap_try_kernel, snap_try_core behaves same way as with u-boot 118 * - bootloader: 119 * - bootloader reads snap_mode to determine if snap_kernel or snap_kernel is used 120 * to get kernel snap revision name 121 * kernel snap revision is then used to search matrix to determine 122 * partition label to be used for current boot 123 * - bootloader NEVER alters this matrix values 124 * 125 * [ <bootimg 1 part label> ] [ <kernel snap revision installed in this boot partition> ] 126 * [ <bootimg 2 part label> ] [ <kernel snap revision installed in this boot partition> ] 127 */ 128 Bootimg_matrix [SNAP_BOOTIMG_PART_NUM][2][SNAP_NAME_MAX_LEN]byte 129 130 /** 131 * name of the boot image from kernel snap to be used for extraction 132 * when not defined or empty, default boot.img will be used 133 */ 134 Bootimg_file_name [SNAP_NAME_MAX_LEN]byte 135 136 /** 137 * gadget assets: Matrix for mapping of gadget asset partions 138 * Optional boot asset tracking, based on bootloader support 139 * Some boot chains support A/B boot assets for increased robustness 140 * example being A/B TrustExecutionEnvironment 141 * This matrix can be used to track current and try boot assets for 142 * robust updates 143 * Use of Gadget_asset_matrix matches use of Bootimg_matrix 144 * 145 * [ <boot assets 1 part label> ] [ <currently installed assets revision in this partition> ] 146 * [ <boot assets 2 part label> ] [ <currently installed assets revision in this partition> ] 147 */ 148 Gadget_asset_matrix [SNAP_BOOTIMG_PART_NUM][2][SNAP_NAME_MAX_LEN]byte 149 150 /* unused placeholders for additional parameters in the future */ 151 Unused_key_01 [SNAP_NAME_MAX_LEN]byte 152 Unused_key_02 [SNAP_NAME_MAX_LEN]byte 153 Unused_key_03 [SNAP_NAME_MAX_LEN]byte 154 Unused_key_04 [SNAP_NAME_MAX_LEN]byte 155 Unused_key_05 [SNAP_NAME_MAX_LEN]byte 156 Unused_key_06 [SNAP_NAME_MAX_LEN]byte 157 Unused_key_07 [SNAP_NAME_MAX_LEN]byte 158 Unused_key_08 [SNAP_NAME_MAX_LEN]byte 159 Unused_key_09 [SNAP_NAME_MAX_LEN]byte 160 Unused_key_10 [SNAP_NAME_MAX_LEN]byte 161 Unused_key_11 [SNAP_NAME_MAX_LEN]byte 162 Unused_key_12 [SNAP_NAME_MAX_LEN]byte 163 Unused_key_13 [SNAP_NAME_MAX_LEN]byte 164 Unused_key_14 [SNAP_NAME_MAX_LEN]byte 165 Unused_key_15 [SNAP_NAME_MAX_LEN]byte 166 Unused_key_16 [SNAP_NAME_MAX_LEN]byte 167 Unused_key_17 [SNAP_NAME_MAX_LEN]byte 168 Unused_key_18 [SNAP_NAME_MAX_LEN]byte 169 Unused_key_19 [SNAP_NAME_MAX_LEN]byte 170 Unused_key_20 [SNAP_NAME_MAX_LEN]byte 171 172 /* unused array of 10 key value pairs */ 173 Kye_value_pairs [10][2][SNAP_NAME_MAX_LEN]byte 174 175 /* crc32 value for structure */ 176 Crc32 uint32 177 } 178 179 // Env contains the data of the uboot environment 180 // path can be file or partition device node 181 type Env struct { 182 path string 183 pathbak string 184 env SnapBootSelect_v1 185 } 186 187 // cToGoString convert string in passed byte array into string type 188 // if string in byte array is not terminated, empty string is returned 189 func cToGoString(c []byte) string { 190 if end := bytes.IndexByte(c, 0); end >= 0 { 191 return string(c[:end]) 192 } 193 // no trailing \0 - return "" 194 return "" 195 } 196 197 // copyString copy passed string into byte array 198 // make sure string is terminated 199 // if string does not fit into byte array, it will be concatenated 200 func copyString(b []byte, s string) { 201 sl := len(s) 202 bs := len(b) 203 if bs > sl { 204 copy(b[:], s) 205 b[sl] = 0 206 } else { 207 copy(b[:bs-1], s) 208 b[bs-1] = 0 209 } 210 } 211 212 func NewEnv(path string) *Env { 213 // osutil.FileExists(path + "bak") 214 return &Env{ 215 path: path, 216 pathbak: path + "bak", 217 env: SnapBootSelect_v1{ 218 Signature: SNAP_BOOTSELECT_SIGNATURE, 219 Version: SNAP_BOOTSELECT_VERSION, 220 }, 221 } 222 } 223 224 func (l *Env) Get(key string) string { 225 switch key { 226 case "snap_mode": 227 return cToGoString(l.env.Snap_mode[:]) 228 case "snap_kernel": 229 return cToGoString(l.env.Snap_kernel[:]) 230 case "snap_try_kernel": 231 return cToGoString(l.env.Snap_try_kernel[:]) 232 case "snap_core": 233 return cToGoString(l.env.Snap_core[:]) 234 case "snap_try_core": 235 return cToGoString(l.env.Snap_try_core[:]) 236 case "snap_gadget": 237 return cToGoString(l.env.Snap_gadget[:]) 238 case "snap_try_gadget": 239 return cToGoString(l.env.Snap_try_gadget[:]) 240 case "reboot_reason": 241 return cToGoString(l.env.Reboot_reason[:]) 242 case "bootimg_file_name": 243 return cToGoString(l.env.Bootimg_file_name[:]) 244 } 245 return "" 246 } 247 248 func (l *Env) Set(key, value string) { 249 switch key { 250 case "snap_mode": 251 copyString(l.env.Snap_mode[:], value) 252 case "snap_kernel": 253 copyString(l.env.Snap_kernel[:], value) 254 case "snap_try_kernel": 255 copyString(l.env.Snap_try_kernel[:], value) 256 case "snap_core": 257 copyString(l.env.Snap_core[:], value) 258 case "snap_try_core": 259 copyString(l.env.Snap_try_core[:], value) 260 case "snap_gadget": 261 copyString(l.env.Snap_gadget[:], value) 262 case "snap_try_gadget": 263 copyString(l.env.Snap_try_gadget[:], value) 264 case "reboot_reason": 265 copyString(l.env.Reboot_reason[:], value) 266 case "bootimg_file_name": 267 copyString(l.env.Bootimg_file_name[:], value) 268 } 269 } 270 271 // ConfigureBootPartitions set boot partitions label names 272 // this function should not be used at run time! 273 // it should be used only at image build time, 274 // if partition labels are not pre-filled by gadget built 275 func (l *Env) ConfigureBootPartitions(boot_1, boot_2 string) { 276 copyString(l.env.Bootimg_matrix[0][MATRIX_ROW_PARTITION][:], boot_1) 277 copyString(l.env.Bootimg_matrix[1][MATRIX_ROW_PARTITION][:], boot_2) 278 } 279 280 // ConfigureBootimgName set boot image file name 281 // boot image file name is used at kernel extraction time 282 // this function should not be used at run time! 283 // it should be used only at image build time 284 // if default boot.img is not set by gadget built 285 func (l *Env) ConfigureBootimgName(bootimgName string) { 286 copyString(l.env.Bootimg_file_name[:], bootimgName) 287 } 288 289 func (l *Env) Load() error { 290 err := l.LoadEnv(l.path) 291 if err != nil { 292 return l.LoadEnv(l.pathbak) 293 } 294 return nil 295 } 296 297 func (l *Env) LoadEnv(path string) error { 298 f, err := os.Open(path) 299 if err != nil { 300 return fmt.Errorf("cannot open LK env file: %v", err) 301 } 302 303 defer f.Close() 304 if err := binary.Read(f, binary.LittleEndian, &l.env); err != nil { 305 return fmt.Errorf("cannot read LK env from file: %v", err) 306 } 307 308 // calculate crc32 to validate structure 309 w := bytes.NewBuffer(nil) 310 ss := binary.Size(l.env) 311 w.Grow(ss) 312 if err := binary.Write(w, binary.LittleEndian, &l.env); err != nil { 313 return fmt.Errorf("cannot write LK env to buffer for validation: %v", err) 314 } 315 if l.env.Version != SNAP_BOOTSELECT_VERSION || l.env.Signature != SNAP_BOOTSELECT_SIGNATURE { 316 return fmt.Errorf("cannot validate version/signature for %s, got 0x%X expected 0x%X, got 0x%X expected 0x%X\n", path, l.env.Version, SNAP_BOOTSELECT_VERSION, l.env.Signature, SNAP_BOOTSELECT_SIGNATURE) 317 } 318 319 crc := crc32.ChecksumIEEE(w.Bytes()[:ss-4]) // size of crc32 itself at the end of the structure 320 if crc != l.env.Crc32 { 321 return fmt.Errorf("cannot validate environment checksum %s, got 0x%X expected 0x%X\n", path, crc, l.env.Crc32) 322 } 323 logger.Debugf("Load: validated crc32 (0x%X)", l.env.Crc32) 324 return nil 325 } 326 327 func (l *Env) Save() error { 328 logger.Debugf("Save") 329 w := bytes.NewBuffer(nil) 330 ss := binary.Size(l.env) 331 w.Grow(ss) 332 if err := binary.Write(w, binary.LittleEndian, &l.env); err != nil { 333 return fmt.Errorf("cannot write LK env to buffer for saving: %v", err) 334 } 335 // calculate crc32 336 l.env.Crc32 = crc32.ChecksumIEEE(w.Bytes()[:ss-4]) 337 logger.Debugf("Save: calculated crc32 (0x%X)", l.env.Crc32) 338 w.Truncate(ss - 4) 339 binary.Write(w, binary.LittleEndian, &l.env.Crc32) 340 341 err := l.SaveEnv(l.path, w) 342 if err != nil { 343 logger.Debugf("Save: failed to save main environment") 344 } 345 // if there is backup environment file save to it as well 346 if osutil.FileExists(l.pathbak) { 347 if err := l.SaveEnv(l.pathbak, w); err != nil { 348 logger.Debugf("Save: failed to save backup environment %v", err) 349 } 350 } 351 return err 352 } 353 354 func (l *Env) SaveEnv(path string, buf *bytes.Buffer) error { 355 f, err := os.OpenFile(path, os.O_WRONLY, 0660) 356 if err != nil { 357 return fmt.Errorf("cannot open LK env file for env storing: %v", err) 358 } 359 defer f.Close() 360 361 if _, err := f.Write(buf.Bytes()); err != nil { 362 return fmt.Errorf("cannot write LK env buf to LK env file: %v", err) 363 } 364 if err := f.Sync(); err != nil { 365 return fmt.Errorf("cannot sync LK env file: %v", err) 366 } 367 return nil 368 } 369 370 // FindFreeBootPartition find free boot partition to be used for new kernel revision 371 // - consider kernel snap blob name, if kernel name matches 372 // already installed revision, return coresponding partition name 373 // - protect partition used by kernel_snap, consider other as free 374 // - consider only boot partitions with defined partition name 375 func (l *Env) FindFreeBootPartition(kernel string) (string, error) { 376 for x := range l.env.Bootimg_matrix { 377 bp := cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]) 378 if bp != "" { 379 k := cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:]) 380 if k != cToGoString(l.env.Snap_kernel[:]) || k == kernel || k == "" { 381 return cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]), nil 382 } 383 } 384 } 385 return "", fmt.Errorf("cannot find free partition for boot image") 386 } 387 388 // SetBootPartition set kernel revision name to passed boot partition 389 func (l *Env) SetBootPartition(bootpart, kernel string) error { 390 for x := range l.env.Bootimg_matrix { 391 if bootpart == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]) { 392 copyString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:], kernel) 393 return nil 394 } 395 } 396 return fmt.Errorf("cannot find defined [%s] boot image partition", bootpart) 397 } 398 399 func (l *Env) GetBootPartition(kernel string) (string, error) { 400 for x := range l.env.Bootimg_matrix { 401 if kernel == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:]) { 402 return cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]), nil 403 } 404 } 405 return "", fmt.Errorf("cannot find kernel %q in boot image partitions", kernel) 406 } 407 408 // FreeBootPartition free passed kernel revision from any boot partition 409 // ignore if there is no boot partition with given kernel revision 410 func (l *Env) FreeBootPartition(kernel string) (bool, error) { 411 for x := range l.env.Bootimg_matrix { 412 if "" != cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]) { 413 if kernel == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:]) { 414 l.env.Bootimg_matrix[x][1][MATRIX_ROW_PARTITION] = 0 415 return true, nil 416 } 417 } 418 } 419 return false, fmt.Errorf("cannot find defined [%s] boot image partition", kernel) 420 } 421 422 // GetBootImageName return expected boot image file name in kernel snap 423 func (l *Env) GetBootImageName() string { 424 if "" != cToGoString(l.env.Bootimg_file_name[:]) { 425 return cToGoString(l.env.Bootimg_file_name[:]) 426 } 427 return BOOTIMG_DEFAULT_NAME 428 }