mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-21 14:46:58 +00:00
mount: skip redundant permission checks under default_permissions (#10089)
With default_permissions (the mount default) the kernel enforces unix permission bits from the getattr/lookup attributes before it ever calls Open, Create, or Mknod. The mount was re-checking permissions in AcquireHandle and createRegularFile anyway, which duplicated the kernel's work and kept the supplementary-group lookup on the per-file hot path. Gate only the mode-bit access check on default_permissions being off, so a non-root copy does no permission work on open/create. createRegularFile still loads the parent to validate it exists, since the create RPC skips the filer-side parent check. With default_permissions off the mount remains the sole enforcer, so the full check still runs.
This commit is contained in:
@@ -359,6 +359,7 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
|
||||
Cipher: cipher,
|
||||
UidGidMapper: uidGidMapper,
|
||||
IncludeSystemEntries: *option.includeSystemEntries,
|
||||
DefaultPermissions: *option.defaultPermissions,
|
||||
DisableXAttr: *option.disableXAttr,
|
||||
IsMacOs: runtime.GOOS == "darwin",
|
||||
MetadataFlushSeconds: *option.metadataFlushSeconds,
|
||||
|
||||
@@ -71,6 +71,13 @@ type Option struct {
|
||||
UidGidMapper *meta_cache.UidGidMapper
|
||||
IncludeSystemEntries bool
|
||||
|
||||
// DefaultPermissions mirrors the FUSE default_permissions mount option.
|
||||
// When set, the kernel enforces unix permission bits from the getattr/
|
||||
// lookup attributes before it ever calls Open/Create/Mknod, so the mount
|
||||
// skips its own redundant permission checks (and the group lookups behind
|
||||
// them) on those hot paths.
|
||||
DefaultPermissions bool
|
||||
|
||||
// Periodic metadata flush interval in seconds (0 to disable)
|
||||
// This protects chunks from being purged by volume.fsck for long-running writes
|
||||
MetadataFlushSeconds int
|
||||
|
||||
@@ -320,7 +320,10 @@ func (wfs *WFS) createRegularFile(dirFullPath util.FullPath, name string, mode u
|
||||
return 0, nil, fuse.Status(syscall.ENOSPC)
|
||||
}
|
||||
|
||||
// Verify write+search permission on the parent directory.
|
||||
// Load the parent directory to validate it exists (the create RPC sets
|
||||
// SkipCheckParentDirectory, so this is the only parent check). With
|
||||
// default_permissions the kernel already verified write+search on it before
|
||||
// Create/Mknod, so skip only the mode-bit check and its group lookup.
|
||||
parentEntry, parentStatus := wfs.maybeLoadEntry(dirFullPath)
|
||||
if parentStatus != fuse.OK {
|
||||
return 0, nil, parentStatus
|
||||
@@ -328,14 +331,16 @@ func (wfs *WFS) createRegularFile(dirFullPath util.FullPath, name string, mode u
|
||||
if parentEntry == nil || parentEntry.Attributes == nil {
|
||||
return 0, nil, fuse.EIO
|
||||
}
|
||||
// Map parent dir uid/gid from filer-space to local-space so the
|
||||
// permission check compares like with like (caller uid/gid are local).
|
||||
parentUid, parentGid := parentEntry.Attributes.Uid, parentEntry.Attributes.Gid
|
||||
if wfs.option.UidGidMapper != nil {
|
||||
parentUid, parentGid = wfs.option.UidGidMapper.FilerToLocal(parentUid, parentGid)
|
||||
}
|
||||
if !hasAccess(uid, gid, parentUid, parentGid, parentEntry.Attributes.FileMode, fuse.W_OK|fuse.X_OK) {
|
||||
return 0, nil, fuse.Status(syscall.EACCES)
|
||||
if !wfs.option.DefaultPermissions {
|
||||
// Map parent dir uid/gid from filer-space to local-space so the
|
||||
// permission check compares like with like (caller uid/gid are local).
|
||||
parentUid, parentGid := parentEntry.Attributes.Uid, parentEntry.Attributes.Gid
|
||||
if wfs.option.UidGidMapper != nil {
|
||||
parentUid, parentGid = wfs.option.UidGidMapper.FilerToLocal(parentUid, parentGid)
|
||||
}
|
||||
if !hasAccess(uid, gid, parentUid, parentGid, parentEntry.Attributes.FileMode, fuse.W_OK|fuse.X_OK) {
|
||||
return 0, nil, fuse.Status(syscall.EACCES)
|
||||
}
|
||||
}
|
||||
|
||||
entryFullPath := dirFullPath.Child(name)
|
||||
|
||||
@@ -487,3 +487,69 @@ func TestCreateExistingFileIgnoresQuotaPreflight(t *testing.T) {
|
||||
t.Fatalf("Create status = %v, want EEXIST", status)
|
||||
}
|
||||
}
|
||||
|
||||
// With default_permissions the kernel enforces unix bits before it calls
|
||||
// Open, so AcquireHandle must skip its own check (and the group lookup behind
|
||||
// it). Without it, AcquireHandle stays the enforcer.
|
||||
func TestAcquireHandleHonorsDefaultPermissions(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
defaultPermissions bool
|
||||
want fuse.Status
|
||||
}{
|
||||
{"kernel enforces", true, fuse.OK},
|
||||
{"mount enforces", false, fuse.EACCES},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
wfs, _ := newCreateTestWFS(t)
|
||||
wfs.option.DefaultPermissions = tc.defaultPermissions
|
||||
|
||||
oldLookup := lookupSupplementaryGroupIDs
|
||||
lookupSupplementaryGroupIDs = func(uint32) ([]string, error) { return nil, nil }
|
||||
clearSupplementaryGroupCache()
|
||||
t.Cleanup(func() {
|
||||
lookupSupplementaryGroupIDs = oldLookup
|
||||
clearSupplementaryGroupCache()
|
||||
})
|
||||
|
||||
entry := &filer_pb.Entry{
|
||||
Name: "secret.txt",
|
||||
Attributes: &filer_pb.FuseAttributes{
|
||||
FileMode: 0o600, // owner-only: an "other" uid has no read
|
||||
Inode: 202,
|
||||
Crtime: 1,
|
||||
Mtime: 1,
|
||||
Uid: 123,
|
||||
Gid: 456,
|
||||
},
|
||||
}
|
||||
if err := wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry("/", entry)); err != nil {
|
||||
t.Fatalf("InsertEntry: %v", err)
|
||||
}
|
||||
inode := wfs.inodeToPath.Lookup(util.FullPath("/secret.txt"), entry.Attributes.Crtime, false, false, entry.Attributes.Inode, true)
|
||||
|
||||
fh, status := wfs.AcquireHandle(inode, syscall.O_RDONLY, 999, 999)
|
||||
if status != tc.want {
|
||||
t.Fatalf("AcquireHandle status = %v, want %v", status, tc.want)
|
||||
}
|
||||
if status == fuse.OK {
|
||||
if fh == nil {
|
||||
t.Fatal("AcquireHandle returned nil handle on OK")
|
||||
}
|
||||
wfs.ReleaseHandle(fh.fh)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// default_permissions skips only the mode-bit check, not parent-existence
|
||||
// validation: the create RPC sets SkipCheckParentDirectory, so the mount's
|
||||
// own parent lookup is the only thing guarding against an orphaned entry.
|
||||
func TestCreateRegularFileValidatesParentUnderDefaultPermissions(t *testing.T) {
|
||||
wfs, _ := newCreateTestWFS(t)
|
||||
wfs.option.DefaultPermissions = true
|
||||
|
||||
if _, _, code := wfs.createRegularFile(util.FullPath("/ghost"), "f.txt", 0o644, 99, 100, 0, false, false); code == fuse.OK {
|
||||
t.Fatal("createRegularFile returned OK for a missing parent directory")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,10 @@ func (wfs *WFS) AcquireHandle(inode uint64, flags, uid, gid uint32) (fileHandle
|
||||
if wormEnforced, _ := wfs.wormEnforcedForEntry(path, entry); wormEnforced && flags&fuse.O_ANYWRITE != 0 {
|
||||
return nil, fuse.EPERM
|
||||
}
|
||||
// Check unix permission bits for the requested access mode.
|
||||
if entry != nil && entry.Attributes != nil {
|
||||
// Check unix permission bits for the requested access mode. With
|
||||
// default_permissions the kernel already enforced them before this
|
||||
// open, so the check (and its supplementary-group lookup) is redundant.
|
||||
if !wfs.option.DefaultPermissions && entry != nil && entry.Attributes != nil {
|
||||
fileUid, fileGid := entry.Attributes.Uid, entry.Attributes.Gid
|
||||
if wfs.option.UidGidMapper != nil {
|
||||
fileUid, fileGid = wfs.option.UidGidMapper.FilerToLocal(fileUid, fileGid)
|
||||
|
||||
Reference in New Issue
Block a user