Files
seaweedFS/weed/mount/weedfs_access.go
Chris Lu cca1555cc7 mount: implement create for rsync temp files (#8749)
* mount: implement create for rsync temp files

* mount: move access implementation out of unsupported

* mount: tighten access checks

* mount: log access group lookup failures

* mount: reset dirty pages on truncate

* mount: tighten create and root access handling

* mount: handle existing creates before quota checks

* mount: restrict access fallback when group lookup fails

When lookupSupplementaryGroupIDs returns an error, the previous code
fell through to checking only the "other" permission bits, which could
overgrant access.  Require both group and other permission classes to
satisfy the mask so access is never broader than intended.

* mount: guard against nil entry in Create existing-file path

maybeLoadEntry can return OK with a nil entry or nil Attributes in
edge cases.  Check before dereferencing to prevent a panic.

* mount: reopen existing file on create race without O_EXCL

When createRegularFile returns EEXIST because another process won the
race, and O_EXCL is not set, reload the winner's entry and open it
instead of propagating the error to the caller.

* mount: check parent directory permission in createRegularFile

Verify the caller has write+search (W_OK|X_OK) permission on the
parent directory before creating a file.  This applies to both
Create and Mknod.  Update test fixture mount mode to 0o777 so the
existing tests pass with the new check.

* mount: enforce file permission bits in AcquireHandle

Map the open flags (O_RDONLY/O_WRONLY/O_RDWR) to an access mask and
call hasAccess before handing out a file handle.  This makes
AcquireHandle the single source of truth for mode-based access
control across Open, Create-existing, and Create-new paths.

---------

Co-authored-by: Copilot <copilot@github.com>
2026-03-24 11:43:41 -07:00

100 lines
2.4 KiB
Go

package mount
import (
"os/user"
"strconv"
"syscall"
"github.com/seaweedfs/go-fuse/v2/fuse"
"github.com/seaweedfs/seaweedfs/weed/glog"
)
var lookupSupplementaryGroupIDs = func(callerUid uint32) ([]string, error) {
u, err := user.LookupId(strconv.Itoa(int(callerUid)))
if err != nil {
glog.Warningf("hasAccess: user.LookupId for uid %d failed: %v", callerUid, err)
return nil, err
}
groupIDs, err := u.GroupIds()
if err != nil {
glog.Warningf("hasAccess: u.GroupIds for uid %d failed: %v", callerUid, err)
return nil, err
}
return groupIDs, nil
}
/**
* Check file access permissions
*
* This will be called for the access() system call. If the
* 'default_permissions' mount option is given, this method is not
* called.
*
* This method is not called under Linux kernel versions 2.4.x
*/
func (wfs *WFS) Access(cancel <-chan struct{}, input *fuse.AccessIn) (code fuse.Status) {
_, _, entry, code := wfs.maybeReadEntry(input.NodeId)
if code != fuse.OK {
return code
}
if entry == nil || entry.Attributes == nil {
return fuse.EIO
}
if hasAccess(input.Uid, input.Gid, entry.Attributes.Uid, entry.Attributes.Gid, entry.Attributes.FileMode, input.Mask) {
return fuse.OK
}
return fuse.EACCES
}
func hasAccess(callerUid, callerGid, fileUid, fileGid uint32, perm uint32, mask uint32) bool {
mask &= fuse.R_OK | fuse.W_OK | fuse.X_OK
if mask == 0 {
return true
}
if callerUid == 0 {
return mask&fuse.X_OK == 0 || perm&0o111 != 0
}
if callerUid == fileUid {
return (perm>>6)&mask == mask
}
isMember := callerGid == fileGid
if !isMember {
groupIDs, err := lookupSupplementaryGroupIDs(callerUid)
if err != nil {
// Cannot determine group membership; require both group and
// other permission classes to satisfy the mask so we never
// overgrant when the lookup fails.
groupMatch := ((perm >> 3) & mask) == mask
otherMatch := (perm & mask) == mask
return groupMatch && otherMatch
}
fileGidStr := strconv.Itoa(int(fileGid))
for _, gidStr := range groupIDs {
if gidStr == fileGidStr {
isMember = true
break
}
}
}
if isMember {
return (perm>>3)&mask == mask
}
return (perm & mask) == mask
}
// openFlagsToAccessMask converts open(2) flags to an access permission mask.
func openFlagsToAccessMask(flags uint32) uint32 {
switch flags & uint32(syscall.O_ACCMODE) {
case syscall.O_WRONLY:
return fuse.W_OK
case syscall.O_RDWR:
return fuse.R_OK | fuse.W_OK
default: // O_RDONLY
return fuse.R_OK
}
}