Skip to content

Commit

Permalink
Implement fsverity functionality
Browse files Browse the repository at this point in the history
Implement calls to the fsverity kernel module, allowing containerd to
enable fsverity on blob data in the content store. This causes fsverity
to veirfy the integrity of blob data when the blob is read.

Signed-off-by: James Jenkins <James.Jenkins@ibm.com>
  • Loading branch information
Jenkins-J committed Mar 28, 2024
1 parent b0d00f8 commit 36e1ad4
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 0 deletions.
148 changes: 148 additions & 0 deletions pkg/fsverity/fsverity_linux.go
@@ -0,0 +1,148 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package fsverity

import (
"fmt"
"os"
"path/filepath"
"sync"
"syscall"
"unsafe"

"github.com/containerd/containerd/v2/pkg/kernelversion"
"golang.org/x/sys/unix"
)

type fsverityEnableArg struct {
version uint32
hashAlgorithm uint32
blockSize uint32
saltSize uint32
saltPtr uint64
sigSize uint32
reserved1 uint32
sigPtr uint64
reserved2 [11]uint64
}

const (
defaultBlockSize int = 4096
maxDigestSize uint16 = 64
)

var (
once sync.Once
supported bool
supportedErr error
)

func IsSupported(rootPath string) (bool, error) {
once.Do(func() {
minKernelVersion := kernelversion.KernelVersion{Kernel: 5, Major: 4}
s, err := kernelversion.GreaterEqualThan(minKernelVersion)
if err != nil {
supported = s
supportedErr = err
return
}

integrityDir := filepath.Join(rootPath, "integrity")
if err = os.MkdirAll(integrityDir, 0755); err != nil {
supported = false
supportedErr = err
return
}

digestPath := filepath.Join(integrityDir, "supported")
digestFile, err := os.Create(digestPath)
if err != nil {
supported = false
supportedErr = err
return
}

digestFile.Close()
defer os.RemoveAll(integrityDir)

eerr := Enable(digestPath)
if eerr != nil {
supported = false
supportedErr = eerr
return
}

supported = true
supportedErr = nil
})
return supported, supportedErr
}

func IsEnabled(path string) (bool, error) {
f, err := os.Open(path)
if err != nil {
return false, err
}

var attr int

_, _, flagErr := unix.Syscall(syscall.SYS_IOCTL, f.Fd(), uintptr(unix.FS_IOC_GETFLAGS), uintptr(unsafe.Pointer(&attr)))
if flagErr != 0 {
return false, fmt.Errorf("error getting inode flags: %w", flagErr)
}

if attr&unix.FS_VERITY_FL == unix.FS_VERITY_FL {
return true, nil
}

return false, nil
}

func Enable(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}

var args = &fsverityEnableArg{}
args.version = 1
args.hashAlgorithm = 1

// fsverity block size should be the minimum between the page size
// and the file system block size
// If neither value is retrieved successfully, set fsverity block size to the default value
blockSize := unix.Getpagesize()

s := unix.Stat_t{}
serr := unix.Stat(path, &s)
if serr == nil && int(s.Blksize) < blockSize {
blockSize = int(s.Blksize)
}

if blockSize <= 0 {
blockSize = defaultBlockSize
}

args.blockSize = uint32(blockSize)

_, _, errno := unix.Syscall(syscall.SYS_IOCTL, f.Fd(), uintptr(unix.FS_IOC_ENABLE_VERITY), uintptr(unsafe.Pointer(args)))
if errno != 0 {
return fmt.Errorf("enable fsverity failed: %w", errno)
}

return nil
}
29 changes: 29 additions & 0 deletions pkg/fsverity/fsverity_other.go
@@ -0,0 +1,29 @@
//go:build !linux

/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package fsverity

import "fmt"

func IsSupported(rootPath string) (bool, error) {
return false, fmt.Errorf("fsverity is only supported on Linux systems")
}

func Enable(_ string) error {
return fmt.Errorf("fsverity is only supported on Linux systems")
}
15 changes: 15 additions & 0 deletions plugins/content/local/writer.go
Expand Up @@ -27,6 +27,7 @@ import (
"time"

"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/pkg/fsverity"
"github.com/containerd/errdefs"
"github.com/containerd/log"
"github.com/opencontainers/go-digest"
Expand Down Expand Up @@ -137,6 +138,20 @@ func (w *writer) Commit(ctx context.Context, size int64, expected digest.Digest,
return err
}

// Enable content blob integrity verification if supported

var (
integritySupported bool
supportErr error
)
if integritySupported, supportErr = fsverity.IsSupported(w.s.root); integritySupported {
if err := fsverity.Enable(target); err != nil {
log.G(ctx).Warnf("failed to enable integrity of blob %v: %s", target, err.Error())
}
} else {
log.G(ctx).WithError(supportErr).Debug("fsverity integrity verification is not supported")
}

// Ingest has now been made available in the content store, attempt to complete
// setting metadata but errors should only be logged and not returned since
// the content store cannot be cleanly rolled back.
Expand Down

0 comments on commit 36e1ad4

Please sign in to comment.