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 }