Missing locking in thread
| Vulnerability potential | Low |
| DDoS potential | Low |
Synchronization is missing for given variable access
Impact
A variable shared between threads is accessed with no synchronization at all: no mutex, no atomic operation, no memory barrier. Concurrent writes, or a write concurrent with a read, race. Updates are lost, reads observe partially written or stale values, and composite invariants (for example a pointer paired with a length) are seen in inconsistent intermediate states. Because the accesses are not ordered, one thread may never observe another thread’s write at all: a spin loop on an un-synchronized flag can hang forever. In native code this is undefined behaviour and the optimizer may hoist the load out of the loop, turning a “should eventually stop” into an infinite loop or a crash.
Vulnerability potential
This issue has limited but real security relevance.
- Unsynchronized access to size/length/index fields can corrupt container bookkeeping and lead to out-of-bounds reads or writes in native code.
- A free performed on one thread racing with use on another is a classic use-after-free / double-free, directly exploitable for memory corruption.
- A loop that waits on an un-synchronized flag may never see the update and hang, contributing to denial of service.
Technical details
Synchronization serves two purposes: mutual exclusion (only one thread in the critical section) and visibility/ordering (one thread’s writes become visible to another in a well-defined order). Omitting it loses both. Modern CPUs and compilers freely reorder and cache memory operations on the assumption that no other thread is observing the same location; that assumption is exactly what is violated here.
Why a missing lock differs from an inconsistent one
With inconsistent locking, one path is wrong; with missing locking, no path synchronizes, so the race is essentially guaranteed under concurrency rather than being a narrow window. Even single-word accesses that look “atomic” on a given CPU are not guaranteed atomic by the language and may still be reordered.
Correct alternatives
Guard the variable with a mutex, or make it an atomic type
(std::atomic<T>, C11 _Atomic, Go’s sync/atomic, Java volatile/Atomic*)
with an explicit memory ordering. For a one-time signal between threads,
condition variables or channels provide the necessary ordering.
Catching the issue
ThreadSanitizer
-fsanitize=thread (C/C++) and -race (Go) instrument loads and stores and
report unsynchronized conflicting accesses with both stacks. This is the most
reliable dynamic detector.
Static analysis
Clang thread-safety annotations (GUARDED_BY) flag accesses to a guarded field
that hold no lock. Coverity and PVS-Studio detect shared variables touched
without consistent locking. Helgrind/DRD (Valgrind) detect missing
happens-before relations at runtime.
Review practice
Treat every variable reachable from more than one thread as requiring an explicit synchronization decision documented at the declaration; absence of a decision is the bug.
How to reproduce
Observe that the worker may spin forever (or only stop with optimizations off)
because the un-synchronized stop flag write is not guaranteed visible.
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
static int stop = 0; /* shared, no synchronization */
static void *worker(void *arg) {
(void)arg;
long iterations = 0;
while (!stop) /* compiler may hoist this load out of the loop */
iterations++;
printf("stopped after %ld iterations\n", iterations);
return NULL;
}
int main(void) {
pthread_t t;
pthread_create(&t, NULL, worker, NULL);
sleep(1);
stop = 1; /* may never become visible to the worker */
pthread_join(t, NULL);
return 0;
}