# Race Condition in Multiprocessing Shared Counter via Unsynchronized File Lock

Language: Python
Severity: High
CWE: CWE-362

## Source
9

## Flow
9-11

## Sink
11

## Vulnerable Code
```python
import multiprocessing, time, os
def update_sensor_telemetry(sensor_id, delta, log_path='/tmp/telemetry.dat'):
    if not os.path.exists(log_path):
        with open(log_path, 'w') as f: f.write('0')
    with open(log_path, 'r') as f:
        current_val = int(f.read().strip())
    time.sleep(0.001)
    new_val = current_val + delta
    with open(log_path, 'w') as f:
        f.write(str(new_val))
    return new_val
def spawn_iot_workers(num_devices=10):
    procs = [multiprocessing.Process(target=update_sensor_telemetry, args=(i, 1)) for i in range(num_devices)]
    [p.start() for p in procs]
    [p.join() for p in procs]
    with open('/tmp/telemetry.dat') as f: return int(f.read())
```

## Explanation

Multiple processes concurrently read the shared telemetry file (lines 5-6), compute a new value (line 8), and write it back (lines 9-10) without any file locking or synchronization primitive. The artificial sleep at line 7 widens the race window, allowing multiple processes to read the same stale value before any of them writes the updated result, causing lost updates (e.g., 10 increments may yield a final value of 1 or 2 instead of 10).

## Remediation

The fix wraps the entire read-modify-write sequence inside an exclusive file lock (fcntl.flock with LOCK_EX), ensuring only one process at a time can read, compute, and write the counter value. The file is opened in 'a+' mode so it is created if absent and can be both read and written, and the lock is always released in a finally block. The artificial sleep was removed as it served only to widen the race window, and file initialization is moved to spawn_iot_workers before any workers start.

## Secure Code
```python
import multiprocessing, time, os, fcntl

def update_sensor_telemetry(sensor_id, delta, log_path='/tmp/telemetry.dat'):
    with open(log_path, 'a+') as f:
        fcntl.flock(f, fcntl.LOCK_EX)
        try:
            f.seek(0)
            content = f.read().strip()
            current_val = int(content) if content else 0
            new_val = current_val + delta
            f.seek(0)
            f.truncate()
            f.write(str(new_val))
            f.flush()
        finally:
            fcntl.flock(f, fcntl.LOCK_UN)
    return new_val

def spawn_iot_workers(num_devices=10):
    log_path = '/tmp/telemetry.dat'
    with open(log_path, 'w') as f:
        f.write('0')
    procs = [multiprocessing.Process(target=update_sensor_telemetry, args=(i, 1, log_path)) for i in range(num_devices)]
    [p.start() for p in procs]
    [p.join() for p in procs]
    with open(log_path) as f:
        return int(f.read())
```
