diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index db8528269..8470f7720 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,7 @@ jobs: GO111MODULE=off go get github.com/opencontainers/umoci/cmd/umoci sudo cp ~/go/bin/umoci /usr/bin curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin - sudo apt-get install -yy autoconf automake make autogen autoconf libtool binutils git squashfs-tools libcryptsetup-dev + sudo apt-get install -yy autoconf automake make autogen autoconf libtool binutils git squashfs-tools libcryptsetup-dev libdevmapper-dev cryptsetup-bin (cd /tmp && git clone https://github.com/AgentD/squashfs-tools-ng && cd squashfs-tools-ng && ./autogen.sh && ./configure --prefix=/usr && make -j2 && sudo make -j2 install && sudo ldconfig -v) (cd /tmp && git clone https://github.com/anuvu/squashfs && cd squashfs && make && sudo cp squashtool/squashtool /usr/bin) - run: | diff --git a/atomfs/molecule.go b/atomfs/molecule.go index 7cee45ab4..8a3382390 100644 --- a/atomfs/molecule.go +++ b/atomfs/molecule.go @@ -27,21 +27,24 @@ func (m Molecule) mountUnderlyingAtoms() error { for _, a := range m.Atoms { target := m.config.MountedAtomsPath(a.Digest.Encoded()) - mountpoint, err := mount.IsMountpoint(target) + rootHash := a.Annotations[squashfs.VerityRootHashAnnotation] + + if !m.config.AllowMissingVerityData && rootHash == "" { + return errors.Errorf("%v is missing verity data", a.Digest) + } + + mounts, err := mount.ParseMounts("/proc/self/mountinfo") if err != nil { return err } - if mountpoint { - // FIXME: this is unsafe. if someone mounted a - // filesystem here, we just use it, without ensuring - // the contents match the dm-verity stuff. in the - // future, we should: - // - // 1. go backwards from the mountpoint to the block dev - // 2. ask the kernel what its root hash of the block - // dev is, and compare that to the annotation we have - // - // but for now let's just re-use to get off the ground. + + mountpoint, mounted := mounts.FindMount(target) + + if mounted { + err = squashfs.ConfirmExistingVerityDeviceHash(mountpoint.Source, rootHash) + if err != nil { + return err + } continue } @@ -49,8 +52,6 @@ func (m Molecule) mountUnderlyingAtoms() error { return err } - rootHash := a.Annotations[squashfs.VerityRootHashAnnotation] - err = squashfs.Mount(m.config.AtomsPath(a.Digest.Encoded()), target, rootHash) if err != nil { return errors.Wrapf(err, "couldn't mount") diff --git a/atomfs/oci.go b/atomfs/oci.go index b7aa9dd04..19d25bdc2 100644 --- a/atomfs/oci.go +++ b/atomfs/oci.go @@ -9,10 +9,11 @@ import ( ) type MountOCIOpts struct { - OCIDir string - MetadataPath string - Tag string - Target string + OCIDir string + MetadataPath string + Tag string + Target string + AllowMissingVerityData bool } func (c MountOCIOpts) AtomsPath(parts ...string) string { diff --git a/cmd/internal_go.go b/cmd/internal_go.go index 44f0c649f..27b120978 100644 --- a/cmd/internal_go.go +++ b/cmd/internal_go.go @@ -154,10 +154,11 @@ func doAtomfsMount(ctx *cli.Context) error { } opts := atomfs.MountOCIOpts{ - OCIDir: config.OCIDir, - MetadataPath: path.Join(wd, "atomfs-metadata"), - Tag: tag, - Target: mountpoint, + OCIDir: config.OCIDir, + MetadataPath: path.Join(wd, "atomfs-metadata"), + Tag: tag, + Target: mountpoint, + AllowMissingVerityData: true, } mol, err := atomfs.BuildMoleculeFromOCI(opts) diff --git a/squashfs/verity.go b/squashfs/verity.go index c0a962e24..484857a45 100644 --- a/squashfs/verity.go +++ b/squashfs/verity.go @@ -1,8 +1,65 @@ package squashfs -// #cgo pkg-config: libcryptsetup libsquashfs1 --static +// #cgo pkg-config: libcryptsetup libsquashfs1 devmapper --static // #include // #include +// #include +// #include +/* +int get_verity_params(char *device, char **params) +{ + struct dm_task *dmt; + struct dm_info dmi; + int r; + uint64_t start, length; + char *type, *tmpParams; + + dmt = dm_task_create(DM_DEVICE_TABLE); + if (!dmt) + return 1; + + r = 2; + if (!dm_task_secure_data(dmt)) + goto out; + + r = 3; + if (!dm_task_set_name(dmt, device)) + goto out; + + r = 4; + if (!dm_task_run(dmt)) + goto out; + + r = 5; + if (!dm_task_get_info(dmt, &dmi)) + goto out; + + r = 6; + if (!dmi.exists) + goto out; + + r = 7; + if (dmi.target_count <= 0) + goto out; + + r = 8; + dm_get_next_target(dmt, NULL, &start, &length, &type, &tmpParams); + if (!type) + goto out; + + r = 9; + if (strcasecmp(type, CRYPT_VERITY)) { + fprintf(stderr, "type: %s (%s) %d\n", type, CRYPT_VERITY, strcmp(type, CRYPT_VERITY)); + goto out; + } + *params = strdup(tmpParams); + + r = 0; +out: + dm_task_destroy(dmt); + return r; +} +*/ import "C" import ( @@ -216,19 +273,17 @@ func Mount(squashfs string, mountpoint string, rootHash string) error { return errors.Errorf("unexpected key size for %s", rootHash) } - err = verityDevice.ActivateByVolumeKey(name, string(rootHashBytes), volumeKeySizeInBytes, C.CRYPT_ACTIVATE_READONLY) + err = verityDevice.ActivateByVolumeKey(name, string(rootHashBytes), volumeKeySizeInBytes, cryptsetup.CRYPT_ACTIVATE_READONLY) if err != nil { loopDev.Detach() return errors.WithStack(err) } + } else { + err = ConfirmExistingVerityDeviceHash(verityDevPath, rootHash) + if err != nil { + return err + } } - // else { - // FIXME: verity device already exists, let's check to make - // sure it is reasonable (i.e. the root hash matches the one - // the user asked us about). without this things are unsafe, as - // someone could create this verity device with the right name - // and trick our atomfs implementation into using it. - // } } else { loopDev, err = losetup.Attach(squashfs, 0, true) if err != nil { @@ -340,3 +395,31 @@ func Umount(mountpoint string) error { return nil } + +func ConfirmExistingVerityDeviceHash(devicePath string, rootHash string) error { + device := filepath.Base(devicePath) + cDevice := C.CString(device) + defer C.free(unsafe.Pointer(cDevice)) + + var cParams *C.char + + rc := C.get_verity_params(cDevice, &cParams) + if rc != 0 { + return errors.Errorf("problem getting hash from %v: %v", device, rc) + } + defer C.free(unsafe.Pointer(cParams)) + + params := C.GoString(cParams) + + // https://gitlab.com/cryptsetup/cryptsetup/-/wikis/DMVerity + fields := strings.Fields(params) + if len(fields) < 10 { + return errors.Errorf("invalid dm params for %v: %v", device, params) + } + + if rootHash != fields[8] { + return errors.Errorf("invalid root hash for %v: %v (expected: %v)", device, fields[7], rootHash) + } + + return nil +} diff --git a/test/atomfs.bats b/test/atomfs.bats index 3374e2603..2c05a5023 100644 --- a/test/atomfs.bats +++ b/test/atomfs.bats @@ -114,3 +114,30 @@ EOF last_layer_hash=$(cat oci/blobs/sha256/$manifest | jq -r .layers[$last_layer].digest | cut -f2 -d:) [ ! -b "/dev/mapper/$last_layer_hash-verity" ] } + +@test "bad existing verity device is rejected" { + require_privilege priv + cat > stacker.yaml <