enforce permissions on all paths, use system user/group

- fileOwnerGroup: session internals owned by agent/agent,
  everything else by current system user and primary group
- checkPerm: always enforce, remove no-enforcement bypass
- Directory modes: /s/{sid}=755, /s/{sid}/t=500, /s=755,
  /u,/x=555, /p=664, /a=744, /sk=754, /tmp=777, /tr=766
- File modes: /p/=664, /sk/=664, /s/{sid}/t/*=500
- Session store: sh=550, b/bfg/bbg=555
This commit is contained in:
Levi Neely 2026-05-19 16:16:36 +02:00
parent 629a841cf5
commit 000ad64673
2 changed files with 31 additions and 18 deletions

View File

@ -9,6 +9,7 @@ import (
"io"
"net"
"os"
"os/user"
"path/filepath"
"strings"
"sync"
@ -331,32 +332,32 @@ func (s *Server) InGroup(group, user string) bool {
}
// fileOwnerGroup returns the uid and gid for a given path.
// Session files are owned by the session's principal with group "agent".
// Prompt library (/p/) is owned by "ollie" with group "user".
// Everything else is owned by "ollie" with group "ollie".
// Session namespace (/s/{sid}/**) is owned by the agent principal with group "agent".
// Everything else is owned by the current system user and their primary group.
func (s *Server) fileOwnerGroup(path string) (uid, gid string) {
if strings.HasPrefix(path, "/s/") {
parts := strings.SplitN(strings.TrimPrefix(path, "/s/"), "/", 2)
if len(parts) >= 1 && parts[0] != "new" {
if len(parts) >= 1 && parts[0] != "new" && !isSessionStoreFile(path) {
if sess := s.sessionStore.Session(parts[0]); sess != nil {
return sess.Uname(), "agent"
}
}
}
if path == "/p" || strings.HasPrefix(path, "/p/") {
return "ollie", "user"
if u, err := user.Current(); err == nil {
gid := u.Gid
if g, err := user.LookupGroupId(u.Gid); err == nil {
gid = g.Name
}
return u.Username, gid
}
return "ollie", "ollie"
}
// checkPerm verifies that uname has the requested access (mode) to path.
// mode is the 9P open mode: OREAD=0, OWRITE=1, ORDWR=2, OEXEC=3.
// Enforced on session files and group-restricted paths (e.g. /p/).
// Enforced on all paths.
func (s *Server) checkPerm(uname, path string, mode uint8) error {
uid, gid := s.fileOwnerGroup(path)
if uid == "ollie" && gid == "ollie" {
return nil // no enforcement
}
dir := s.makeStat(path)
perm := uint32(dir.Mode) & 0777
@ -1641,14 +1642,26 @@ func (s *Server) makeStat(path string) plan9.Dir {
if strings.HasPrefix(path, "/s/") {
parts := strings.SplitN(strings.TrimPrefix(path, "/s/"), "/", 3)
if (len(parts) == 2 && parts[1] == "t") || (len(parts) == 3 && parts[1] == "t") {
mode = plan9.DMDIR | 0700
mode = plan9.DMDIR | 0500 // rx owner only
} else {
mode = plan9.DMDIR | 0555
mode = plan9.DMDIR | 0755
}
} else if path == "/a" || path == "/m" || path == "/tmp" || path == "/u" {
} else if path == "/s" {
mode = plan9.DMDIR | 0755
} else {
} else if path == "/u" || path == "/x" {
mode = plan9.DMDIR | 0555
} else if path == "/p" {
mode = plan9.DMDIR | 0664
} else if path == "/a" {
mode = plan9.DMDIR | 0744
} else if path == "/sk" {
mode = plan9.DMDIR | 0754
} else if path == "/tmp" {
mode = plan9.DMDIR | 0777
} else if path == "/tr" {
mode = plan9.DMDIR | 0766
} else {
mode = plan9.DMDIR | 0755
}
} else {
switch base {
@ -1671,19 +1684,19 @@ func (s *Server) makeStat(path string) plan9.Dir {
} else if strings.HasPrefix(path, "/a/") {
mode = 0666
} else if strings.HasPrefix(path, "/p/") {
mode = 0640
mode = 0664
} else if strings.HasPrefix(path, "/m/") {
mode = 0666
} else if path == "/sk/idx" {
mode = 0444
} else if strings.HasPrefix(path, "/sk/") {
mode = 0666
mode = 0664
} else if strings.HasPrefix(path, "/s/") {
parts := strings.SplitN(strings.TrimPrefix(path, "/s/"), "/", 3)
if len(parts) == 3 && parts[1] == "t" && parts[2] == "idx" {
mode = 0400
} else if len(parts) == 3 && parts[1] == "t" {
mode = 0700
mode = 0500
} else {
mode = 0444
}

View File

@ -83,7 +83,7 @@ var sessionStoreFiles = map[string]os.FileMode{
"idx": 0444,
"ls": 0555,
"kill": 0555,
"sh": 0555,
"sh": 0550,
"b": 0555,
"bfg": 0555,
"bbg": 0555,