Unrestricted Externally Accessible Lock

Description

Unrestricted Externally Accessible Lock is a vulnerability where a product properly checks for the existence of a lock, but the lock can be externally controlled or influenced by an actor that is not a valid part of the product's execution. This prevents the product from acting on associated resources or performing lock-controlled behaviors. When locks can be held indefinitely by external parties, denial of service becomes possible. This affects any resources or behaviors regulated by lock presence, including exclusive locks, mutexes, file locks, or shared resources functioning as locks.

Risk

Externally accessible locks enable denial-of-service attacks where attackers can prevent legitimate operations by holding locks indefinitely. If lock files are created in world-writable directories with predictable names, attackers can pre-create them. Programs waiting for mutex releases may hang forever if an attacker process holds the mutex. Critical security operations like logging or policy enforcement can be bypassed by locking required files. The impact extends beyond availability—security controls may be circumvented when their operations depend on inaccessible locked resources.

Solution

Leverage access control features offered by the locking functionality to restrict who can acquire locks. Use unpredictable lock names or identifiers when feasible. Create lock files in directories with restrictive permissions. Implement timeouts on lock acquisition to prevent indefinite waiting. Consider non-blocking synchronization methods that fail gracefully when locks are unavailable. Validate lock ownership before trusting lock state. Use operating system features that prevent external interference with locks. Design systems to degrade gracefully when locks cannot be acquired.

Common Consequences

ImpactDetails
AvailabilityScope: Availability

DoS: Resource Consumption (Other) - When an attacker can control a lock, the program may wait indefinitely until the attacker releases the lock, causing a denial of service to other users of the program.

Example Code

Vulnerable Code

<?php
// Vulnerable: Lock file in world-writable directory
function vulnerableWriteToLog($message) {
    $logfile = fopen("/tmp/app.log", "a");

    // Vulnerable: Blocks indefinitely waiting for lock
    // Attacker can hold lock on /tmp/app.log forever
    if (flock($logfile, LOCK_EX)) {
        fwrite($logfile, $message);
        flock($logfile, LOCK_UN);
    }

    fclose($logfile);
}

// Attacker script:
// $f = fopen("/tmp/app.log", "a");
// flock($f, LOCK_EX);
// sleep(PHP_INT_MAX);  // Hold lock forever
?>
# Vulnerable: Predictable lock file name
import fcntl
import os

def vulnerable_critical_operation():
    # Vulnerable: Predictable lock file in /tmp
    lock_file = open("/tmp/myapp.lock", "w")

    # Vulnerable: Blocks indefinitely
    fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)

    try:
        perform_critical_operation()
    finally:
        fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
        lock_file.close()

# Attacker can pre-create /tmp/myapp.lock and hold exclusive lock
// Vulnerable: Mutex with no timeout
#include <pthread.h>

pthread_mutex_t shared_mutex = PTHREAD_MUTEX_INITIALIZER;

void vulnerable_access_resource() {
    // Vulnerable: Blocks forever if mutex held by malicious process
    pthread_mutex_lock(&shared_mutex);

    access_shared_resource();

    pthread_mutex_unlock(&shared_mutex);
}

// If shared_mutex is in shared memory accessible to attackers,
// they can lock it and never release
// Vulnerable: File lock with no timeout
import java.io.*;
import java.nio.channels.*;

public class VulnerableLocking {

    public void writeData(String data) throws IOException {
        try (FileOutputStream fos = new FileOutputStream("/tmp/data.txt");
             FileChannel channel = fos.getChannel()) {

            // Vulnerable: Blocks indefinitely waiting for lock
            FileLock lock = channel.lock();  // No timeout!

            try {
                fos.write(data.getBytes());
            } finally {
                lock.release();
            }
        }
    }
}

Fixed Code

<?php
// Fixed: Lock file in protected directory with timeout
function secureWriteToLog($message) {
    // Fixed: Use protected directory
    $lockDir = "/var/run/myapp/";
    if (!is_dir($lockDir)) {
        mkdir($lockDir, 0700, true);
    }

    $logfile = fopen($lockDir . "app.log", "a");

    // Fixed: Use non-blocking lock with timeout
    $timeout = 5;  // 5 second timeout
    $start = time();

    while (time() - $start < $timeout) {
        if (flock($logfile, LOCK_EX | LOCK_NB)) {
            fwrite($logfile, $message);
            flock($logfile, LOCK_UN);
            fclose($logfile);
            return true;
        }
        usleep(100000);  // Wait 100ms before retry
    }

    fclose($logfile);
    error_log("Failed to acquire lock within timeout");
    return false;
}
?>
# Fixed: Protected lock file with timeout
import fcntl
import os
import time
import errno

