github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/teams/audit.go (about)

     1  package teams
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  	"sync"
     8  	"time"
     9  
    10  	lru "github.com/hashicorp/golang-lru"
    11  
    12  	"github.com/keybase/client/go/libkb"
    13  	"github.com/keybase/client/go/protocol/keybase1"
    14  	"github.com/keybase/client/go/teams/hidden"
    15  	"github.com/keybase/pipeliner"
    16  )
    17  
    18  // AuditCurrentVersion is the version that works with this code. Older stored
    19  // versions will be discarded on load from level DB.
    20  const AuditCurrentVersion = keybase1.AuditVersion_V4
    21  
    22  var desktopParams = libkb.TeamAuditParams{
    23  	RootFreshness:         5 * time.Minute,
    24  	MerkleMovementTrigger: keybase1.Seqno(10000),
    25  	NumPreProbes:          20,
    26  	NumPostProbes:         20,
    27  	Parallelism:           4,
    28  	LRUSize:               1000,
    29  }
    30  
    31  var mobileParamsWifi = libkb.TeamAuditParams{
    32  	RootFreshness:         10 * time.Minute,
    33  	MerkleMovementTrigger: keybase1.Seqno(200000),
    34  	NumPreProbes:          8,
    35  	NumPostProbes:         8,
    36  	Parallelism:           3,
    37  	LRUSize:               500,
    38  }
    39  
    40  var mobileParamsNoWifi = libkb.TeamAuditParams{
    41  	RootFreshness:         15 * time.Minute,
    42  	MerkleMovementTrigger: keybase1.Seqno(300000),
    43  	NumPreProbes:          4,
    44  	NumPostProbes:         4,
    45  	Parallelism:           3,
    46  	LRUSize:               500,
    47  }
    48  
    49  var devParams = libkb.TeamAuditParams{
    50  	RootFreshness:         10 * time.Minute,
    51  	MerkleMovementTrigger: keybase1.Seqno(10000),
    52  	NumPreProbes:          3,
    53  	NumPostProbes:         3,
    54  	Parallelism:           3,
    55  	LRUSize:               500,
    56  }
    57  
    58  // getAuditParams will return parameters based on the platform. On mobile,
    59  // we're going to be performing a smaller audit, and therefore have a smaller
    60  // security margin (1-2^10). But it's worth it given the bandwidth and CPU
    61  // constraints.
    62  func getAuditParams(m libkb.MetaContext) libkb.TeamAuditParams {
    63  	if m.G().Env.Test.TeamAuditParams != nil {
    64  		return *m.G().Env.Test.TeamAuditParams
    65  	}
    66  	if m.G().Env.GetRunMode() == libkb.DevelRunMode {
    67  		return devParams
    68  	}
    69  	if libkb.IsMobilePlatform() {
    70  		if m.G().MobileNetState.State().IsLimited() {
    71  			return mobileParamsNoWifi
    72  		}
    73  		return mobileParamsWifi
    74  	}
    75  	return desktopParams
    76  }
    77  
    78  type dummyAuditor struct{}
    79  
    80  func (d dummyAuditor) AuditTeam(m libkb.MetaContext, id keybase1.TeamID, isPublic bool,
    81  	headMerkleSeqno keybase1.Seqno, chain map[keybase1.Seqno]keybase1.LinkID, hiddenChain map[keybase1.Seqno]keybase1.LinkID,
    82  	maxSeqno keybase1.Seqno, maxHiddenSeqno keybase1.Seqno, lastMerkleRoot *libkb.MerkleRoot, auditMode keybase1.AuditMode) error {
    83  	return nil
    84  }
    85  
    86  type Auditor struct {
    87  
    88  	// single-flight lock on TeamID
    89  	locktab *libkb.LockTable
    90  
    91  	// Map of TeamID -> AuditHistory
    92  	// The LRU is protected by a mutex, because it's swapped out on logout.
    93  	lruMutex sync.Mutex
    94  	lru      *lru.Cache
    95  }
    96  
    97  // NewAuditor makes a new auditor
    98  func NewAuditor(g *libkb.GlobalContext) *Auditor {
    99  	ret := &Auditor{
   100  		locktab: libkb.NewLockTable(),
   101  	}
   102  	ret.newLRU(libkb.NewMetaContextBackground(g))
   103  	return ret
   104  }
   105  
   106  // NewAuditorAndInstall makes a new Auditor and dangles it
   107  // off of the given GlobalContext.
   108  func NewAuditorAndInstall(g *libkb.GlobalContext) {
   109  	if g.GetEnv().GetDisableTeamAuditor() {
   110  		g.Log.CDebugf(context.TODO(), "Using dummy auditor, audit disabled")
   111  		g.SetTeamAuditor(dummyAuditor{})
   112  	} else {
   113  		a := NewAuditor(g)
   114  		g.SetTeamAuditor(a)
   115  		g.AddLogoutHook(a, "team auditor")
   116  		g.AddDbNukeHook(a, "team auditor")
   117  	}
   118  }
   119  
   120  // AuditTeam runs an audit on the links of the given team chain (or team chain suffix).
   121  // The security factor of the audit is a function of the hardcoded parameters above,
   122  // and the amount of time since the last audit. This method should use some sort of
   123  // long-lived cache (via local DB) so that previous audits can be combined with the
   124  // current one. headMerkleSeqno is is the Merkle Root claimed in the head of the team.
   125  // maxSeqno is the maximum seqno of the chainLinks passed; that is, the highest
   126  // Seqno for which chain[s] is defined.
   127  func (a *Auditor) AuditTeam(m libkb.MetaContext, id keybase1.TeamID, isPublic bool, headMerkleSeqno keybase1.Seqno, chain map[keybase1.Seqno]keybase1.LinkID,
   128  	hiddenChain map[keybase1.Seqno]keybase1.LinkID, maxSeqno keybase1.Seqno, maxHiddenSeqno keybase1.Seqno,
   129  	lastMerkleRoot *libkb.MerkleRoot, auditMode keybase1.AuditMode) (err error) {
   130  
   131  	m = m.WithLogTag("AUDIT")
   132  	defer m.Trace(fmt.Sprintf("Auditor#AuditTeam(%+v)", id), &err)()
   133  	defer m.PerfTrace(fmt.Sprintf("Auditor#AuditTeam(%+v)", id), &err)()
   134  	start := time.Now()
   135  	defer func() {
   136  		var message string
   137  		if err == nil {
   138  			message = fmt.Sprintf("Audited team %s", id)
   139  		} else {
   140  			message = fmt.Sprintf("Failed auditing %s", id)
   141  		}
   142  		m.G().RuntimeStats.PushPerfEvent(keybase1.PerfEvent{
   143  			EventType: keybase1.PerfEventType_TEAMAUDIT,
   144  			Message:   message,
   145  			Ctime:     keybase1.ToTime(start),
   146  		})
   147  	}()
   148  
   149  	if id.IsPublic() != isPublic {
   150  		return NewBadPublicError(id, isPublic)
   151  	}
   152  
   153  	// Single-flight lock by team ID.
   154  	lock := a.locktab.AcquireOnName(m.Ctx(), m.G(), id.String())
   155  	defer lock.Release(m.Ctx())
   156  
   157  	err = a.auditLocked(m, id, headMerkleSeqno, chain, hiddenChain, maxSeqno, maxHiddenSeqno, lastMerkleRoot, auditMode)
   158  	if hidden.ShouldClearSupportFlagOnError(err) {
   159  		m.Debug("Clearing support hidden chain flag for team %s because of error %v in Auditor", id, err)
   160  		m.G().GetHiddenTeamChainManager().ClearSupportFlagIfFalse(m, id)
   161  	}
   162  	return err
   163  }
   164  
   165  func (a *Auditor) getLRU() *lru.Cache {
   166  	a.lruMutex.Lock()
   167  	defer a.lruMutex.Unlock()
   168  	return a.lru
   169  }
   170  
   171  func (a *Auditor) getFromLRU(m libkb.MetaContext, id keybase1.TeamID, lru *lru.Cache) *keybase1.AuditHistory {
   172  	tmp, found := lru.Get(id)
   173  	if !found {
   174  		return nil
   175  	}
   176  	ret, ok := tmp.(*keybase1.AuditHistory)
   177  	if !ok {
   178  		m.Error("Bad type assertion in Auditor#getFromLRU")
   179  		return nil
   180  	}
   181  	return ret
   182  }
   183  
   184  func (a *Auditor) getFromDisk(m libkb.MetaContext, id keybase1.TeamID) (*keybase1.AuditHistory, error) {
   185  	var ret keybase1.AuditHistory
   186  	found, err := m.G().LocalDb.GetInto(&ret, libkb.DbKey{Typ: libkb.DBTeamAuditor, Key: string(id)})
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  	if !found {
   191  		return nil, nil
   192  	}
   193  	if ret.Version != AuditCurrentVersion {
   194  		m.Debug("Discarding audit at version %d (we are supporting %d)", ret.Version, AuditCurrentVersion)
   195  		return nil, nil
   196  	}
   197  	return &ret, nil
   198  }
   199  
   200  func (a *Auditor) getFromCache(m libkb.MetaContext, id keybase1.TeamID, lru *lru.Cache) (*keybase1.AuditHistory, error) {
   201  
   202  	ret := a.getFromLRU(m, id, lru)
   203  	if ret != nil {
   204  		return ret, nil
   205  	}
   206  	ret, err := a.getFromDisk(m, id)
   207  	return ret, err
   208  }
   209  
   210  func (a *Auditor) putToCache(m libkb.MetaContext, id keybase1.TeamID, lru *lru.Cache, h *keybase1.AuditHistory) (err error) {
   211  	lru.Add(id, h)
   212  	err = m.G().LocalDb.PutObj(libkb.DbKey{Typ: libkb.DBTeamAuditor, Key: string(id)}, nil, *h)
   213  	return err
   214  }
   215  
   216  func (a *Auditor) checkRecent(m libkb.MetaContext, history *keybase1.AuditHistory, root *libkb.MerkleRoot) bool {
   217  	if root == nil {
   218  		m.Debug("no recent known merkle root in checkRecent")
   219  		return false
   220  	}
   221  	last := lastAudit(history)
   222  	if last == nil {
   223  		m.Debug("no recent audits")
   224  		return false
   225  	}
   226  	diff := *root.Seqno() - last.MaxMerkleSeqno
   227  	if diff >= getAuditParams(m).MerkleMovementTrigger {
   228  		m.Debug("previous merkle audit was %v ago", diff)
   229  		return false
   230  	}
   231  	return true
   232  }
   233  
   234  func lastAudit(h *keybase1.AuditHistory) *keybase1.Audit {
   235  	if h == nil {
   236  		return nil
   237  	}
   238  	if len(h.Audits) == 0 {
   239  		return nil
   240  	}
   241  	ret := h.Audits[len(h.Audits)-1]
   242  	return &ret
   243  }
   244  
   245  func maxMerkleProbeInAuditHistory(h *keybase1.AuditHistory) keybase1.Seqno {
   246  	if h == nil {
   247  		return keybase1.Seqno(0)
   248  	}
   249  
   250  	// The audits at the back of the list are the most recent, but some
   251  	// of these audits might have been "nil" audits that didn't actually probe
   252  	// any new paths (see comment in doPostProbes about how you can short-circuit
   253  	// doing probes). So keep going backwards until we hit the first non-0
   254  	// maxMerkleProbe. Remember, maxMerkleProbe is the maximum merkle seqno
   255  	// probed in the last audit.
   256  	for i := len(h.Audits) - 1; i >= 0; i-- {
   257  		if mmp := h.Audits[i].MaxMerkleProbe; mmp >= keybase1.Seqno(0) {
   258  			return mmp
   259  		}
   260  	}
   261  	return keybase1.Seqno(0)
   262  }
   263  
   264  func makeHistory(history *keybase1.AuditHistory, id keybase1.TeamID) *keybase1.AuditHistory {
   265  	if history == nil {
   266  		return &keybase1.AuditHistory{
   267  			ID:          id,
   268  			Public:      id.IsPublic(),
   269  			Version:     AuditCurrentVersion,
   270  			PreProbes:   make(map[keybase1.Seqno]keybase1.Probe),
   271  			PostProbes:  make(map[keybase1.Seqno]keybase1.Probe),
   272  			Tails:       make(map[keybase1.Seqno]keybase1.LinkID),
   273  			HiddenTails: make(map[keybase1.Seqno]keybase1.LinkID),
   274  		}
   275  	}
   276  	ret := history.DeepCopy()
   277  	return &ret
   278  }
   279  
   280  // doPostProbes probes the sequence timeline _after_ the team was created.
   281  func (a *Auditor) doPostProbes(m libkb.MetaContext, history *keybase1.AuditHistory, probeID int, headMerkleSeqno keybase1.Seqno, latestMerkleSeqno keybase1.Seqno, chain map[keybase1.Seqno]keybase1.LinkID,
   282  	hiddenChain map[keybase1.Seqno]keybase1.LinkID, maxChainSeqno keybase1.Seqno, maxHiddenSeqno keybase1.Seqno, auditMode keybase1.AuditMode) (numProbes int, maxMerkleProbe keybase1.Seqno, probeTuples []probeTuple, err error) {
   283  	defer m.Trace("Auditor#doPostProbes", &err)()
   284  
   285  	var low keybase1.Seqno
   286  	lastMaxMerkleProbe := maxMerkleProbeInAuditHistory(history)
   287  	var prev *probeTuple
   288  	if lastMaxMerkleProbe == keybase1.Seqno(0) {
   289  		low = headMerkleSeqno
   290  	} else {
   291  		low = lastMaxMerkleProbe
   292  		probe, ok := history.PostProbes[lastMaxMerkleProbe]
   293  		if !ok {
   294  			// This might happen if leveldb was corrupted, or if we had a bug of some sort.
   295  			// But it makes sense not error out of the audit process.
   296  			m.Warning("previous audit pointed to a bogus probe (seqno=%d); starting from scratch at head Merkle seqno=%d", lastMaxMerkleProbe, headMerkleSeqno)
   297  			low = headMerkleSeqno
   298  		} else {
   299  			prev = &probeTuple{
   300  				merkle: lastMaxMerkleProbe,
   301  				team:   probe.TeamSeqno,
   302  				// leave linkID nil, it's not needed...
   303  			}
   304  		}
   305  	}
   306  
   307  	// Probe only after the checkpoint. Merkle roots before the checkpoint aren't examined and are
   308  	// trusted to be legit, being buried far enough in the past. Therefore probing is not necessary.
   309  	first := m.G().GetMerkleClient().FirstExaminableHistoricalRoot(m)
   310  
   311  	if first == nil {
   312  		return 0, keybase1.Seqno(0), nil, NewAuditError("cannot find a first modern merkle sequence")
   313  	}
   314  
   315  	if low < *first {
   316  		m.Debug("bumping low sequence number to last merkle checkpoint: %s", *first)
   317  		low = *first
   318  	}
   319  
   320  	probeTuples, err = a.computeProbes(m, history.ID, history.PostProbes, probeID, low, latestMerkleSeqno, 0, getAuditParams(m).NumPostProbes, history.PostProbesToRetry)
   321  	if err != nil {
   322  		return 0, keybase1.Seqno(0), probeTuples, err
   323  	}
   324  	if len(probeTuples) == 0 {
   325  		m.Debug("No probe tuples, so bailing")
   326  		return 0, keybase1.Seqno(0), probeTuples, nil
   327  	}
   328  
   329  	ret := 0
   330  
   331  	for _, tuple := range probeTuples {
   332  		m.Debug("postProbe: checking probe at %+v", tuple)
   333  
   334  		// Note that we might see tuple.team == 0 in a legit case. There is a race here. If seqno=1
   335  		// of team foo was made when the last merkle seqno was 2000, let's say, then for merkle seqnos
   336  		// 2000-2001, the team foo might not be in the tree. So we'd expect the tuple.team to be 0
   337  		// in that (unlikely) case. So we don't check the validity of the linkID in that case
   338  		// (since it doesn't exist). However, we still do check that tuple.team's are non-decreasing,
   339  		// so this 0 value is checked below (see comment).
   340  		if tuple.team > keybase1.Seqno(0) {
   341  			expectedLinkID, ok := chain[tuple.team]
   342  			if !ok {
   343  				return 0, keybase1.Seqno(0), probeTuples, NewAuditError("team chain rollback: merkle seqno %v referred to team chain seqno %v which is not part of our chain (which at merkle seqno %v ends at %v)", tuple.merkle, tuple.team, latestMerkleSeqno, maxChainSeqno)
   344  			}
   345  			if !expectedLinkID.Eq(tuple.linkID) {
   346  				return 0, keybase1.Seqno(0), probeTuples, NewAuditError("team chain linkID mismatch at %d: wanted %s but got %s via merkle seqno %d", tuple.team, expectedLinkID, tuple.linkID, tuple.merkle)
   347  			}
   348  		}
   349  
   350  		ret++
   351  
   352  		// This condition is the key ordering condition. It is still checked in the case of the race
   353  		// condition at tuple.team==0 mentioned just above.
   354  		if prev != nil && prev.team > tuple.team {
   355  			return 0, keybase1.Seqno(0), probeTuples, NewAuditError("team chain unexpected jump: %d > %d via merkle seqno %d", prev.team, tuple.team, tuple.merkle)
   356  		}
   357  		if tuple.merkle > maxMerkleProbe {
   358  			maxMerkleProbe = tuple.merkle
   359  		}
   360  		prev = &tuple
   361  	}
   362  	return ret, maxMerkleProbe, probeTuples, nil
   363  }
   364  
   365  // doPreProbes probabilistically checks that no team occupied the slot before the team
   366  // in question was created. It selects probes from before the team was created. Each
   367  // probed leaf must not be occupied.
   368  func (a *Auditor) doPreProbes(m libkb.MetaContext, history *keybase1.AuditHistory, probeID int, headMerkleSeqno keybase1.Seqno, auditMode keybase1.AuditMode) (numProbes int, probeTuples []probeTuple, err error) {
   369  	defer m.Trace("Auditor#doPreProbes", &err)()
   370  
   371  	first := m.G().GetMerkleClient().FirstExaminableHistoricalRoot(m)
   372  	if first == nil {
   373  		return 0, nil, NewAuditError("cannot find a first modern merkle sequence")
   374  	}
   375  
   376  	probeTuples, err = a.computeProbes(m, history.ID, history.PreProbes, probeID, *first, headMerkleSeqno, len(history.PreProbes), getAuditParams(m).NumPreProbes, history.PreProbesToRetry)
   377  	if err != nil {
   378  		return 0, probeTuples, err
   379  	}
   380  	if len(probeTuples) == 0 {
   381  		m.Debug("No probe pairs, so bailing")
   382  		return 0, nil, nil
   383  	}
   384  	for _, tuple := range probeTuples {
   385  		m.Debug("preProbe: checking probe at merkle %d", tuple.merkle)
   386  		if tuple.team != keybase1.Seqno(0) || !tuple.linkID.IsNil() {
   387  			return 0, probeTuples, NewAuditError("merkle root should not have had a leaf for team %v: got %s/%d at merkle seqno %v",
   388  				history.ID, tuple.linkID, tuple.team, tuple.merkle)
   389  		}
   390  	}
   391  	return len(probeTuples), probeTuples, nil
   392  }
   393  
   394  // randSeqno picks a random number in [lo,hi] inclusively.
   395  func randSeqno(m libkb.MetaContext, lo keybase1.Seqno, hi keybase1.Seqno) (keybase1.Seqno, error) {
   396  	s, err := m.G().GetRandom().RndRange(int64(lo), int64(hi))
   397  	return keybase1.Seqno(s), err
   398  }
   399  
   400  type probeTuple struct {
   401  	merkle keybase1.Seqno
   402  	team   keybase1.Seqno
   403  	linkID keybase1.LinkID
   404  }
   405  
   406  func (a *Auditor) computeProbes(m libkb.MetaContext, teamID keybase1.TeamID, probes map[keybase1.Seqno]keybase1.Probe, probeID int, left keybase1.Seqno, right keybase1.Seqno, probesInRange int, n int, probesToRetry []keybase1.Seqno) (ret []probeTuple, err error) {
   407  	ret, err = a.scheduleProbes(m, probes, probeID, left, right, probesInRange, n, probesToRetry)
   408  	if err != nil {
   409  		return nil, err
   410  	}
   411  	err = a.lookupProbes(m, teamID, ret)
   412  	if err != nil {
   413  		// In case of error, we still return the probes we selected so they can
   414  		// be retried in the next audit
   415  		return ret, err
   416  	}
   417  	return ret, err
   418  }
   419  
   420  func (a *Auditor) scheduleProbes(m libkb.MetaContext, previousProbes map[keybase1.Seqno]keybase1.Probe, probeID int, left keybase1.Seqno, right keybase1.Seqno, probesInRange int, n int, probesToRetry []keybase1.Seqno) (ret []probeTuple, err error) {
   421  	defer m.Trace(fmt.Sprintf("Auditor#scheduleProbes(left=%d,right=%d)", left, right), &err)()
   422  	if probesInRange > n {
   423  		m.Debug("no more probes needed; did %d, wanted %d", probesInRange, n)
   424  		return nil, nil
   425  	}
   426  	rng := right - left + 1
   427  	if int(rng) <= probesInRange {
   428  		m.Debug("no more probes needed; range was only %d, and we did %d", rng, probesInRange)
   429  		return nil, nil
   430  	}
   431  	currentProbes := make(map[keybase1.Seqno]bool)
   432  	for _, s := range probesToRetry {
   433  		ret = append(ret, probeTuple{merkle: s})
   434  		currentProbes[s] = true
   435  	}
   436  	currentProbesWanted := n - len(probesToRetry)
   437  	for i := 0; i < currentProbesWanted; i++ {
   438  		x, err := randSeqno(m, left, right)
   439  		if err != nil {
   440  			return nil, err
   441  		}
   442  		if _, found := previousProbes[x]; found {
   443  			continue
   444  		}
   445  		if currentProbes[x] {
   446  			continue
   447  		}
   448  		ret = append(ret, probeTuple{merkle: x})
   449  		currentProbes[x] = true
   450  	}
   451  	sort.SliceStable(ret, func(i, j int) bool {
   452  		return ret[i].merkle < ret[j].merkle
   453  	})
   454  	m.Debug("scheduled probes: %+v", ret)
   455  	return ret, nil
   456  }
   457  
   458  func (a *Auditor) lookupProbe(m libkb.MetaContext, teamID keybase1.TeamID, probe *probeTuple) (err error) {
   459  	defer m.Trace(fmt.Sprintf("Auditor#lookupProbe(%v,%v)", teamID, *probe), &err)()
   460  	leaf, _, _, err := m.G().GetMerkleClient().LookupLeafAtSeqnoForAudit(m, teamID.AsUserOrTeam(), probe.merkle, hidden.ProcessHiddenResponseFunc)
   461  	if err != nil {
   462  		return err
   463  	}
   464  	if leaf == nil || leaf.Private == nil {
   465  		m.Debug("nil leaf at %v/%v", teamID, probe.merkle)
   466  		return nil
   467  	}
   468  	probe.team = leaf.Private.Seqno
   469  	if leaf.Private.LinkID != nil {
   470  		probe.linkID = leaf.Private.LinkID.Export()
   471  	}
   472  	return nil
   473  }
   474  
   475  func (a *Auditor) lookupProbes(m libkb.MetaContext, teamID keybase1.TeamID, tuples []probeTuple) (err error) {
   476  	pipeliner := pipeliner.NewPipeliner(getAuditParams(m).Parallelism)
   477  	for i := range tuples {
   478  		if err = pipeliner.WaitForRoom(m.Ctx()); err != nil {
   479  			return err
   480  		}
   481  		go func(probe *probeTuple) {
   482  			err := a.lookupProbe(m, teamID, probe)
   483  			pipeliner.CompleteOne(err)
   484  		}(&tuples[i])
   485  	}
   486  	err = pipeliner.Flush(m.Ctx())
   487  	return err
   488  }
   489  
   490  func (a *Auditor) checkTail(m libkb.MetaContext, history *keybase1.AuditHistory, lastAudit keybase1.Audit, chain map[keybase1.Seqno]keybase1.LinkID, hiddenChain map[keybase1.Seqno]keybase1.LinkID, maxChainSeqno keybase1.Seqno, maxHiddenSeqno keybase1.Seqno, auditMode keybase1.AuditMode) (err error) {
   491  	link, ok := chain[lastAudit.MaxChainSeqno]
   492  	if !ok || link.IsNil() {
   493  		return NewAuditError("last audit ended at %d, but wasn't found in new chain", lastAudit.MaxChainSeqno)
   494  	}
   495  	tail, ok := history.Tails[lastAudit.MaxChainSeqno]
   496  	if !ok || tail.IsNil() {
   497  		return NewAuditError("previous chain tail at %d did not have a linkID", lastAudit.MaxChainSeqno)
   498  	}
   499  	if !link.Eq(tail) {
   500  		return NewAuditError("bad chain tail mismatch (%s != %s) at chain link %d", link, tail, lastAudit.MaxChainSeqno)
   501  	}
   502  	link, ok = chain[maxChainSeqno]
   503  	if !ok || link.IsNil() {
   504  		return NewAuditError("given chain didn't have a link at %d, but it was expected", maxChainSeqno)
   505  	}
   506  
   507  	if auditMode == keybase1.AuditMode_STANDARD_NO_HIDDEN {
   508  		m.Debug("Skipping hidden tail check as the auditMode does not require it.")
   509  		return nil
   510  	}
   511  	if lastAudit.MaxHiddenSeqno == 0 {
   512  		m.Debug("Skipping hidden tail check as there was none in the last audit.")
   513  		return nil
   514  	}
   515  	link, ok = hiddenChain[lastAudit.MaxHiddenSeqno]
   516  	if !ok || link.IsNil() {
   517  		return NewAuditError("last audit ended at %d, but wasn't found in new chain", lastAudit.MaxHiddenSeqno)
   518  	}
   519  	tail, ok = history.HiddenTails[lastAudit.MaxHiddenSeqno]
   520  	if !ok || tail.IsNil() {
   521  		return NewAuditError("previous hidden chain tail at %d did not have a linkID", lastAudit.MaxHiddenSeqno)
   522  	}
   523  	if !link.Eq(tail) {
   524  		return NewAuditError("hidden chain tail mismatch (%s != %s) at chain link %d", link, tail, lastAudit.MaxHiddenSeqno)
   525  	}
   526  	return nil
   527  }
   528  
   529  func (a *Auditor) skipAuditSinceJustCreated(m libkb.MetaContext, id keybase1.TeamID, headMerkleSeqno keybase1.Seqno) (err error) {
   530  	now := m.G().Clock().Now()
   531  	until := now.Add(getAuditParams(m).RootFreshness)
   532  	m.Debug("team (%s) was just created; skipping the audit until %v", id, until)
   533  	history := makeHistory(nil, id)
   534  	history.SkipUntil = keybase1.ToTime(until)
   535  	history.PriorMerkleSeqno = headMerkleSeqno
   536  	lru := a.getLRU()
   537  	return a.putToCache(m, id, lru, history)
   538  }
   539  
   540  func (a *Auditor) holdOffSinceJustCreated(m libkb.MetaContext, history *keybase1.AuditHistory) (res bool, err error) {
   541  	if history == nil || history.SkipUntil == keybase1.Time(0) {
   542  		return false, nil
   543  	}
   544  	now := m.G().Clock().Now()
   545  	until := keybase1.FromTime(history.SkipUntil)
   546  	if now.After(until) {
   547  		return false, nil
   548  	}
   549  	m.Debug("holding off on subsequent audits since the team (%s) was just created (until %v)", history.ID, until)
   550  	return true, nil
   551  }
   552  
   553  func (a *Auditor) auditLocked(m libkb.MetaContext, id keybase1.TeamID, headMerkleSeqno keybase1.Seqno, chain map[keybase1.Seqno]keybase1.LinkID, hiddenChain map[keybase1.Seqno]keybase1.LinkID, maxChainSeqno keybase1.Seqno, maxHiddenSeqno keybase1.Seqno, lastMerkleRoot *libkb.MerkleRoot, auditMode keybase1.AuditMode) (err error) {
   554  
   555  	defer m.Trace(fmt.Sprintf("Auditor#auditLocked(%v,%s)", id, auditMode), &err)()
   556  
   557  	lru := a.getLRU()
   558  
   559  	switch auditMode {
   560  	case keybase1.AuditMode_JUST_CREATED:
   561  		return a.skipAuditSinceJustCreated(m, id, headMerkleSeqno)
   562  	case keybase1.AuditMode_SKIP:
   563  		m.Debug("skipping audit due to AuditModeSkip flag")
   564  		return nil
   565  	}
   566  
   567  	history, err := a.getFromCache(m, id, lru)
   568  	if err != nil {
   569  		return err
   570  	}
   571  
   572  	holdOff, err := a.holdOffSinceJustCreated(m, history)
   573  	if err != nil {
   574  		return err
   575  	}
   576  	if holdOff {
   577  		return nil
   578  	}
   579  
   580  	if lastMerkleRoot.Seqno() == nil {
   581  		return NewAuditError("logic error: nil lastMerkleRoot.Seqno()")
   582  	}
   583  
   584  	last := lastAudit(history)
   585  
   586  	// It's possible that we're bouncing back and forth between the Fast and Slow
   587  	// loader. Therefore, it might have been that we previous audited up to chainlink
   588  	// 20, and now we're seeing an audit only for link 18 (if one of them was stale).
   589  	// That's fine, just make sure to short-circuit as long as we've audited past
   590  	// the given maxChainSeqno.
   591  	if last != nil && last.MaxChainSeqno >= maxChainSeqno {
   592  		m.Debug("Short-circuit audit, since there is no new data (@%v <= %v)", maxChainSeqno, last.MaxChainSeqno)
   593  		return nil
   594  	}
   595  
   596  	// Check that the last time we ran an audit is a subchain of the new links
   597  	// we got down. It suffices to check that the last link in that chain
   598  	// appears in the given chain with the right link ID.
   599  	if last != nil {
   600  		err = a.checkTail(m, history, *last, chain, hiddenChain, maxChainSeqno, maxHiddenSeqno, auditMode)
   601  		if err != nil {
   602  			return err
   603  		}
   604  	}
   605  
   606  	if history != nil && a.checkRecent(m, history, lastMerkleRoot) {
   607  		m.Debug("cached audit was recent; short-circuiting")
   608  		return nil
   609  	}
   610  
   611  	history = makeHistory(history, id)
   612  
   613  	newAuditIndex := len(history.Audits)
   614  
   615  	var numPreProbes, numPostProbes int
   616  
   617  	numPreProbes, preProbeTuples, err := a.doPreProbes(m, history, newAuditIndex, headMerkleSeqno, auditMode)
   618  	if err != nil {
   619  		history.PreProbesToRetry = getMerkleSeqnosFromProbes(preProbeTuples)
   620  		err2 := a.putToCache(m, id, lru, history)
   621  		if err2 != nil {
   622  			return NewAuditError("Error during doPreProbes (%v) followed by an error in storing the audit state (%v)", err.Error(), err2.Error())
   623  		}
   624  		return err
   625  	}
   626  
   627  	numPostProbes, maxMerkleProbe, postProbeTuples, err := a.doPostProbes(m, history, newAuditIndex, headMerkleSeqno, *(lastMerkleRoot.Seqno()), chain, hiddenChain, maxChainSeqno, maxHiddenSeqno, auditMode)
   628  	if err != nil {
   629  		history.PostProbesToRetry = getMerkleSeqnosFromProbes(postProbeTuples)
   630  		err2 := a.putToCache(m, id, lru, history)
   631  		if err2 != nil {
   632  			return NewAuditError("Error during doPostProbes (%v) followed by an error in storing the audit state (%v)", err.Error(), err2.Error())
   633  		}
   634  		return err
   635  	}
   636  
   637  	for _, tuple := range postProbeTuples {
   638  		history.PostProbes[tuple.merkle] = keybase1.Probe{Index: newAuditIndex, TeamSeqno: tuple.team}
   639  	}
   640  
   641  	if numPostProbes+numPreProbes == 0 {
   642  		m.Debug("No new probes, not writing to cache")
   643  		return nil
   644  	}
   645  
   646  	m.Debug("Probes completed; numPre=%d, numPost=%d", numPreProbes, numPostProbes)
   647  
   648  	audit := keybase1.Audit{
   649  		Time:           keybase1.ToTime(m.G().Clock().Now()),
   650  		MaxMerkleSeqno: *lastMerkleRoot.Seqno(),
   651  		MaxChainSeqno:  maxChainSeqno,
   652  		MaxHiddenSeqno: maxHiddenSeqno,
   653  		// Note that the MaxMerkleProbe can be 0 in the case that there were
   654  		// pre-probes, but no post-probes.
   655  		MaxMerkleProbe: maxMerkleProbe,
   656  	}
   657  	history.Audits = append(history.Audits, audit)
   658  	history.PriorMerkleSeqno = headMerkleSeqno
   659  	history.Tails[maxChainSeqno] = chain[maxChainSeqno]
   660  	if maxHiddenSeqno != 0 {
   661  		if hiddenChain[maxHiddenSeqno].IsNil() {
   662  			m.Debug("hiddenChain on audit for team %s: %+v", id, hiddenChain)
   663  			return NewAuditError("Logic error while auditing %s: trying to save an audit with maxHiddenSeqno=%v, but the hiddenChain does not have the corresponding link.", id, maxHiddenSeqno)
   664  		}
   665  		history.HiddenTails[maxHiddenSeqno] = hiddenChain[maxHiddenSeqno]
   666  	}
   667  
   668  	// if the audit was successful, there will be nothing to retry next time
   669  	history.PreProbesToRetry = nil
   670  	history.PostProbesToRetry = nil
   671  
   672  	err = a.putToCache(m, id, lru, history)
   673  	if err != nil {
   674  		return err
   675  	}
   676  	return nil
   677  }
   678  
   679  func getMerkleSeqnosFromProbes(probeTuples []probeTuple) (seqnos []keybase1.Seqno) {
   680  	for _, tuple := range probeTuples {
   681  		seqnos = append(seqnos, tuple.merkle)
   682  	}
   683  	return seqnos
   684  }
   685  
   686  func (a *Auditor) newLRU(m libkb.MetaContext) {
   687  
   688  	a.lruMutex.Lock()
   689  	defer a.lruMutex.Unlock()
   690  
   691  	if a.lru != nil {
   692  		a.lru.Purge()
   693  	}
   694  
   695  	lru, err := lru.New(getAuditParams(m).LRUSize)
   696  	if err != nil {
   697  		panic(err)
   698  	}
   699  	a.lru = lru
   700  }
   701  
   702  func (a *Auditor) OnLogout(mctx libkb.MetaContext) error {
   703  	a.newLRU(mctx)
   704  	return nil
   705  }
   706  
   707  func (a *Auditor) OnDbNuke(mctx libkb.MetaContext) error {
   708  	a.newLRU(mctx)
   709  	return nil
   710  }