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