{"title":"Race Condition in Multiprocessing Shared Counter via Unsynchronized File Lock","language":"Python","severity":"High","cwe":"CWE-362","source_lines":[9],"flow_lines":[9,11],"sink_lines":[11],"vulnerable_code":"import multiprocessing, time, os\ndef update_sensor_telemetry(sensor_id, delta, log_path='/tmp/telemetry.dat'):\n    if not os.path.exists(log_path):\n        with open(log_path, 'w') as f: f.write('0')\n    with open(log_path, 'r') as f:\n        current_val = int(f.read().strip())\n    time.sleep(0.001)\n    new_val = current_val + delta\n    with open(log_path, 'w') as f:\n        f.write(str(new_val))\n    return new_val\ndef spawn_iot_workers(num_devices=10):\n    procs = [multiprocessing.Process(target=update_sensor_telemetry, args=(i, 1)) for i in range(num_devices)]\n    [p.start() for p in procs]\n    [p.join() for p in procs]\n    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":"import multiprocessing, time, os, fcntl\n\ndef update_sensor_telemetry(sensor_id, delta, log_path='/tmp/telemetry.dat'):\n    with open(log_path, 'a+') as f:\n        fcntl.flock(f, fcntl.LOCK_EX)\n        try:\n            f.seek(0)\n            content = f.read().strip()\n            current_val = int(content) if content else 0\n            new_val = current_val + delta\n            f.seek(0)\n            f.truncate()\n            f.write(str(new_val))\n            f.flush()\n        finally:\n            fcntl.flock(f, fcntl.LOCK_UN)\n    return new_val\n\ndef spawn_iot_workers(num_devices=10):\n    log_path = '/tmp/telemetry.dat'\n    with open(log_path, 'w') as f:\n        f.write('0')\n    procs = [multiprocessing.Process(target=update_sensor_telemetry, args=(i, 1, log_path)) for i in range(num_devices)]\n    [p.start() for p in procs]\n    [p.join() for p in procs]\n    with open(log_path) as f:\n        return int(f.read())"}