github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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/lk/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 partition 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 populated 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 slot (first row, second column) 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_try_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 partitions 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 Key_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 return &Env{ 214 path: path, 215 pathbak: path + "bak", 216 env: SnapBootSelect_v1{ 217 Signature: SNAP_BOOTSELECT_SIGNATURE, 218 Version: SNAP_BOOTSELECT_VERSION, 219 }, 220 } 221 } 222 223 func (l *Env) Get(key string) string { 224 switch key { 225 case "snap_mode": 226 return cToGoString(l.env.Snap_mode[:]) 227 case "snap_kernel": 228 return cToGoString(l.env.Snap_kernel[:]) 229 case "snap_try_kernel": 230 return cToGoString(l.env.Snap_try_kernel[:]) 231 case "snap_core": 232 return cToGoString(l.env.Snap_core[:]) 233 case "snap_try_core": 234 return cToGoString(l.env.Snap_try_core[:]) 235 case "snap_gadget": 236 return cToGoString(l.env.Snap_gadget[:]) 237 case "snap_try_gadget": 238 return cToGoString(l.env.Snap_try_gadget[:]) 239 case "reboot_reason": 240 return cToGoString(l.env.Reboot_reason[:]) 241 case "bootimg_file_name": 242 return cToGoString(l.env.Bootimg_file_name[:]) 243 } 244 return "" 245 } 246 247 func (l *Env) Set(key, value string) { 248 switch key { 249 case "snap_mode": 250 copyString(l.env.Snap_mode[:], value) 251 case "snap_kernel": 252 copyString(l.env.Snap_kernel[:], value) 253 case "snap_try_kernel": 254 copyString(l.env.Snap_try_kernel[:], value) 255 case "snap_core": 256 copyString(l.env.Snap_core[:], value) 257 case "snap_try_core": 258 copyString(l.env.Snap_try_core[:], value) 259 case "snap_gadget": 260 copyString(l.env.Snap_gadget[:], value) 261 case "snap_try_gadget": 262 copyString(l.env.Snap_try_gadget[:], value) 263 case "reboot_reason": 264 copyString(l.env.Reboot_reason[:], value) 265 case "bootimg_file_name": 266 copyString(l.env.Bootimg_file_name[:], value) 267 } 268 } 269 270 // ConfigureBootPartitions set boot partitions label names 271 // this function should not be used at run time! 272 // it should be used only at image build time, 273 // if partition labels are not pre-filled by gadget built 274 func (l *Env) ConfigureBootPartitions(boot_1, boot_2 string) { 275 copyString(l.env.Bootimg_matrix[0][MATRIX_ROW_PARTITION][:], boot_1) 276 copyString(l.env.Bootimg_matrix[1][MATRIX_ROW_PARTITION][:], boot_2) 277 } 278 279 // ConfigureBootimgName set boot image file name 280 // boot image file name is used at kernel extraction time 281 // this function should not be used at run time! 282 // it should be used only at image build time 283 // if default boot.img is not set by gadget built 284 func (l *Env) ConfigureBootimgName(bootimgName string) { 285 copyString(l.env.Bootimg_file_name[:], bootimgName) 286 } 287 288 func (l *Env) Load() error { 289 err := l.LoadEnv(l.path) 290 if err != nil { 291 return l.LoadEnv(l.pathbak) 292 } 293 return nil 294 } 295 296 func (l *Env) LoadEnv(path string) error { 297 f, err := os.Open(path) 298 if err != nil { 299 return fmt.Errorf("cannot open LK env file: %v", err) 300 } 301 302 defer f.Close() 303 if err := binary.Read(f, binary.LittleEndian, &l.env); err != nil { 304 return fmt.Errorf("cannot read LK env from file: %v", err) 305 } 306 307 // calculate crc32 to validate structure 308 w := bytes.NewBuffer(nil) 309 ss := binary.Size(l.env) 310 w.Grow(ss) 311 if err := binary.Write(w, binary.LittleEndian, &l.env); err != nil { 312 return fmt.Errorf("cannot write LK env to buffer for validation: %v", err) 313 } 314 if l.env.Version != SNAP_BOOTSELECT_VERSION || l.env.Signature != SNAP_BOOTSELECT_SIGNATURE { 315 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) 316 } 317 318 crc := crc32.ChecksumIEEE(w.Bytes()[:ss-4]) // size of crc32 itself at the end of the structure 319 if crc != l.env.Crc32 { 320 return fmt.Errorf("cannot validate environment checksum %s, got 0x%X expected 0x%X\n", path, crc, l.env.Crc32) 321 } 322 logger.Debugf("Load: validated crc32 (0x%X)", l.env.Crc32) 323 return nil 324 } 325 326 func (l *Env) Save() error { 327 logger.Debugf("Save") 328 w := bytes.NewBuffer(nil) 329 ss := binary.Size(l.env) 330 w.Grow(ss) 331 if err := binary.Write(w, binary.LittleEndian, &l.env); err != nil { 332 return fmt.Errorf("cannot write LK env to buffer for saving: %v", err) 333 } 334 // calculate crc32 335 l.env.Crc32 = crc32.ChecksumIEEE(w.Bytes()[:ss-4]) 336 logger.Debugf("Save: calculated crc32 (0x%X)", l.env.Crc32) 337 w.Truncate(ss - 4) 338 binary.Write(w, binary.LittleEndian, &l.env.Crc32) 339 340 err := l.saveEnv(l.path, w) 341 if err != nil { 342 logger.Debugf("Save: failed to save main environment") 343 } 344 // if there is backup environment file save to it as well 345 if osutil.FileExists(l.pathbak) { 346 if err := l.saveEnv(l.pathbak, w); err != nil { 347 logger.Debugf("Save: failed to save backup environment %v", err) 348 } 349 } 350 return err 351 } 352 353 func (l *Env) saveEnv(path string, buf *bytes.Buffer) error { 354 f, err := os.OpenFile(path, os.O_WRONLY, 0660) 355 if err != nil { 356 return fmt.Errorf("cannot open LK env file for env storing: %v", err) 357 } 358 defer f.Close() 359 360 if _, err := f.Write(buf.Bytes()); err != nil { 361 return fmt.Errorf("cannot write LK env buf to LK env file: %v", err) 362 } 363 if err := f.Sync(); err != nil { 364 return fmt.Errorf("cannot sync LK env file: %v", err) 365 } 366 return nil 367 } 368 369 // FindFreeBootPartition find free boot partition to be used for new kernel revision 370 // - consider kernel snap blob name, if kernel name matches 371 // already installed revision, return coresponding partition name 372 // - protect partition used by kernel_snap, consider other as free 373 // - consider only boot partitions with defined partition name 374 func (l *Env) FindFreeBootPartition(kernel string) (string, error) { 375 for x := range l.env.Bootimg_matrix { 376 bp := cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]) 377 if bp != "" { 378 k := cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:]) 379 if k != cToGoString(l.env.Snap_kernel[:]) || k == kernel || k == "" { 380 return cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]), nil 381 } 382 } 383 } 384 return "", fmt.Errorf("cannot find free partition for boot image") 385 } 386 387 // SetBootPartition sets the kernel revision reference in the provided boot 388 // partition reference to the provided kernel revision. It returns a non-nil err 389 // if the provided boot partition reference was not found. 390 func (l *Env) SetBootPartition(bootpart, kernel string) error { 391 for x := range l.env.Bootimg_matrix { 392 if bootpart == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]) { 393 copyString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:], kernel) 394 return nil 395 } 396 } 397 return fmt.Errorf("cannot find defined [%s] boot image partition", bootpart) 398 } 399 400 // GetBootPartition returns the first found boot partition that contains a 401 // reference to the given kernel revision. If the revision was not found, a 402 // non-nil error is returned. 403 func (l *Env) GetBootPartition(kernel string) (string, error) { 404 for x := range l.env.Bootimg_matrix { 405 if kernel == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:]) { 406 return cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]), nil 407 } 408 } 409 return "", fmt.Errorf("cannot find kernel %q in boot image partitions", kernel) 410 } 411 412 // RemoveKernelRevisionFromBootPartition removes from the boot image matrix the 413 // first found boot partition that contains a reference to the given kernel 414 // revision. If the referenced kernel revision was not found, a non-nil err is 415 // returned, otherwise the reference is removed and nil is returned. 416 // Note that to persist this change the env must be saved afterwards with Save. 417 func (l *Env) RemoveKernelRevisionFromBootPartition(kernel string) error { 418 for x := range l.env.Bootimg_matrix { 419 if "" != cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]) { 420 if kernel == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:]) { 421 l.env.Bootimg_matrix[x][1][MATRIX_ROW_PARTITION] = 0 422 return nil 423 } 424 } 425 } 426 return fmt.Errorf("cannot find defined [%s] boot image partition", kernel) 427 } 428 429 // GetBootImageName return expected boot image file name in kernel snap 430 func (l *Env) GetBootImageName() string { 431 if "" != cToGoString(l.env.Bootimg_file_name[:]) { 432 return cToGoString(l.env.Bootimg_file_name[:]) 433 } 434 return BOOTIMG_DEFAULT_NAME 435 }