Count a setuid find only where the account can execute it, read at every judgement (hq issue 361)
The two finds on the control-node sit in the operator's home of mode 0700, which the agent account cannot search. The search keeps every find; each judgement reads the modes on the way again, since an account can open its own home without root. A find in a home is said by count, its path in the journal alone. Also from review: a checkpoint racing SearchNow forgets the walk it just kept, and a walk kept by an older engine says its counts as lower bounds.
This commit is contained in:
@@ -123,6 +123,8 @@ type SearchProgress struct {
|
||||
// far, before the package manager is asked about them.
|
||||
Visited int64
|
||||
Found int
|
||||
// AtLeast says Visited is a lower bound: the walk was begun by an engine that did not count.
|
||||
AtLeast bool
|
||||
// Kept is when its progress was last kept, zero before the first time.
|
||||
Kept time.Time
|
||||
// Finished is every filesystem walked to its end, in order, and how long its walk took.
|
||||
@@ -139,7 +141,7 @@ type SearchedFilesystem struct {
|
||||
// progressOf is p as anybody may read it. Counts and mount points, no I/O.
|
||||
func progressOf(p *SetuidProgress, kept time.Time) *SearchProgress {
|
||||
sp := &SearchProgress{Started: p.Started, Filesystems: len(p.Mounts), Done: len(p.Done), Visited: p.Visited,
|
||||
Found: len(p.Found), Kept: kept}
|
||||
Found: len(p.Found), Kept: kept, AtLeast: !p.Counted}
|
||||
for i, m := range p.Done {
|
||||
took := time.Duration(-1)
|
||||
if i < len(p.Took) {
|
||||
@@ -165,7 +167,11 @@ func (sp *SearchProgress) Words() string {
|
||||
if sp.Walking != "" {
|
||||
s += ", walking " + sp.Walking
|
||||
}
|
||||
s += fmt.Sprintf(", %d entries visited, %d setuid- or setgid-root files found so far", sp.Visited, sp.Found)
|
||||
atLeast := ""
|
||||
if sp.AtLeast {
|
||||
atLeast = "at least "
|
||||
}
|
||||
s += fmt.Sprintf(", %s%d entries visited, %d setuid- or setgid-root files found so far", atLeast, sp.Visited, sp.Found)
|
||||
if sp.Kept.IsZero() {
|
||||
s += ", not kept yet"
|
||||
} else {
|
||||
@@ -286,7 +292,7 @@ type SetuidSearch func(ctx context.Context, p *SetuidProgress, checkpoint func()
|
||||
// start, a result reads at most one search older than it is, which errs towards not judging.
|
||||
func (c *SetuidCache) Look(now time.Time, mounts []string, search SetuidSearch) ([]string, time.Time, error) {
|
||||
if c == nil {
|
||||
p := &SetuidProgress{Started: now, Mounts: slices.Clone(mounts)}
|
||||
p := &SetuidProgress{Started: now, Mounts: slices.Clone(mounts), Counted: true}
|
||||
found, err := search(context.Background(), p, func() {})
|
||||
return found, now, err
|
||||
}
|
||||
@@ -360,7 +366,7 @@ func (c *SetuidCache) start(now time.Time, mounts []string, search SetuidSearch)
|
||||
resumed := p != nil
|
||||
if p == nil {
|
||||
p = &SetuidProgress{ID: fmt.Sprintf("%x", now.UnixNano()), Started: now, Mounts: slices.Clone(mounts),
|
||||
Cursor: map[string]string{}}
|
||||
Cursor: map[string]string{}, Counted: true}
|
||||
}
|
||||
c.running, c.startedAt, c.attemptAt, c.again = true, p.Started, now, false
|
||||
c.starts++
|
||||
@@ -395,7 +401,16 @@ func (c *SetuidCache) start(now time.Time, mounts []string, search SetuidSearch)
|
||||
if !superseded { // root acted since this walk started: its progress is not to be carried on
|
||||
at := time.Now()
|
||||
p.Kept = at
|
||||
if kerr := c.keepProgress(p); kerr != nil {
|
||||
kerr := c.keepProgress(p)
|
||||
// Superseded while it wrote (SearchAgain, SearchNow), after they forgot the kept walk: forgotten
|
||||
// again here, so no engine restarted before this walk ends carries it on (the review of
|
||||
// 2026-10-10). Written outside the lock, so a look never waits on the disk.
|
||||
c.mu.Lock()
|
||||
if c.again || c.gen != gen {
|
||||
c.forgetProgress()
|
||||
}
|
||||
c.mu.Unlock()
|
||||
if kerr != nil {
|
||||
p.Kept = kept // not kept: the file still says the time before
|
||||
if !unkept {
|
||||
unkept = true
|
||||
@@ -450,8 +465,13 @@ func (c *SetuidCache) start(now time.Time, mounts []string, search SetuidSearch)
|
||||
"restarts: " + err.Error())
|
||||
}
|
||||
c.forgetProgress()
|
||||
// The paths in the engine's journal alone: the verdict says one in a home by count.
|
||||
named := ""
|
||||
if len(found) > 0 {
|
||||
named = ": " + strings.Join(found, ", ")
|
||||
}
|
||||
c.say(fmt.Sprintf("the search for setuid programs started %s completed (%s this time): %d no package "+
|
||||
"owns", p.Started.Local().Format("2006-01-02 15:04"), took.Round(time.Second), len(found)))
|
||||
"owns%s", p.Started.Local().Format("2006-01-02 15:04"), took.Round(time.Second), len(found), named))
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user