Merge pull request #3225 from rn/vmlinux

Add experimental support for building uncompressed kernels
This commit is contained in:
Justin Cormack 2018-11-23 11:18:19 +00:00 committed by GitHub
commit 36aa581400
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 134 additions and 34 deletions

View File

@ -1,7 +1,7 @@
FROM rust:1.30.0-stretch FROM rust:1.30.0-stretch
ENV CROSVM_REPO=https://chromium.googlesource.com/chromiumos/platform/crosvm ENV CROSVM_REPO=https://chromium.googlesource.com/chromiumos/platform/crosvm
ENV CROSVM_COMMIT=510c783c847b6d0c18516f31fbe3dbdc782f1252 ENV CROSVM_COMMIT=c527c1a7e8136dae1e8ae728dfd9932bf3967e7e
ENV MINIJAIL_REPO=https://android.googlesource.com/platform/external/minijail ENV MINIJAIL_REPO=https://android.googlesource.com/platform/external/minijail
ENV MINIJAIL_COMMIT=d45fc420bb8fd9d1fc9297174f3c344db8c20bbd ENV MINIJAIL_COMMIT=d45fc420bb8fd9d1fc9297174f3c344db8c20bbd

View File

@ -26,35 +26,29 @@ You may also have to create an empty directory `/var/empty`.
You can build a LinuxKit image suitable for `crosvm` with the You can build a LinuxKit image suitable for `crosvm` with the
`kernel+squashfs` build format. For example, using `minimal.yml` from `kernel+squashfs` build format. For example, using `minimal.yml` from
the `./examples` directory, run: the `./examples` directory, run (but also see the known issues):
```sh ```sh
linuxkit build -format kernel+squashfs minimal.yml linuxkit build -format kernel+squashfs -decompress-kernel minimal.yml
``` ```
The generated kernel file (`minimal-kernel`) needs to be converted as The `-vmlinux` switch is needed since `crosvm` does not grok
`crosvm` does not grok `bzImage`s. You can convert the LinuxKit kernel compressed linux kernel images.
image with
[extract-vmlinux](https://raw.githubusercontent.com/torvalds/linux/master/scripts/extract-vmlinux):
```sh
extract-vmlinux minimal-kernel > minimal-vmlinux
```
Then you can run `crosvm`: Then you can run `crosvm`:
```sh ```sh
./crosvm run --seccomp-policy-dir=./seccomp/x86_64 \ crosvm run --disable-sandbox \
--root ./minimal-squashfs.img \ --root ./minimal-squashfs.img \
--mem 2048 \ --mem 2048 \
--multiprocess \
--socket ./linuxkit-socket \ --socket ./linuxkit-socket \
minimal-vmlinux minimal-kernel
``` ```
## Known issues ## Known issues
- With 4.14.x, a `BUG_ON()` is hit in `drivers/base/driver.c`. 4.9.x - With 4.14.x, a `BUG_ON()` is hit in `drivers/base/driver.c`. 4.9.x
kernels seem to work. kernels seem to work.
- With the latest version, I don't seem to get a interactive console.
- Networking does not yet work, so don't include a `onboot` `dhcpd` service. - Networking does not yet work, so don't include a `onboot` `dhcpd` service.
- `poweroff` from the command line does not work (crosvm does not seem - `poweroff` from the command line does not work (crosvm does not seem
to support ACPI). So to stop a VM you can use the control socket to support ACPI). So to stop a VM you can use the control socket

View File

@ -49,6 +49,7 @@ func build(args []string) {
buildSize := buildCmd.String("size", "1024M", "Size for output image, if supported and fixed size") buildSize := buildCmd.String("size", "1024M", "Size for output image, if supported and fixed size")
buildPull := buildCmd.Bool("pull", false, "Always pull images") buildPull := buildCmd.Bool("pull", false, "Always pull images")
buildDisableTrust := buildCmd.Bool("disable-content-trust", false, "Skip image trust verification specified in trust section of config (default false)") buildDisableTrust := buildCmd.Bool("disable-content-trust", false, "Skip image trust verification specified in trust section of config (default false)")
buildDecompressKernel := buildCmd.Bool("decompress-kernel", false, "Decompress the Linux kernel (default false)")
buildCmd.Var(&buildFormats, "format", "Formats to create [ "+strings.Join(outputTypes, " ")+" ]") buildCmd.Var(&buildFormats, "format", "Formats to create [ "+strings.Join(outputTypes, " ")+" ]")
if err := buildCmd.Parse(args); err != nil { if err := buildCmd.Parse(args); err != nil {
@ -203,7 +204,7 @@ func build(args []string) {
if moby.Streamable(buildFormats[0]) { if moby.Streamable(buildFormats[0]) {
tp = buildFormats[0] tp = buildFormats[0]
} }
err = moby.Build(m, w, *buildPull, tp) err = moby.Build(m, w, *buildPull, tp, *buildDecompressKernel)
if err != nil { if err != nil {
log.Fatalf("%v", err) log.Fatalf("%v", err)
} }

View File

@ -3,6 +3,8 @@ package moby
import ( import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"compress/gzip"
"encoding/binary"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -142,7 +144,7 @@ func outputImage(image *Image, section string, prefix string, m Moby, idMap map[
} }
// Build performs the actual build process // Build performs the actual build process
func Build(m Moby, w io.Writer, pull bool, tp string) error { func Build(m Moby, w io.Writer, pull bool, tp string, decompressKernel bool) error {
if MobyDir == "" { if MobyDir == "" {
MobyDir = defaultMobyConfigDir() MobyDir = defaultMobyConfigDir()
} }
@ -179,7 +181,7 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
if m.Kernel.ref != nil { if m.Kernel.ref != nil {
// get kernel and initrd tarball and ucode cpio archive from container // get kernel and initrd tarball and ucode cpio archive from container
log.Infof("Extract kernel image: %s", m.Kernel.ref) log.Infof("Extract kernel image: %s", m.Kernel.ref)
kf := newKernelFilter(iw, m.Kernel.Cmdline, m.Kernel.Binary, m.Kernel.Tar, m.Kernel.UCode) kf := newKernelFilter(iw, m.Kernel.Cmdline, m.Kernel.Binary, m.Kernel.Tar, m.Kernel.UCode, decompressKernel)
err := ImageTar(m.Kernel.ref, "", kf, enforceContentTrust(m.Kernel.ref.String(), &m.Trust), pull, "") err := ImageTar(m.Kernel.ref, "", kf, enforceContentTrust(m.Kernel.ref.String(), &m.Trust), pull, "")
if err != nil { if err != nil {
return fmt.Errorf("Failed to extract kernel image and tarball: %v", err) return fmt.Errorf("Failed to extract kernel image and tarball: %v", err)
@ -255,19 +257,21 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
// kernelFilter is a tar.Writer that transforms a kernel image into the output we want on underlying tar writer // kernelFilter is a tar.Writer that transforms a kernel image into the output we want on underlying tar writer
type kernelFilter struct { type kernelFilter struct {
tw *tar.Writer tw *tar.Writer
buffer *bytes.Buffer buffer *bytes.Buffer
cmdline string hdr *tar.Header
kernel string cmdline string
tar string kernel string
ucode string tar string
discard bool ucode string
foundKernel bool decompressKernel bool
foundKTar bool discard bool
foundUCode bool foundKernel bool
foundKTar bool
foundUCode bool
} }
func newKernelFilter(tw *tar.Writer, cmdline string, kernel string, tar, ucode *string) *kernelFilter { func newKernelFilter(tw *tar.Writer, cmdline string, kernel string, tar, ucode *string, decompressKernel bool) *kernelFilter {
tarName, kernelName, ucodeName := "kernel.tar", "kernel", "" tarName, kernelName, ucodeName := "kernel.tar", "kernel", ""
if tar != nil { if tar != nil {
tarName = *tar tarName = *tar
@ -281,13 +285,36 @@ func newKernelFilter(tw *tar.Writer, cmdline string, kernel string, tar, ucode *
if ucode != nil { if ucode != nil {
ucodeName = *ucode ucodeName = *ucode
} }
return &kernelFilter{tw: tw, cmdline: cmdline, kernel: kernelName, tar: tarName, ucode: ucodeName} return &kernelFilter{tw: tw, cmdline: cmdline, kernel: kernelName, tar: tarName, ucode: ucodeName, decompressKernel: decompressKernel}
} }
func (k *kernelFilter) finishTar() error { func (k *kernelFilter) finishTar() error {
if k.buffer == nil { if k.buffer == nil {
return nil return nil
} }
if k.hdr != nil {
if k.decompressKernel {
log.Debugf("Decompressing kernel")
b, err := decompressKernel(k.buffer)
if err != nil {
return err
}
k.buffer = b
k.hdr.Size = int64(k.buffer.Len())
}
if err := k.tw.WriteHeader(k.hdr); err != nil {
return err
}
if _, err := k.tw.Write(k.buffer.Bytes()); err != nil {
return err
}
k.hdr = nil
k.buffer = nil
return nil
}
tr := tar.NewReader(k.buffer) tr := tar.NewReader(k.buffer)
err := tarAppend(k.tw, tr) err := tarAppend(k.tw, tr)
k.buffer = nil k.buffer = nil
@ -362,15 +389,14 @@ func (k *kernelFilter) WriteHeader(hdr *tar.Header) error {
if err != nil { if err != nil {
return err return err
} }
whdr = &tar.Header{ // Stash the kernel header and prime the buffer for the kernel
k.hdr = &tar.Header{
Name: "boot/kernel", Name: "boot/kernel",
Mode: hdr.Mode, Mode: hdr.Mode,
Size: hdr.Size, Size: hdr.Size,
Format: tar.FormatPAX, Format: tar.FormatPAX,
} }
if err := tw.WriteHeader(whdr); err != nil { k.buffer = new(bytes.Buffer)
return err
}
case k.tar: case k.tar:
k.foundKTar = true k.foundKTar = true
k.discard = false k.discard = false
@ -427,6 +453,85 @@ func tarAppend(iw *tar.Writer, tr *tar.Reader) error {
return nil return nil
} }
// Attempt to decompress a Linux kernel image
// The kernel image can be a plain gzip'ed image (e.g., the LinuxKit arm64 kernel) or a bzImage (x86)
// or not compressed at all (e.g., s390x). This function tries to detect the image type and decompress
// the kernel. If no supported compressed kernel is found it returns an error.
// For bzImages it performs some sanity checks on the header and currently only supports gzip'ed bzImages.
func decompressKernel(src *bytes.Buffer) (*bytes.Buffer, error) {
const gzipMagic = "\037\213"
s := src.Bytes()
if bytes.HasPrefix(s, []byte(gzipMagic)) {
log.Debugf("Found gzip signature at offset: 0")
return gunzip(src)
}
// Check if it is a bzImage
// See: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/x86/boot.txt
const bzMagicIdx = 0x1fe
const bzMagic = uint16(0xaa55)
const bzHeaderIdx = 0x202
const bzHeader = "HdrS"
const bzMinLen = 0x250 // Minimum length for the required 2.0.8+ header
if len(s) > bzMinLen &&
binary.LittleEndian.Uint16(s[bzMagicIdx:bzMagicIdx+2]) == bzMagic &&
bytes.HasPrefix(s[bzHeaderIdx:], []byte(bzHeader)) {
log.Debugf("Found bzImage Magic and Header")
const versionIdx = 0x206
const setupSectorsIdx = 0x1f1
const sectorSize = 512
const payloadIdx = 0x248
const payloadLengthIdx = 0x24c
// Check that the version is 2.08+
versionMajor := int(s[versionIdx])
versionMinor := int(s[versionIdx+1])
if versionMajor < 2 && versionMinor < 8 {
return nil, fmt.Errorf("Unsupported bzImage version: %d.%d", versionMajor, versionMinor)
}
setupSectors := uint32(s[setupSectorsIdx])
payloadOff := binary.LittleEndian.Uint32(s[payloadIdx : payloadIdx+4])
payloadLen := binary.LittleEndian.Uint32(s[payloadLengthIdx : payloadLengthIdx+4])
payloadOff += (setupSectors + 1) * sectorSize
log.Debugf("bzImage: Payload at Offset: %d Length: %d", payloadOff, payloadLen)
if len(s) < int(payloadOff+payloadLen) {
return nil, fmt.Errorf("Compressed bzImage payload exceeds size of image")
}
if bytes.HasPrefix(s[payloadOff:], []byte(gzipMagic)) {
log.Debugf("bzImage: gzip signature at offset: %d", payloadOff)
return gunzip(bytes.NewBuffer(s[payloadOff : payloadOff+payloadLen]))
}
// TODO(rn): Add more supported formats
return nil, fmt.Errorf("Unsupported bzImage payload format at offset %d", payloadOff)
}
return nil, fmt.Errorf("No compressed kernel or no supported format found")
}
func gunzip(src *bytes.Buffer) (*bytes.Buffer, error) {
dst := new(bytes.Buffer)
zr, err := gzip.NewReader(src)
if err != nil {
return nil, err
}
n, err := io.Copy(dst, zr)
if err != nil {
return nil, err
}
log.Debugf("gunzip'ed %d bytes", n)
return dst, nil
}
// this allows inserting metadata into a file in the image // this allows inserting metadata into a file in the image
func metadata(m Moby, md string) ([]byte, error) { func metadata(m Moby, md string) ([]byte, error) {
// Make sure the Image strings are update to date with the refs // Make sure the Image strings are update to date with the refs

View File

@ -62,7 +62,7 @@ func ensureLinuxkitImage(name string) error {
return err return err
} }
defer os.Remove(tf.Name()) defer os.Remove(tf.Name())
Build(m, tf, false, "") Build(m, tf, false, "", false)
if err := tf.Close(); err != nil { if err := tf.Close(); err != nil {
return err return err
} }