fix 9P session/agent boundary enforcement
Bug: reading agent files (log, chat, state, etc.) via 9P returned the same data regardless of which session or agent ID was specified. The server did not validate agent IDs and had out-of-bounds slice access on when the path had fewer components than expected. Fixes: - fs/session/root.go: Added guard before all checks in rootReaddir, rootStat, rootOpen. Added guard before access in rootStat. Added agent ID validation — returns error if the requested agent ID doesn't match the session's actual agent. - cmd/olliesrv/server.go: Restructured pathType() to nest agent path checks inside a single block, preventing proc-path checks from incorrectly matching agent paths. Added agent ID validation (returns "" = not found). Added agent-path support in makeStat() for Qid version tracking on log/chat. Skip blocking files (chat, statewait) in stat-size computation to prevent hangs.
This commit is contained in:
parent
f017486858
commit
3046afd8ed
|
|
@ -543,8 +543,37 @@ func (s *Server) pathType(path string) string {
|
|||
return "dir"
|
||||
}
|
||||
// /session/{id}/agent — directory
|
||||
if len(parts) == 2 && parts[1] == "agent" {
|
||||
return "dir"
|
||||
if len(parts) >= 2 && parts[1] == "agent" {
|
||||
if len(parts) == 2 {
|
||||
return "dir"
|
||||
}
|
||||
// Validate agent ID
|
||||
expectedID := sess.Core.Agent().ID()
|
||||
if expectedID == "" {
|
||||
expectedID = "0"
|
||||
}
|
||||
if parts[2] != expectedID {
|
||||
return ""
|
||||
}
|
||||
// /session/{id}/agent/{aid} — directory
|
||||
if len(parts) == 3 {
|
||||
return "dir"
|
||||
}
|
||||
// /session/{id}/agent/{aid}/proc — directory
|
||||
if len(parts) == 4 && parts[3] == "proc" {
|
||||
return "dir"
|
||||
}
|
||||
// /session/{id}/agent/{aid}/proc/{pid} — verify exists
|
||||
if len(parts) == 5 && parts[3] == "proc" {
|
||||
for _, p := range sess.Core.Agent().ListDetached() {
|
||||
if fmt.Sprintf("%d", p.PID) == parts[4] {
|
||||
return "file"
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
// /session/{id}/agent/{aid}/{file} — file
|
||||
return "file"
|
||||
}
|
||||
// /session/{id}/proc/{pid} — verify PID exists in detached list
|
||||
if len(parts) == 3 && parts[1] == "proc" {
|
||||
|
|
@ -555,27 +584,6 @@ func (s *Server) pathType(path string) string {
|
|||
}
|
||||
return ""
|
||||
}
|
||||
// /session/{id}/agent/{aid} — directory
|
||||
if len(parts) == 3 && parts[1] == "agent" {
|
||||
return "dir"
|
||||
}
|
||||
// /session/{id}/agent/{aid}/proc — directory
|
||||
if len(parts) == 4 && parts[1] == "agent" && parts[3] == "proc" {
|
||||
return "dir"
|
||||
}
|
||||
// /session/{id}/agent/{aid}/proc/{pid} — verify exists
|
||||
if len(parts) == 5 && parts[1] == "agent" && parts[3] == "proc" {
|
||||
for _, p := range sess.Core.Agent().ListDetached() {
|
||||
if fmt.Sprintf("%d", p.PID) == parts[4] {
|
||||
return "file"
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
// /session/{id}/agent/{aid}/{file} — file
|
||||
if len(parts) >= 4 && parts[1] == "agent" {
|
||||
return "file"
|
||||
}
|
||||
// /session/{id}/{file} — file
|
||||
return "file"
|
||||
}
|
||||
|
|
@ -1259,7 +1267,7 @@ func (s *Server) makeStat(path string) plan9.Dir {
|
|||
|
||||
// For chat and tailable mutable files, report actual size and
|
||||
// Qid version so polling tools (tail -f) can detect changes via stat.
|
||||
// Path format: /session/{sessid}/{file}
|
||||
// Path format: /session/{sessid}/{file} or /session/{sessid}/agent/{aid}/{file}
|
||||
if strings.HasPrefix(path, fs.PathSessions) {
|
||||
parts := strings.SplitN(strings.TrimPrefix(path, "/"), "/", 3)
|
||||
if len(parts) == 3 && parts[0] == "session" {
|
||||
|
|
@ -1274,6 +1282,20 @@ func (s *Server) makeStat(path string) plan9.Dir {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Also handle agent paths: /session/{sessid}/agent/{aid}/{file}
|
||||
parts = strings.SplitN(strings.TrimPrefix(path, "/"), "/", 5)
|
||||
if len(parts) == 5 && parts[0] == "session" && parts[2] == "agent" {
|
||||
if sess := fs.Lookup(s.sessionTree, parts[1]); sess != nil {
|
||||
if base == "log" || base == "chat" {
|
||||
length, vers := sess.LogInfo()
|
||||
if length > 64*1024 {
|
||||
length = 64 * 1024
|
||||
}
|
||||
dir.Length = uint64(length)
|
||||
dir.Qid.Vers = vers
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For all other readable files, compute content length so clients
|
||||
|
|
@ -1291,6 +1313,12 @@ func (s *Server) makeStat(path string) plan9.Dir {
|
|||
case strings.HasPrefix(path, fs.PathSessions):
|
||||
// HACK: 9pfuse trusts stat Length — if we report 0, the kernel
|
||||
// never issues a read. Compute size by reading the file content.
|
||||
// Skip blocking/wait files (chat, statewait) — their size is dynamic.
|
||||
base := pathBase(path)
|
||||
if base == "chat" || base == "statewait" {
|
||||
dir.Length = 4096
|
||||
break
|
||||
}
|
||||
if entry, err := s.openEntry(path); err == nil {
|
||||
if data, err := entry.Read(); err == nil {
|
||||
dir.Length = uint64(len(data))
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ func rootReaddir(rs *rootState, name string) ([]os.DirEntry, error) {
|
|||
}
|
||||
return entries, nil
|
||||
}
|
||||
if parts[1] == "agent" {
|
||||
if len(parts) >= 2 && parts[1] == "agent" {
|
||||
if len(parts) == 2 {
|
||||
aid := sess.Core.Agent().ID()
|
||||
if aid == "" {
|
||||
|
|
@ -218,6 +218,16 @@ func rootReaddir(rs *rootState, name string) ([]os.DirEntry, error) {
|
|||
entries := []os.DirEntry{DirEntry(aid, 0755)}
|
||||
return entries, nil
|
||||
}
|
||||
if len(parts) >= 3 {
|
||||
// Validate agent ID matches the session's actual agent
|
||||
expectedID := sess.Core.Agent().ID()
|
||||
if expectedID == "" {
|
||||
expectedID = "0"
|
||||
}
|
||||
if parts[2] != expectedID {
|
||||
return nil, fmt.Errorf("agent %s not found in session %s", parts[2], sessID)
|
||||
}
|
||||
}
|
||||
if len(parts) == 3 {
|
||||
afs := openAgentTree(rs, sess)
|
||||
entries, err := afs.List()
|
||||
|
|
@ -254,14 +264,23 @@ func rootStat(rs *rootState, name string) (os.FileInfo, error) {
|
|||
if len(parts) == 1 {
|
||||
return &SyntheticFileInfo{Name_: sessID, Mode_: Perms[PathSessionDir].DirMode, IsDir_: true}, nil
|
||||
}
|
||||
if parts[1] == "agent" {
|
||||
if len(parts) >= 2 && parts[1] == "agent" {
|
||||
if len(parts) == 2 {
|
||||
return &SyntheticFileInfo{Name_: "agent", Mode_: 0755, IsDir_: true}, nil
|
||||
}
|
||||
if len(parts) >= 3 {
|
||||
expectedID := sess.Core.Agent().ID()
|
||||
if expectedID == "" {
|
||||
expectedID = "0"
|
||||
}
|
||||
if parts[2] != expectedID {
|
||||
return nil, fmt.Errorf("agent %s not found in session %s", parts[2], sessID)
|
||||
}
|
||||
}
|
||||
if len(parts) == 3 {
|
||||
return &SyntheticFileInfo{Name_: parts[2], Mode_: 0755, IsDir_: true}, nil
|
||||
}
|
||||
if parts[3] == "proc" {
|
||||
if len(parts) >= 4 && parts[3] == "proc" {
|
||||
return &SyntheticFileInfo{Name_: "proc", Mode_: 0755, IsDir_: true}, nil
|
||||
}
|
||||
afs := openAgentTree(rs, sess)
|
||||
|
|
@ -346,10 +365,18 @@ func rootOpen(rs *rootState, name string) (File, error) {
|
|||
return nil, fmt.Errorf("%s: is a directory", name)
|
||||
}
|
||||
|
||||
if parts[1] == "agent" {
|
||||
if len(parts) >= 2 && parts[1] == "agent" {
|
||||
if len(parts) < 4 {
|
||||
return nil, fmt.Errorf("%s: is a directory", name)
|
||||
}
|
||||
// Validate agent ID
|
||||
expectedID := sess.Core.Agent().ID()
|
||||
if expectedID == "" {
|
||||
expectedID = "0"
|
||||
}
|
||||
if parts[2] != expectedID {
|
||||
return nil, fmt.Errorf("agent %s not found in session %s", parts[2], sessID)
|
||||
}
|
||||
afs := openAgentTree(rs, sess)
|
||||
return afs.Open(parts[3])
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue