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