You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Time-of-check to time-of-use (aka “TOCTTOU”, “TOCTOU”, or “TOC/TOU”) is a class of race condition bugs that can be observed in almost any language. Some basic examples in Go would be:
// BAD: Checks if the file is there before opening it instead of catching// os.ErrNotExist with errors.Is. File may be deleted between the call to// os.Stat and the call to os.Open._, err=os.Stat(fileName)
iferr==nil {
f, err=os.Open(fileName)
iferr==nil {
fatal()
}
}
And:
// BAD: lockedX could change between the first mu.Unlock() and the second// mu.Lock(). Either lock the whole method or use the atomic.CompareAndSwap*// family of functions.varxintmu.Lock()
x=lockedXmu.Unlock()
ifx==0 {
mu.Lock()
lockedX=newXmu.Unlock()
}
Detecting such bugs in general is probably an impossible task (although there seems to have been some work put into it), but I think that these two patterns could be detected reliably at least within one function.
I'm not certain it'll be free of false positives. There might be scenarios where this IS the desired behavior? Will have to check on an actual corpus to find out.
I don't think we can do much for the second pattern, as we'll have to allow arbitrary code between the first and second access of the guarded variable, which makes it far too likely to introduce false positives.
I'll give the linked to paper a read once time permits.
And, let's just say that I haven't filed this issue out of nowhere, heh. Although the code that motivated me to do so had the first part and the second part in neighbouring functions. Overall, it seems like at least half of all os.Stat calls that ignore the main result are at least a little bit suspicious.
Another one I found in real code several times, reduced:
typefrobulatorstruct {
// mu protects m.mu*sync.RWMutexmmap[int]int
}
func (f*frobulator) frobulate() (nint) {
f.mu.RLock()
iff.m==nil {
f.mu.RUnlock()
return0
}
// A silly attempt at “upgrading” a read lock into a write one.// Clearly a TOCTTOU.f.mu.RUnlock()
f.mu.Lock()
deferf.mu.Unlock()
f.m[123] =42returnf.m[456]
}
I'm pretty sure that about 95 % of all RUnlocks followed by a Lock are bugs, but perhaps there are weird cases where they aren't. The cases where the same data is manipulated are almost guaranteed to be bugs, imo.
Activity
dominikh commentedon Mar 16, 2021
We can check for the first pattern, but
I don't think we can do much for the second pattern, as we'll have to allow arbitrary code between the first and second access of the guarded variable, which makes it far too likely to introduce false positives.
I'll give the linked to paper a read once time permits.
ainar-g commentedon Mar 16, 2021
How about the Go Team :-) ?
https://github.com/golang/tools/blob/6d45e3d999c13387b54fe2b35939efb993dda8e3/internal/lsp/lsprpc/lsprpc.go#L328
A quick
grepshows thatgolang.org/x/toolshas a lot of suspiciousos.Statcalls, although most of them are in tests andgen.goscripts.Another one, from
modernc.org/ql, also found withgrep, although this one is a bit trickier:https://gitlab.com/cznic/ql/-/blob/08f6e2127ceae9291763c7db60c9c6ca683b3cef/helper/helper.go#L273
And, let's just say that I haven't filed this issue out of nowhere, heh. Although the code that motivated me to do so had the first part and the second part in neighbouring functions. Overall, it seems like at least half of all
os.Statcalls that ignore the main result are at least a little bit suspicious.ainar-g commentedon Jun 21, 2021
Another one I found in real code several times, reduced:
I'm pretty sure that about 95 % of all
RUnlocks followed by aLockare bugs, but perhaps there are weird cases where they aren't. The cases where the same data is manipulated are almost guaranteed to be bugs, imo.