github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/boot/bzimage/bzimage.go (about) 1 // Copyright 2015-2018 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 // Package bzImage implements decoding for bzImage files. 6 // 7 // The bzImage struct contains all the information about the file and can 8 // be used to create a new bzImage. 9 package bzimage 10 11 // xz --check=crc32 $BCJ --lzma2=$LZMA2OPTS,dict=32MiB 12 13 import ( 14 "bytes" 15 "debug/elf" 16 "encoding/binary" 17 "fmt" 18 "io" 19 "io/ioutil" 20 "os/exec" 21 "reflect" 22 "strings" 23 24 "github.com/u-root/u-root/pkg/cpio" 25 ) 26 27 var ( 28 xzmagic = [...]byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00} 29 // String of unknown meaning. 30 // The build script has this value: 31 //initRAMFStag = [4]byte{0250, 0362, 0156, 0x01} 32 // The resultant bzd has this value: 33 initRAMFStag = [4]byte{0xf8, 0x85, 0x21, 0x01} 34 Debug = func(string, ...interface{}) {} 35 ) 36 37 // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. 38 // For now, it hardwires the KernelBase to 0x100000. 39 // bzImages were created by a process of evilution, and they are wondrous to behold. 40 // bzImages are almost impossible to modify. They form a sandwich with 41 // the compressed kernel code in the middle. It's actually a BLT: 42 // MBR and bootparams first 512 bytes 43 // the MBR includes 0xc0 bytes of boot code which is vestigial. 44 // Then there is "preamble" code which is the kernel decompressor; then the 45 // xz compressed kernel; then a library of sorts after the kernel which is called 46 // by the early uncompressed kernel code. This is all linked together and forms 47 // an essentially indivisible whole -- which we wish to divisible. 48 // Hence the groveling around for the xz header that you see here, and hence the checks 49 // to ensure that the kernel layout ends up largely the same before and after. 50 // That said, if you keep layout unchanged, you can modify the uncompressed kernel. 51 // For example, when you first build a kernel, you can: 52 // dd if=/dev/urandom of=x bs=1048576 count=8 53 // echo x | cpio -o > x.cpio 54 // and use that as an initrd, it's more or less an 8 MiB block you can replace 55 // as needed. Just make sure nothing grows. And make sure the initramfs is in 56 // the same place. Ah, joy. 57 func (b *BzImage) UnmarshalBinary(d []byte) error { 58 Debug("Processing %d byte image", len(d)) 59 r := bytes.NewBuffer(d) 60 if err := binary.Read(r, binary.LittleEndian, &b.Header); err != nil { 61 return err 62 } 63 Debug("Header was %d bytes", len(d)-r.Len()) 64 Debug("magic %x switch %v", b.Header.HeaderMagic, b.Header.RealModeSwitch) 65 if b.Header.HeaderMagic != HeaderMagic { 66 return fmt.Errorf("not a bzImage: magic should be %02x, and is %02x", HeaderMagic, b.Header.HeaderMagic) 67 } 68 Debug("RamDisk image %x size %x", b.Header.RamDiskImage, b.Header.RamDiskSize) 69 Debug("StartSys %x", b.Header.StartSys) 70 Debug("Boot type: %s(%x)", LoaderType[boottype(b.Header.TypeOfLoader)], b.Header.TypeOfLoader) 71 Debug("SetupSects %d", b.Header.SetupSects) 72 73 off := len(d) - r.Len() 74 b.KernelOffset = (uintptr(b.Header.SetupSects) + 1) * 512 75 bclen := int(b.KernelOffset) - off 76 Debug("Kernel offset is %d bytes, low1mcode is %d bytes", b.KernelOffset, bclen) 77 b.BootCode = make([]byte, bclen) 78 if _, err := r.Read(b.BootCode); err != nil { 79 return err 80 } 81 Debug("%d bytes of BootCode", len(b.BootCode)) 82 83 Debug("Remaining length is %d bytes, PayloadSize %d", r.Len(), b.Header.PayloadSize) 84 x := bytes.Index(r.Bytes(), xzmagic[:]) 85 if x == -1 { 86 return fmt.Errorf("can't find xz header") 87 } 88 Debug("xz is at %d", x) 89 b.HeadCode = make([]byte, x) 90 if _, err := r.Read(b.HeadCode); err != nil { 91 return fmt.Errorf("can't read HeadCode: %v", err) 92 } 93 // Now size up the kernel code. Is it just PayloadSize? 94 b.compressed = make([]byte, b.Header.PayloadSize) 95 if _, err := r.Read(b.compressed); err != nil { 96 return fmt.Errorf("can't read HeadCode: %v", err) 97 } 98 var err error 99 Debug("Uncompress %d bytes", len(b.compressed)) 100 if b.KernelCode, err = unpack(b.compressed); err != nil { 101 return err 102 } 103 b.TailCode = make([]byte, r.Len()) 104 if _, err := r.Read(b.TailCode); err != nil { 105 return fmt.Errorf("can't read TailCode: %v", err) 106 } 107 Debug("Kernel at %d, %d bytes", b.KernelOffset, len(b.KernelCode)) 108 b.KernelBase = uintptr(0x100000) 109 if b.Header.RamDiskImage == 0 { 110 return nil 111 } 112 if r.Len() != 0 { 113 return fmt.Errorf("%d bytes left over", r.Len()) 114 } 115 return nil 116 } 117 118 // MarshalBinary implements the encoding.BinaryMarshaler interface. 119 func (b *BzImage) MarshalBinary() ([]byte, error) { 120 // First step, make sure we can compress the kernel. 121 dat, err := compress(b.KernelCode, "--lzma2=,dict=32MiB") 122 if err != nil { 123 return nil, err 124 } 125 dat = append(dat, initRAMFStag[:]...) 126 if len(dat) > len(b.compressed) { 127 return nil, fmt.Errorf("marshal: compressed KernelCode too big: was %d, now %d", len(b.compressed), len(dat)) 128 } 129 Debug("b.compressed len %#x dat len %#x pad it out", len(b.compressed), len(dat)) 130 if len(dat) < len(b.compressed) { 131 l := len(dat) 132 n := make([]byte, len(b.compressed)-4) 133 copy(n, dat[:l-4]) 134 n = append(n, initRAMFStag[:]...) 135 dat = n 136 } 137 138 var w bytes.Buffer 139 if err := binary.Write(&w, binary.LittleEndian, &b.Header); err != nil { 140 return nil, err 141 } 142 Debug("Wrote %d bytes of header", w.Len()) 143 if _, err := w.Write(b.BootCode); err != nil { 144 return nil, err 145 } 146 Debug("Wrote %d bytes of BootCode", w.Len()) 147 if _, err := w.Write(b.HeadCode); err != nil { 148 return nil, err 149 } 150 Debug("Wrote %d bytes of HeadCode", w.Len()) 151 if _, err := w.Write(dat); err != nil { 152 return nil, err 153 } 154 Debug("Last bytes %#02x", dat[len(dat)-4:]) 155 Debug("Last bytes %#o", dat[len(dat)-4:]) 156 Debug("Last bytes %#d", dat[len(dat)-4:]) 157 b.compressed = dat 158 Debug("Wrote %d bytes of Compressed kernel", w.Len()) 159 if _, err := w.Write(b.TailCode); err != nil { 160 return nil, err 161 } 162 Debug("Wrote %d bytes of header", w.Len()) 163 Debug("Finished writing, len is now %d bytes", w.Len()) 164 165 return w.Bytes(), nil 166 } 167 168 // unpack extracts the header code and data from the kernel part 169 // of the bzImage. It also uncompresses the kernel. 170 // It searches the Kernel []byte for an xz header. Where it begins 171 // is never certain. We only do relatively newer images, i.e. we only 172 // look for the xz magic. 173 func unpack(d []byte) ([]byte, error) { 174 Debug("Kernel is %d bytes", len(d)) 175 Debug("Some kernel data: %#02x %#02x", d[:32], d[len(d)-8:]) 176 c := exec.Command("xzcat") 177 stdout, err := c.StdoutPipe() 178 if err != nil { 179 return nil, err 180 } 181 stderr, err := c.StderrPipe() 182 if err != nil { 183 return nil, err 184 } 185 c.Stdin = bytes.NewBuffer(d) 186 if err := c.Start(); err != nil { 187 return nil, err 188 } 189 190 dat, err := ioutil.ReadAll(stdout) 191 if err != nil { 192 return nil, err 193 } 194 // fyi, the xz standard and code are shit. A shame. 195 // You can enable this if you have a nasty bug from xz. 196 // Just be aware that xz ALWAYS errors out even when nothing is wrong. 197 if false { 198 if e, err := ioutil.ReadAll(stderr); err != nil || len(e) > 0 { 199 Debug("xz stderr: '%s', %v", string(e), err) 200 } 201 } 202 Debug("Uncompressed kernel is %d bytes", len(dat)) 203 return dat, nil 204 } 205 206 // compress compresses a []byte via xz using the dictOps, collecting it from stdout 207 func compress(b []byte, dictOps string) ([]byte, error) { 208 Debug("b is %d bytes", len(b)) 209 c := exec.Command("xz", "--check=crc32", "--x86", dictOps, "--stdout") 210 stdout, err := c.StdoutPipe() 211 if err != nil { 212 return nil, err 213 } 214 c.Stdin = bytes.NewBuffer(b) 215 if err := c.Start(); err != nil { 216 return nil, err 217 } 218 219 dat, err := ioutil.ReadAll(stdout) 220 if err != nil { 221 return nil, err 222 } 223 if err := c.Wait(); err != nil { 224 return nil, err 225 } 226 Debug("Compressed data is %d bytes, starts with %#02x", len(dat), dat[:32]) 227 Debug("Last 16 bytes: %#02x", dat[len(dat)-16:]) 228 return dat, nil 229 } 230 231 // Extract extracts the KernelCode as an ELF. 232 func (b *BzImage) ELF() (*elf.File, error) { 233 e, err := elf.NewFile(bytes.NewReader(b.KernelCode)) 234 if err != nil { 235 return nil, err 236 } 237 return e, nil 238 } 239 240 func Equal(a, b []byte) error { 241 if len(a) != len(b) { 242 return fmt.Errorf("images differ in len: %d bytes and %d bytes", len(a), len(b)) 243 } 244 var ba BzImage 245 if err := ba.UnmarshalBinary(a); err != nil { 246 return err 247 } 248 var bb BzImage 249 if err := bb.UnmarshalBinary(b); err != nil { 250 return err 251 } 252 if !reflect.DeepEqual(ba.Header, bb.Header) { 253 return fmt.Errorf("headers do not match: %s", ba.Header.Diff(&bb.Header)) 254 } 255 // this is overkill, I can't see any way it can happen. 256 if len(ba.KernelCode) != len(bb.KernelCode) { 257 return fmt.Errorf("kernel lengths differ: %d vs %d bytes", len(ba.KernelCode), len(bb.KernelCode)) 258 } 259 if len(ba.BootCode) != len(bb.BootCode) { 260 return fmt.Errorf("boot code lengths differ: %d vs %d bytes", len(ba.KernelCode), len(bb.KernelCode)) 261 } 262 263 if !reflect.DeepEqual(ba.BootCode, bb.BootCode) { 264 return fmt.Errorf("boot code does not match") 265 } 266 if !reflect.DeepEqual(ba.KernelCode, bb.KernelCode) { 267 return fmt.Errorf("kernels do not match") 268 } 269 return nil 270 } 271 272 func (b *BzImage) AddInitRAMFS(name string) error { 273 u, err := ioutil.ReadFile(name) 274 if err != nil { 275 return err 276 } 277 // Should we ever want to compress the initramfs this is one 278 // way to do it. 279 d := u 280 if false { 281 d, err = compress(u, "--lzma2=,dict=1MiB") 282 if err != nil { 283 return err 284 } 285 } 286 s, e, err := b.InitRAMFS() 287 if err != nil { 288 return err 289 } 290 l := e - s 291 292 if len(d) > l { 293 return fmt.Errorf("new initramfs is %d bytes, won't fit in %d byte old one", len(d), l) 294 } 295 // Do this in a stupid way that is easy to read. 296 // What's interesting: the kernel decompressor, if I read it right, 297 // finds it easier to skip a bunch of leading nulls. So do that. 298 n := make([]byte, l) 299 Debug("Offset into n is %d\n", len(n)-len(d)) 300 copy(n[len(n)-len(d):], d) 301 Debug("Install %d byte initramfs in %d bytes of kernel code, @ %d:%d", len(d), len(n), s, e) 302 copy(b.KernelCode[s:e], n) 303 return nil 304 } 305 306 // MakeLinuxHeader marshals a LinuxHeader into a []byte. 307 func MakeLinuxHeader(h *LinuxHeader) ([]byte, error) { 308 buf := new(bytes.Buffer) 309 err := binary.Write(buf, binary.LittleEndian, h) 310 return buf.Bytes(), err 311 } 312 313 // Show stringifies a LinuxHeader into a []string 314 func (h *LinuxHeader) Show() []string { 315 var s []string 316 317 val := reflect.ValueOf(*h) 318 for i := 0; i < val.NumField(); i++ { 319 v := val.Field(i) 320 k := reflect.ValueOf(v).Kind() 321 n := val.Type().Field(i).Name 322 switch k { 323 case reflect.Slice: 324 s = append(s, fmt.Sprintf("%s:%#02x", n, v)) 325 case reflect.Bool: 326 s = append(s, fmt.Sprintf("%s:%v", n, v)) 327 default: 328 s = append(s, fmt.Sprintf("%s:%#02x", n, v)) 329 } 330 } 331 return s 332 } 333 334 // Diff is a convenience function that returns a string showing 335 // differences between a header and another header. 336 func (h *LinuxHeader) Diff(i *LinuxHeader) string { 337 var s string 338 hs := h.Show() 339 is := i.Show() 340 for i := range hs { 341 if hs[i] != is[i] { 342 s += fmt.Sprintf("%s != %s", hs[i], is[i]) 343 } 344 } 345 return s 346 } 347 348 // Diff is a convenience function that returns a string showing 349 // differences between a bzImage and another bzImage 350 func (b *BzImage) Diff(b2 *BzImage) string { 351 s := b.Header.Diff(&b2.Header) 352 if len(b.BootCode) != len(b2.BootCode) { 353 s = s + fmt.Sprintf("b Bootcode is %d; b2 BootCode is %d", len(b.BootCode), len(b2.BootCode)) 354 } 355 if len(b.HeadCode) != len(b2.HeadCode) { 356 s = s + fmt.Sprintf("b Headcode is %d; b2 HeadCode is %d", len(b.HeadCode), len(b2.HeadCode)) 357 } 358 if len(b.KernelCode) != len(b2.KernelCode) { 359 s = s + fmt.Sprintf("b Kernelcode is %d; b2 KernelCode is %d", len(b.KernelCode), len(b2.KernelCode)) 360 } 361 if len(b.TailCode) != len(b2.TailCode) { 362 s = s + fmt.Sprintf("b Tailcode is %d; b2 TailCode is %d", len(b.TailCode), len(b2.TailCode)) 363 } 364 if b.KernelBase != b2.KernelBase { 365 s = s + fmt.Sprintf("b KernelBase is %#x; b2 KernelBase is %#x", b.KernelBase, b2.KernelBase) 366 } 367 if b.KernelOffset != b2.KernelOffset { 368 s = s + fmt.Sprintf("b KernelOffset is %#x; b2 KernelOffset is %#x", b.KernelOffset, b2.KernelOffset) 369 } 370 return s 371 } 372 373 // String stringifies a LinuxHeader into comma-separated parts 374 func (h *LinuxHeader) String() string { 375 return strings.Join(h.Show(), ",") 376 } 377 378 // InitRAMFS returns a []byte from KernelCode which can be used to save or replace 379 // an existing InitRAMFS. The fun part is that there are no symbols; what we do instead 380 // is find the programs what are RW and look for the cpio magic in them. If we find it, 381 // we see if it can be read as a cpio and, if so, if there is a /dev or /init inside. 382 // We repeat until we succeed or there's nothing left. 383 func (b *BzImage) InitRAMFS() (int, int, error) { 384 f, err := b.ELF() 385 if err != nil { 386 return -1, -1, err 387 } 388 // Find the program header with RWE. 389 var dat []byte 390 var prog *elf.Prog 391 for _, p := range f.Progs { 392 if p.Flags&(elf.PF_X|elf.PF_W|elf.PF_R) == elf.PF_X|elf.PF_W|elf.PF_R { 393 dat, err = ioutil.ReadAll(p.Open()) 394 if err != nil { 395 return -1, -1, err 396 } 397 prog = p 398 break 399 } 400 } 401 if dat == nil { 402 return -1, -1, fmt.Errorf("can't find an RWE prog in kernel") 403 } 404 405 archiver, err := cpio.Format("newc") 406 if err != nil { 407 return -1, -1, fmt.Errorf("format newc not supported: %v", err) 408 } 409 var cur int 410 for cur < len(dat) { 411 x := bytes.Index(dat, []byte("070701")) 412 if x == -1 { 413 return -1, -1, fmt.Errorf("no newc cpio magic found") 414 } 415 if err != nil { 416 return -1, -1, err 417 } 418 cur = x 419 var r = bytes.NewReader(dat[cur:]) 420 rr := archiver.Reader(r) 421 Debug("r.Len is %v", r.Len()) 422 var found bool 423 var size int 424 for { 425 rec, err := rr.ReadRecord() 426 Debug("Check %v", rec) 427 if err == io.EOF { 428 break 429 } 430 if err != nil { 431 Debug("error reading records: %v", err) 432 break 433 } 434 switch rec.Name { 435 case "init", "dev", "bin", "usr": 436 Debug("Found initramfs at %d, %d bytes", cur, len(dat)-r.Len()) 437 found = true 438 } 439 size = int(rec.FilePos) + int(rec.FileSize) 440 } 441 Debug("Size is %d", size) 442 // Add the trailer size. 443 y := x + size 444 if found { 445 // The slice consists of the bytes for cur to the length of initramfs. 446 // We can derive the initramfs length by knowing how much is left of the reader. 447 Debug("Return %d %#x slice %d:%d from %d byte dat", len(dat[x:y]), len(dat[x:y]), cur, y, len(dat)) 448 x += int(prog.Off) 449 y += int(prog.Off) 450 // We need to round y up to the end of the record. We have to do this after we 451 // add the prog.Off value to it. 452 y = (y + 3) &^ 3 453 // and add the size of the trailer record. 454 y += 120 455 y += 4 // and add at least one word of null 456 y = (y + 3) &^ 3 457 Debug("InitRAMFS: return %d, %d", x, y) 458 return x, y, nil 459 } 460 cur += 6 461 } 462 return -1, -1, fmt.Errorf("no cpio found") 463 }