Merge pull request #11352 from ChengyuZhu6/fsverity

erofs-snapshotter: add fsverity support
This commit is contained in:
Akihiro Suda
2025-02-25 03:08:40 +00:00
committed by GitHub
4 changed files with 158 additions and 7 deletions

View File

@@ -82,6 +82,13 @@ The following configuration can be used in your containerd `config.toml`. Don't
forget to restart containerd after changing the configuration.
```
[plugins."io.containerd.snapshotter.v1.erofs"]
# Enable fsverity support for EROFS layers, default is false
enable_fsverity = true
# Optional: Additional mount options for overlayfs
ovl_mount_options = []
[plugins."io.containerd.service.v1.diff-service"]
default = ["erofs","walking"]
```
@@ -135,6 +142,13 @@ In other words, the EROFS differ can only be used with the EROFS snapshotter;
otherwise, it will skip to the next differ. The EROFS snapshotter can work
with or without the EROFS differ.
## Data Integrity with fsverity
The EROFS snapshotter supports fsverity for data integrity verification of EROFS layers.
When enabled via `enable_fsverity = true`, the snapshotter will:
- Enable fsverity on EROFS layers during commit
- Verify fsverity status before mounting layers
- Skip fsverity if the filesystem or kernel does not support it
## TODO

View File

@@ -28,6 +28,7 @@ import (
"github.com/containerd/containerd/v2/core/mount"
"github.com/containerd/containerd/v2/core/snapshots"
"github.com/containerd/containerd/v2/core/snapshots/storage"
"github.com/containerd/containerd/v2/internal/fsverity"
"github.com/containerd/containerd/v2/plugins/snapshots/erofs/erofsutils"
"github.com/containerd/continuity/fs"
"github.com/containerd/log"
@@ -39,6 +40,8 @@ import (
type SnapshotterConfig struct {
// ovlOptions are the base options added to the overlayfs mount (defaults to [""])
ovlOptions []string
// enableFsverity enables fsverity for EROFS layers
enableFsverity bool
}
// Opt is an option to configure the erofs snapshotter
@@ -51,6 +54,13 @@ func WithOvlOptions(options []string) Opt {
}
}
// WithFsverity enables fsverity for EROFS layers
func WithFsverity() Opt {
return func(config *SnapshotterConfig) {
config.enableFsverity = true
}
}
type MetaStore interface {
TransactionContext(ctx context.Context, writable bool) (context.Context, storage.Transactor, error)
WithTransaction(ctx context.Context, writable bool, fn storage.TransactionCallback) error
@@ -58,9 +68,10 @@ type MetaStore interface {
}
type snapshotter struct {
root string
ms *storage.MetaStore
ovlOptions []string
root string
ms *storage.MetaStore
ovlOptions []string
enableFsverity bool
}
// check if EROFS kernel filesystem is registered or not
@@ -108,6 +119,17 @@ func NewSnapshotter(root string, opts ...Opt) (snapshots.Snapshotter, error) {
return nil, fmt.Errorf("EROFS unsupported, please `modprobe erofs`: %w", plugin.ErrSkipPlugin)
}
// Check fsverity support if enabled
if config.enableFsverity {
supported, err := fsverity.IsSupported(root)
if err != nil {
return nil, fmt.Errorf("failed to check fsverity support on %q: %w", root, err)
}
if !supported {
return nil, fmt.Errorf("fsverity is not supported on the filesystem of %q", root)
}
}
ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db"))
if err != nil {
return nil, err
@@ -118,9 +140,10 @@ func NewSnapshotter(root string, opts ...Opt) (snapshots.Snapshotter, error) {
}
return &snapshotter{
root: root,
ms: ms,
ovlOptions: config.ovlOptions,
root: root,
ms: ms,
ovlOptions: config.ovlOptions,
enableFsverity: config.enableFsverity,
}, nil
}
@@ -182,12 +205,16 @@ func (s *snapshotter) mounts(snap storage.Snapshot, info snapshots.Info) ([]moun
var options []string
if len(snap.ParentIDs) == 0 {
// If the EROFS layer blob is valid, only snapshots.KindView is allowed.
m, _, err := s.lowerPath(snap.ID)
if err == nil {
if snap.Kind != snapshots.KindView {
return nil, fmt.Errorf("only works for snapshots.KindView on a committed snapshot: %w", err)
}
if s.enableFsverity {
if err := s.verifyFsverity(m.Source); err != nil {
return nil, err
}
}
// We have to force a loop device here since mount[] is static.
m.Options = append(m.Options, "loop")
return []mount.Mount{m}, nil
@@ -369,6 +396,14 @@ func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snap
}
}
}
// Enable fsverity on the EROFS layer if configured
if s.enableFsverity {
if err := fsverity.Enable(layerBlob); err != nil {
return fmt.Errorf("failed to enable fsverity: %w", err)
}
}
return nil
})
@@ -536,3 +571,18 @@ func (s *snapshotter) Usage(ctx context.Context, key string) (_ snapshots.Usage,
}
return usage, nil
}
// Add a method to verify fsverity
func (s *snapshotter) verifyFsverity(path string) error {
if !s.enableFsverity {
return nil
}
enabled, err := fsverity.IsEnabled(path)
if err != nil {
return fmt.Errorf("failed to check fsverity status: %w", err)
}
if !enabled {
return fmt.Errorf("fsverity is not enabled on %s", path)
}
return nil
}

