gitee.com/mysnapcore/mysnapd@v0.1.0/boot/bootchain.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020-2022 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 boot 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "fmt" 26 "os" 27 "path/filepath" 28 "sort" 29 30 "gitee.com/mysnapcore/mysnapd/asserts" 31 "gitee.com/mysnapcore/mysnapd/bootloader" 32 "gitee.com/mysnapcore/mysnapd/dirs" 33 "gitee.com/mysnapcore/mysnapd/osutil" 34 "gitee.com/mysnapcore/mysnapd/secboot" 35 ) 36 37 // TODO:UC20 add a doc comment when this is stabilized 38 type bootChain struct { 39 BrandID string `json:"brand-id"` 40 Model string `json:"model"` 41 Classic bool `json:"classic,omitempty"` 42 Grade asserts.ModelGrade `json:"grade"` 43 ModelSignKeyID string `json:"model-sign-key-id"` 44 AssetChain []bootAsset `json:"asset-chain"` 45 Kernel string `json:"kernel"` 46 // KernelRevision is the revision of the kernel snap. It is empty if 47 // kernel is unasserted, in which case always reseal. 48 KernelRevision string `json:"kernel-revision"` 49 KernelCmdlines []string `json:"kernel-cmdlines"` 50 51 kernelBootFile bootloader.BootFile 52 } 53 54 func (b *bootChain) modelForSealing() *modelForSealing { 55 return &modelForSealing{ 56 brandID: b.BrandID, 57 model: b.Model, 58 classic: b.Classic, 59 grade: b.Grade, 60 modelSignKeyID: b.ModelSignKeyID, 61 } 62 } 63 64 // TODO:UC20 add a doc comment when this is stabilized 65 type bootAsset struct { 66 Role bootloader.Role `json:"role"` 67 Name string `json:"name"` 68 Hashes []string `json:"hashes"` 69 } 70 71 func bootAssetLess(b, other *bootAsset) bool { 72 byRole := b.Role < other.Role 73 byName := b.Name < other.Name 74 // sort order: role -> name -> hash list (len -> lexical) 75 if b.Role != other.Role { 76 return byRole 77 } 78 if b.Name != other.Name { 79 return byName 80 } 81 return stringListsLess(b.Hashes, other.Hashes) 82 } 83 84 func stringListsEqual(sl1, sl2 []string) bool { 85 if len(sl1) != len(sl2) { 86 return false 87 } 88 for i := range sl1 { 89 if sl1[i] != sl2[i] { 90 return false 91 } 92 } 93 return true 94 } 95 96 func stringListsLess(sl1, sl2 []string) bool { 97 if len(sl1) != len(sl2) { 98 return len(sl1) < len(sl2) 99 } 100 for idx := range sl1 { 101 if sl1[idx] < sl2[idx] { 102 return true 103 } 104 } 105 return false 106 } 107 108 func toPredictableBootAsset(b *bootAsset) *bootAsset { 109 if b == nil { 110 return nil 111 } 112 newB := *b 113 if b.Hashes != nil { 114 newB.Hashes = make([]string, len(b.Hashes)) 115 copy(newB.Hashes, b.Hashes) 116 sort.Strings(newB.Hashes) 117 } 118 return &newB 119 } 120 121 func toPredictableBootChain(b *bootChain) *bootChain { 122 if b == nil { 123 return nil 124 } 125 newB := *b 126 if b.AssetChain != nil { 127 newB.AssetChain = make([]bootAsset, len(b.AssetChain)) 128 for i := range b.AssetChain { 129 newB.AssetChain[i] = *toPredictableBootAsset(&b.AssetChain[i]) 130 } 131 } 132 if b.KernelCmdlines != nil { 133 newB.KernelCmdlines = make([]string, len(b.KernelCmdlines)) 134 copy(newB.KernelCmdlines, b.KernelCmdlines) 135 sort.Strings(newB.KernelCmdlines) 136 } 137 return &newB 138 } 139 140 func predictableBootAssetsEqual(b1, b2 []bootAsset) bool { 141 b1JSON, err := json.Marshal(b1) 142 if err != nil { 143 return false 144 } 145 b2JSON, err := json.Marshal(b2) 146 if err != nil { 147 return false 148 } 149 return bytes.Equal(b1JSON, b2JSON) 150 } 151 152 func predictableBootAssetsLess(b1, b2 []bootAsset) bool { 153 if len(b1) != len(b2) { 154 return len(b1) < len(b2) 155 } 156 for i := range b1 { 157 if bootAssetLess(&b1[i], &b2[i]) { 158 return true 159 } 160 } 161 return false 162 } 163 164 type byBootChainOrder []bootChain 165 166 func (b byBootChainOrder) Len() int { return len(b) } 167 func (b byBootChainOrder) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 168 func (b byBootChainOrder) Less(i, j int) bool { 169 // sort by model info 170 if b[i].BrandID != b[j].BrandID { 171 return b[i].BrandID < b[j].BrandID 172 } 173 if b[i].Model != b[j].Model { 174 return b[i].Model < b[j].Model 175 } 176 if b[i].Grade != b[j].Grade { 177 return b[i].Grade < b[j].Grade 178 } 179 if b[i].ModelSignKeyID != b[j].ModelSignKeyID { 180 return b[i].ModelSignKeyID < b[j].ModelSignKeyID 181 } 182 // then boot assets 183 if !predictableBootAssetsEqual(b[i].AssetChain, b[j].AssetChain) { 184 return predictableBootAssetsLess(b[i].AssetChain, b[j].AssetChain) 185 } 186 // then kernel 187 if b[i].Kernel != b[j].Kernel { 188 return b[i].Kernel < b[j].Kernel 189 } 190 if b[i].KernelRevision != b[j].KernelRevision { 191 return b[i].KernelRevision < b[j].KernelRevision 192 } 193 // and last kernel command lines 194 if !stringListsEqual(b[i].KernelCmdlines, b[j].KernelCmdlines) { 195 return stringListsLess(b[i].KernelCmdlines, b[j].KernelCmdlines) 196 } 197 return false 198 } 199 200 type predictableBootChains []bootChain 201 202 // hasUnrevisionedKernels returns true if any of the chains have an 203 // unrevisioned kernel. Revisions will not be set for unasserted 204 // kernels. 205 func (pbc predictableBootChains) hasUnrevisionedKernels() bool { 206 for i := range pbc { 207 if pbc[i].KernelRevision == "" { 208 return true 209 } 210 } 211 return false 212 } 213 214 func toPredictableBootChains(chains []bootChain) predictableBootChains { 215 if chains == nil { 216 return nil 217 } 218 predictableChains := make([]bootChain, len(chains)) 219 for i := range chains { 220 predictableChains[i] = *toPredictableBootChain(&chains[i]) 221 } 222 sort.Sort(byBootChainOrder(predictableChains)) 223 return predictableChains 224 } 225 226 type bootChainEquivalence int 227 228 const ( 229 bootChainEquivalent bootChainEquivalence = 0 230 bootChainDifferent bootChainEquivalence = 1 231 bootChainUnrevisioned bootChainEquivalence = -1 232 ) 233 234 // predictableBootChainsEqualForReseal returns bootChainEquivalent 235 // when boot chains are equivalent for reseal. If the boot chains 236 // are clearly different it returns bootChainDifferent. 237 // If it would return bootChainEquivalent but the chains contain 238 // unrevisioned kernels it will return bootChainUnrevisioned. 239 func predictableBootChainsEqualForReseal(pb1, pb2 predictableBootChains) bootChainEquivalence { 240 pb1JSON, err := json.Marshal(pb1) 241 if err != nil { 242 return bootChainDifferent 243 } 244 pb2JSON, err := json.Marshal(pb2) 245 if err != nil { 246 return bootChainDifferent 247 } 248 if bytes.Equal(pb1JSON, pb2JSON) { 249 if pb1.hasUnrevisionedKernels() { 250 return bootChainUnrevisioned 251 } 252 return bootChainEquivalent 253 } 254 return bootChainDifferent 255 } 256 257 // bootAssetsToLoadChains generates a list of load chains covering given boot 258 // assets sequence. At the end of each chain, adds an entry for the kernel boot 259 // file. 260 func bootAssetsToLoadChains(assets []bootAsset, kernelBootFile bootloader.BootFile, roleToBlName map[bootloader.Role]string) ([]*secboot.LoadChain, error) { 261 // kernel is added after all the assets 262 addKernelBootFile := len(assets) == 0 263 if addKernelBootFile { 264 return []*secboot.LoadChain{secboot.NewLoadChain(kernelBootFile)}, nil 265 } 266 267 thisAsset := assets[0] 268 blName := roleToBlName[thisAsset.Role] 269 if blName == "" { 270 return nil, fmt.Errorf("internal error: no bootloader name for boot asset role %q", thisAsset.Role) 271 } 272 var chains []*secboot.LoadChain 273 for _, hash := range thisAsset.Hashes { 274 var bf bootloader.BootFile 275 var next []*secboot.LoadChain 276 var err error 277 278 p := filepath.Join( 279 dirs.SnapBootAssetsDir, 280 trustedAssetCacheRelPath(blName, thisAsset.Name, hash)) 281 if !osutil.FileExists(p) { 282 return nil, fmt.Errorf("file %s not found in boot assets cache", p) 283 } 284 bf = bootloader.NewBootFile( 285 "", // asset comes from the filesystem, not a snap 286 p, 287 thisAsset.Role, 288 ) 289 next, err = bootAssetsToLoadChains(assets[1:], kernelBootFile, roleToBlName) 290 if err != nil { 291 return nil, err 292 } 293 chains = append(chains, secboot.NewLoadChain(bf, next...)) 294 } 295 return chains, nil 296 } 297 298 // predictableBootChainsWrapperForStorage wraps the boot chains so 299 // that we do not store the arrays directly as JSON and we can add 300 // other information 301 type predictableBootChainsWrapperForStorage struct { 302 ResealCount int `json:"reseal-count"` 303 BootChains predictableBootChains `json:"boot-chains"` 304 } 305 306 func readBootChains(path string) (pbc predictableBootChains, resealCount int, err error) { 307 inf, err := os.Open(path) 308 if err != nil { 309 if os.IsNotExist(err) { 310 return nil, 0, nil 311 } 312 return nil, 0, fmt.Errorf("cannot open existing boot chains data file: %v", err) 313 } 314 defer inf.Close() 315 var wrapped predictableBootChainsWrapperForStorage 316 if err := json.NewDecoder(inf).Decode(&wrapped); err != nil { 317 return nil, 0, fmt.Errorf("cannot read boot chains data: %v", err) 318 } 319 return wrapped.BootChains, wrapped.ResealCount, nil 320 } 321 322 func writeBootChains(pbc predictableBootChains, path string, resealCount int) error { 323 if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { 324 return fmt.Errorf("cannot create device fde state directory: %v", err) 325 } 326 outf, err := osutil.NewAtomicFile(path, 0600, 0, osutil.NoChown, osutil.NoChown) 327 if err != nil { 328 return fmt.Errorf("cannot create a temporary boot chains file: %v", err) 329 } 330 // becomes noop when the file is committed 331 defer outf.Cancel() 332 333 wrapped := predictableBootChainsWrapperForStorage{ 334 ResealCount: resealCount, 335 BootChains: pbc, 336 } 337 if err := json.NewEncoder(outf).Encode(wrapped); err != nil { 338 return fmt.Errorf("cannot write boot chains data: %v", err) 339 } 340 return outf.Commit() 341 }