def secure_critical_operation(timeout_seconds=5):
    # Fixed: Use protected directory with restrictive permissions
    lock_dir = "/var/run/myapp"
    os.makedirs(lock_dir, mode=0o700, exist_ok=True)

    lock_path = os.path.join(lock_dir, "operation.lock")
    lock_file = open(lock_path, "w")

    # Fixed: Non-blocking lock with timeout
    start_time = time.time()
    while True:
        try:
            fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
            break  # Lock acquired
        except IOError as e:
            if e.errno != errno.EWOULDBLOCK:
                raise

            if time.time() - start_time > timeout_seconds:
                lock_file.close()
                raise TimeoutError("Could not acquire lock within timeout")

            time.sleep(0.1)  # Wait before retry

    try:
        perform_critical_operation()
    finally:
        fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
        lock_file.close()

# Alternative: Use context manager
import contextlib

@contextlib.contextmanager
def acquire_lock_with_timeout(lock_path, timeout=5):
    lock_file = open(lock_path, "w")
    start = time.time()

    while True:
        try:
            fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
            break
        except IOError:
            if time.time() - start > timeout:
                lock_file.close()
                raise TimeoutError("Lock acquisition timeout")
            time.sleep(0.1)

    try:
        yield lock_file
    finally:
        fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
        lock_file.close()
// Fixed: Mutex with timeout
#include <pthread.h>
#include <time.h>
#include <errno.h>

pthread_mutex_t shared_mutex = PTHREAD_MUTEX_INITIALIZER;

int secure_access_resource(int timeout_ms) {
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    ts.tv_sec += timeout_ms / 1000;
    ts.tv_nsec += (timeout_ms % 1000) * 1000000;

    // Fixed: Use timed lock
    int result = pthread_mutex_timedlock(&shared_mutex, &ts);

    if (result == ETIMEDOUT) {
        // Fixed: Handle timeout gracefully
        log_error("Failed to acquire mutex within timeout");
        return -1;
    }

    if (result != 0) {
        log_error("Mutex lock failed: %d", result);
        return -1;
    }

    access_shared_resource();

    pthread_mutex_unlock(&shared_mutex);
    return 0;
}

// Alternative: Try-lock pattern
int secure_access_with_trylock() {
    int retries = 50;  // 5 seconds with 100ms intervals

    while (retries > 0) {
        if (pthread_mutex_trylock(&shared_mutex) == 0) {
            access_shared_resource();
            pthread_mutex_unlock(&shared_mutex);
            return 0;
        }
        usleep(100000);  // 100ms
        retries--;
    }

    log_error("Could not acquire mutex after retries");
    return -1;
}
// Fixed: File lock with timeout
import java.io.*;
import java.nio.channels.*;
import java.util.concurrent.*;

public class SecureLocking {

    public boolean writeData(String data, long timeoutMs) throws IOException {
        File lockDir = new File("/var/run/myapp");
        lockDir.mkdirs();

        // Fixed: Set restrictive permissions (Java 7+)
        lockDir.setReadable(false, false);
        lockDir.setReadable(true, true);
        lockDir.setWritable(false, false);
        lockDir.setWritable(true, true);
        lockDir.setExecutable(false, false);
        lockDir.setExecutable(true, true);

        File dataFile = new File(lockDir, "data.txt");

        try (FileOutputStream fos = new FileOutputStream(dataFile);
             FileChannel channel = fos.getChannel()) {

            // Fixed: Try lock with timeout
            long deadline = System.currentTimeMillis() + timeoutMs;

            while (System.currentTimeMillis() < deadline) {
                FileLock lock = channel.tryLock();

                if (lock != null) {
                    try {
                        fos.write(data.getBytes());
                        return true;
                    } finally {
                        lock.release();
                    }
                }

                // Wait before retry
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return false;
                }
            }

            // Fixed: Timeout occurred
            return false;
        }
    }
}

CVE Examples

  • CVE-2001-0682 — Program cannot execute when attacker obtains a mutex.
  • CVE-2002-1914 — Program blocked when attacker locks critical output file.
  • CVE-2002-0051 — Critical file opened with exclusive read access, preventing security policy application.
  • CVE-2000-0338 — Predictable lock filenames allow pre-creation by attackers.
  • CVE-2002-1869 — Logging bypassed via exclusive file lock access.

References

  1. MITRE Corporation. "CWE-412: Unrestricted Externally Accessible Lock." https://cwe.mitre.org/data/definitions/412.html
  2. CAPEC-25. "Forced Deadlock." https://capec.mitre.org/data/definitions/25.html