View File

@@ -18,11 +18,16 @@ package erofs
import (
"context"
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/containerd/containerd/v2/core/mount"
"github.com/containerd/containerd/v2/core/snapshots"
"github.com/containerd/containerd/v2/core/snapshots/storage"
"github.com/containerd/containerd/v2/core/snapshots/testsuite"
"github.com/containerd/containerd/v2/internal/fsverity"
"github.com/containerd/containerd/v2/pkg/testutil"
)
@@ -51,3 +56,78 @@ func TestErofs(t *testing.T) {
testutil.RequiresRoot(t)
testsuite.SnapshotterSuite(t, "erofs", newSnapshotter(t))
}
func TestErofsFsverity(t *testing.T) {
testutil.RequiresRoot(t)
ctx := context.Background()
root := t.TempDir()
// Skip if fsverity is not supported
supported, err := fsverity.IsSupported(root)
if !supported || err != nil {
t.Skip("fsverity not supported, skipping test")
}
// Create snapshotter with fsverity enabled
s, err := NewSnapshotter(root, WithFsverity())
if err != nil {
t.Fatal(err)
}
defer s.Close()
// Create a test snapshot
key := "test-snapshot"
mounts, err := s.Prepare(ctx, key, "")
if err != nil {
t.Fatal(err)
}
target := filepath.Join(root, key)
if err := os.MkdirAll(target, 0755); err != nil {
t.Fatal(err)
}
if err := mount.All(mounts, target); err != nil {
t.Fatal(err)
}
defer testutil.Unmount(t, target)
// Write test data
if err := os.WriteFile(filepath.Join(target, "foo"), []byte("test data"), 0777); err != nil {
t.Fatal(err)
}
// Commit the snapshot
commitKey := "test-commit"
if err := s.Commit(ctx, commitKey, key); err != nil {
t.Fatal(err)
}
snap := s.(*snapshotter)
// Get the internal ID from the snapshotter
var id string
if err := snap.ms.WithTransaction(ctx, false, func(ctx context.Context) error {
id, _, _, err = storage.GetInfo(ctx, commitKey)
return err
}); err != nil {
t.Fatal(err)
}
// Verify fsverity is enabled on the EROFS layer
layerPath := snap.layerBlobPath(id)
enabled, err := fsverity.IsEnabled(layerPath)
if err != nil {
t.Fatalf("Failed to check fsverity status: %v", err)
}
if !enabled {
t.Fatal("Expected fsverity to be enabled on committed layer")
}
// Try to modify the layer file directly (should fail)
if err := os.WriteFile(layerPath, []byte("tampered data"), 0666); err == nil {
t.Fatal("Expected direct write to fsverity-enabled layer to fail")
}
}

View File

@@ -33,6 +33,9 @@ type Config struct {
// MountOptions are options used for the EROFS overlayfs mount
OvlOptions []string `toml:"ovl_mount_options"`
// EnableFsverity enables fsverity for EROFS layers
EnableFsverity bool `toml:"enable_fsverity"`
}
func init() {
@@ -58,6 +61,10 @@ func init() {
opts = append(opts, erofs.WithOvlOptions(config.OvlOptions))
}
if config.EnableFsverity {
opts = append(opts, erofs.WithFsverity())
}
ic.Meta.Exports[plugins.SnapshotterRootDir] = root
return erofs.NewSnapshotter(root, opts...)
},