Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fsverity content verification #10007

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
130 changes: 130 additions & 0 deletions pkg/fsverity/fsverity_linux.go
@@ -0,0 +1,130 @@
/*
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"
"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
)

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

integrityDir := filepath.Join(rootPath, "integrity")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can rename this to .fsverity-check by os.MkdirTemp. So, the node admin will know it's tempdir to verify something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thank you for the suggestion.

if err = os.MkdirAll(integrityDir, 0755); err != nil {
return false, err
}

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

digestFile.Close()
defer os.RemoveAll(integrityDir)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this line to L57.


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

return true, nil
}

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

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)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

content.Provider needs to call FS_IOC_MEASURE_VERITY ?

Copy link
Contributor Author

@Jenkins-J Jenkins-J Mar 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fsverity module will automatically verify the integrity of the data on every read operation of the enabled file. If fsverity detects corruption, the read operation will return an error. The case where we would need to call FS_IOC_MEASURE_VERITY manually is if we wanted to detect intentional, malicious corruption of the blob data. In that case, we would measure the data after fsverity is enabled on the blob, safely store the known-good digest, then when the blob data a is read by the provider, call FS_IOC_MEASURE_VERITY again and compare the returned digest to the known-good value.

I'd be happy to implement this if the malicious corruption of blob data is a case containerd should cover.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would be nice to cover malicious corruption.
Can be another PR.

if errno != 0 {
return fmt.Errorf("enable fsverity failed: %w", errno)
}

return nil
}
33 changes: 33 additions & 0 deletions pkg/fsverity/fsverity_other.go
@@ -0,0 +1,33 @@
//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 IsEnabled(path 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")
}