Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
29 changes: 15 additions & 14 deletions atomfs/molecule.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,31 @@ 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
}

if err := os.MkdirAll(target, 0755); err != nil {
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")
Expand Down
9 changes: 5 additions & 4 deletions atomfs/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 5 additions & 4 deletions cmd/internal_go.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
101 changes: 92 additions & 9 deletions squashfs/verity.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,65 @@
package squashfs

// #cgo pkg-config: libcryptsetup libsquashfs1 --static
// #cgo pkg-config: libcryptsetup libsquashfs1 devmapper --static
// #include <libcryptsetup.h>
// #include <stdlib.h>
// #include <errno.h>
// #include <libdevmapper.h>
/*
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 (
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
27 changes: 27 additions & 0 deletions test/atomfs.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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 <<EOF
test:
from:
type: oci
url: $CENTOS_OCI
run: |
touch /hello
EOF
stacker build --layer-type=squashfs

manifest=$(cat oci/index.json | jq -r .manifests[0].digest | cut -f2 -d:)
first_layer_hash=$(cat oci/blobs/sha256/$manifest | jq -r .layers[0].digest | cut -f2 -d:)
devname="$first_layer_hash-verity"

# make an evil device and fake it as an existing verity device
dd if=/dev/random of=mydev bs=50K count=1
root_hash=$(veritysetup format mydev mydev.hash | grep "Root hash:" | awk '{print $NF}')
echo "root hash $root_hash"
veritysetup open mydev "$devname" mydev.hash "$root_hash"

mkdir mountpoint
bad_stacker internal-go atomfs mount test-squashfs mountpoint | grep "invalid root hash"
veritysetup close "$devname"
}