home intel cve-2025-14341-divvydrive-rce-mass-assignment-flood
CVE Analysis 2026-05-07 · 9 min read

CVE-2025-14341: DivvyDrive Mass Assignment + Resource Exhaustion RCE

DivvyDrive's sync engine accepts attacker-controlled attribute names without a whitelist and allocates unbounded buffers, enabling remote code execution via heap corruption and resource exhaustion.

#object-attribute-manipulation#resource-exhaustion#denial-of-service#remote-code-execution#input-validation-bypass
Technical mode — for security professionals
▶ Attack flow — CVE-2025-14341 · Remote Code Execution
ATTACKERRemote / unauthREMOTE CODE EXECCVE-2025-14341Cross-platform · HIGHCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

CVE-2025-14341 is a compound vulnerability in DivvyDrive (versions 4.8.2.19 through 4.8.3.1) combining two distinct weaknesses: CWE-915 (Improperly Controlled Modification of Dynamically-Determined Object Attributes — the "mass assignment" class) and CWE-770 (Allocation of Resources Without Limits or Throttling). Together they provide a reliable path from an unauthenticated network position to remote code execution on any platform DivvyDrive supports. CVSS 8.3 (HIGH) reflects network reachability, no authentication required, and high impact to integrity.

The vulnerability class will be immediately familiar to anyone who has audited Rails or Django APIs circa 2012-2016, but the implementation here is in a native sync daemon — making exploitation consequences significantly worse than a typical web mass-assignment bug. Heap corruption arises from the allocation side, not just privilege escalation.

Root cause: The DivvyDrive sync daemon deserialises peer-supplied JSON attribute maps directly into internal drive-object structs without a field whitelist, and then allocates accumulation buffers sized from attacker-controlled length fields with no upper bound, allowing heap overflow and object-attribute injection in a single protocol exchange.

Affected Component

The vulnerable code lives in the DivvyDrive cross-platform sync daemon (divvyd / DivvyDriveService.exe on Windows). The daemon exposes a local loopback API for the UI shell and a peer-to-peer sync protocol over TCP for device-to-device transfers. Both surfaces are reachable; the P2P protocol requires no shared secret by default in affected builds.

Relevant source paths inferred from binary symbol recovery:

  • sync/object_store.c — object attribute merging
  • net/transfer_session.c — chunked transfer allocation
  • proto/json_dispatch.c — JSON field-to-struct dispatch

Root Cause Analysis

The sync protocol represents shared drive objects as JSON dictionaries. When a remote peer sends an OBJECT_UPDATE message, object_merge_attrs() iterates the incoming key-value pairs and writes them into the live drive_object_t struct using a string-keyed lookup that falls back to dynamic attribute creation — the mass-assignment sink.


/* sync/object_store.c — DivvyDrive 4.8.2.x */

typedef struct drive_object {
    /* +0x00 */ uint64_t   obj_id;
    /* +0x08 */ uint32_t   obj_type;       // FILE=1, DIR=2, SYMLINK=3
    /* +0x0C */ uint32_t   flags;          // READONLY, SHARED, ENCRYPTED …
    /* +0x10 */ char      *display_name;
    /* +0x18 */ char      *remote_path;
    /* +0x20 */ uint64_t   size_bytes;
    /* +0x28 */ uint64_t   mtime;
    /* +0x30 */ uint32_t   chunk_count;
    /* +0x34 */ uint32_t   acl_mask;       // BUG: writable via mass-assign
    /* +0x38 */ attr_bag_t *dyn_attrs;     // dynamic overflow bucket
    /* +0x40 */ transfer_ctx_t *xfer;
} drive_object_t;

/* Called for every key-value pair in a peer OBJECT_UPDATE message */
int object_merge_attrs(drive_object_t *obj, json_obj_t *patch) {
    json_iter_t it;
    json_iter_init(&it, patch);

    const char *key;
    json_val_t  val;

    while (json_iter_next(&it, &key, &val)) {
        /* BUG: no whitelist — attacker can name any struct field.
         * Known-field fast path writes directly into the struct.
         * 'acl_mask', 'flags', 'obj_type' are all reachable here. */
        if (!object_set_known_field(obj, key, &val)) {
            // fallback: store in dynamic bag (unbounded)
            attr_bag_insert(obj->dyn_attrs, key, &val); // BUG: no quota
        }
    }
    return 0; // always succeeds — no error propagation
}

object_set_known_field() uses a plain strcmp dispatch table. Any key that matches a known field name — including "acl_mask", "flags", and "obj_type" — is written without privilege checking. An attacker can flip READONLY off, set acl_mask to 0xFFFFFFFF, or downgrade obj_type to SYMLINK and redirect path resolution.

The second weakness lives in the chunked transfer allocator called immediately after object merging when the patch includes a new size_bytes:


/* net/transfer_session.c */

#define CHUNK_SIZE  0x40000   // 256 KB fixed read stride

transfer_ctx_t *transfer_alloc_recv(drive_object_t *obj,
                                    uint64_t        advertised_size) {
    transfer_ctx_t *ctx = calloc(1, sizeof(transfer_ctx_t));
    ctx->obj            = obj;
    ctx->total          = advertised_size;         // BUG: attacker-controlled, no cap

    /* Allocate reassembly buffer for entire object up front */
    ctx->buf            = malloc(advertised_size); // BUG: no upper bound — OOM / integer wrap
    ctx->written        = 0;

    return ctx;
}

int transfer_recv_chunk(transfer_ctx_t *ctx, peer_conn_t *conn) {
    size_t stride = CHUNK_SIZE;  // always 256 KB

    /* BUG: stride is not clamped to (ctx->total - ctx->written).
     * If advertised_size was crafted small and peer sends more data,
     * or if integer wrap makes ctx->buf small, this overflows. */
    peer_read(conn, ctx->buf + ctx->written, &stride); // BUG: heap overflow
    ctx->written += stride;

    if (ctx->written >= ctx->total)
        transfer_finalize(ctx);

    return 0;
}

Memory Layout


HEAP STATE — transfer_alloc_recv() with advertised_size = 0x1FFFFFFC0
(integer truncation on 32-bit target: 0x1FFFFFFC0 & 0xFFFFFFFF = 0xFFFFFFC0)

BEFORE transfer_recv_chunk():
[ chunk A: drive_object_t        0x148 bytes  ]
[ chunk B: transfer_ctx_t        0x60  bytes  ]
[ chunk C: ctx->buf malloc(0xFFFFFFC0)         ]  <- wraps; actual alloc ≈ 192 bytes on some
                                                    allocators (glibc bin reuse)
[ chunk D: attr_bag_t            0x80  bytes  ]  <- adjacant, targeted

AFTER first transfer_recv_chunk() (stride = 0x40000, buf ≈ 0xC0 bytes):
[ chunk C: ctx->buf              0xC0  bytes  ]
[ OVERFLOW → chunk D: attr_bag_t            ]
  attr_bag_t+0x00 : 0x4141414141414141  (attacker data — fake next ptr)
  attr_bag_t+0x08 : 0x4242424242424242  (fake capacity — controls realloc)
  attr_bag_t+0x10 : ... (key/value pairs overwritten)

Subsequent attr_bag_insert() → realloc(fake_ptr, fake_capacity)
→ allocator operates on attacker-controlled pointer → arbitrary write primitive.

Exploitation Mechanics


EXPLOIT CHAIN — CVE-2025-14341 (unauthenticated P2P surface):

1. DISCOVERY
   Connect to DivvyDrive P2P sync port (default TCP 47200).
   Send valid HELLO handshake — no credentials required in affected builds.

2. MASS ASSIGNMENT — disable ACL enforcement
   Send OBJECT_UPDATE for an existing shared object with patch:
     { "acl_mask": 4294967295, "flags": 0, "obj_type": 1 }
   object_merge_attrs() writes 0xFFFFFFFF into obj->acl_mask via known-field
   dispatch. Subsequent ACL checks on this object pass unconditionally.

3. SIZE CONFUSION — trigger integer truncation
   Send OBJECT_UPDATE with:
     { "size_bytes": 8589934528 }   // 0x200000100 — low 32 bits = 0x100
   transfer_alloc_recv() called: malloc(0x100) returns small buffer,
   ctx->total stores full 64-bit value (or truncated — target-ABI dependent).

4. HEAP GROOMING
   Issue 12x rapid OBJECT_UPDATE messages for dummy objects to shape
   the heap so that an attr_bag_t lands immediately after ctx->buf.

5. OVERFLOW — corrupt attr_bag_t
   Send CHUNK_DATA payload of 0x40000 bytes.
   transfer_recv_chunk() reads full stride into 0x100-byte buffer,
   overwriting the adjacent attr_bag_t header with attacker data.
   Craft overflow bytes:
     +0x00: ptr  = &fake_chunk - 0x10   (points into attacker-controlled data)
     +0x08: cap  = 0x1                  (forces immediate realloc on next insert)

6. ARBITRARY WRITE — trigger realloc
   Send second OBJECT_UPDATE with any new dynamic attribute key.
   attr_bag_insert() sees cap exhausted, calls realloc(fake_ptr, new_cap).
   Allocator interprets fake_ptr as a heap chunk; returns attacker-chosen
   address as the new bag buffer.

7. FUNCTION POINTER OVERWRITE
   Write shellcode address into transfer_finalize_cb stored in transfer_ctx_t
   via the now-redirected attr_bag write.

8. CODE EXECUTION
   Send final chunk that sets ctx->written >= ctx->total.
   transfer_finalize(ctx) invokes ctx->finalize_cb → shellcode.

Patch Analysis

The fix in version 4.8.3.2 addresses both primitives independently:


/* BEFORE (vulnerable) — object_merge_attrs() */
while (json_iter_next(&it, &key, &val)) {
    if (!object_set_known_field(obj, key, &val)) {
        attr_bag_insert(obj->dyn_attrs, key, &val); // no quota
    }
}

/* AFTER (patched 4.8.3.2) — object_merge_attrs() */
static const char *FIELD_WHITELIST[] = {
    "display_name", "remote_path", "size_bytes", "mtime", NULL
};

while (json_iter_next(&it, &key, &val)) {
    if (!is_whitelisted(key, FIELD_WHITELIST)) {
        log_warn("object_merge_attrs: rejected field '%s' from peer", key);
        continue;  // drop — no fallback to known-field write
    }
    if (!object_set_known_field(obj, key, &val)) {
        if (obj->dyn_attrs->count >= ATTR_BAG_MAX_ENTRIES) { // quota: 64
            return -ENOSPC;
        }
        attr_bag_insert(obj->dyn_attrs, key, &val);
    }
}

/* BEFORE (vulnerable) — transfer_alloc_recv() + transfer_recv_chunk() */
ctx->buf = malloc(advertised_size);          // no cap, no overflow check
// ...
size_t stride = CHUNK_SIZE;
peer_read(conn, ctx->buf + ctx->written, &stride); // stride not clamped

/* AFTER (patched 4.8.3.2) */
#define TRANSFER_MAX_SIZE  (1ULL * 1024 * 1024 * 1024)  // 1 GB hard cap

if (advertised_size > TRANSFER_MAX_SIZE || advertised_size == 0)
    return NULL;  // reject before any allocation

ctx->buf = malloc(advertised_size);
if (!ctx->buf) return NULL;

// ...
size_t remaining = ctx->total - ctx->written;
size_t stride    = (remaining < CHUNK_SIZE) ? remaining : CHUNK_SIZE;
peer_read(conn, ctx->buf + ctx->written, &stride); // clamped to remaining
ctx->written += stride;

Note that the whitelist approach for the mass-assignment fix is correct but incomplete in the patch as shipped — object_set_known_field() still handles the whitelisted size_bytes field without validating against an already-open transfer session, which could re-open a TOCTOU window. Researchers auditing 4.8.3.2 should verify whether a concurrent OBJECT_UPDATE { "size_bytes": X } mid-transfer can resize the live ctx->total.

Detection and Indicators

Network signatures: Look for OBJECT_UPDATE messages on TCP/47200 containing the keys acl_mask, flags, or obj_type — none of these should appear in legitimate peer traffic. A Suricata rule skeleton:


alert tcp any any -> any 47200 (
    msg:"CVE-2025-14341 DivvyDrive mass-assign attempt";
    content:"OBJECT_UPDATE"; content:"acl_mask"; distance:0;
    sid:2025143410; rev:1;
)

alert tcp any any -> any 47200 (
    msg:"CVE-2025-14341 DivvyDrive oversized transfer";
    content:"size_bytes";
    byte_test:8,>,1073741824,0,relative,big-endian;
    sid:2025143411; rev:1;
)

Host indicators:

  • divvyd process RSS growing unboundedly without active user transfers
  • Crash dumps in %APPDATA%\DivvyDrive\logs\ or ~/.divvydrive/logs/ referencing transfer_recv_chunk or attr_bag_insert in the backtrace
  • acl_mask or flags keys present in object_store.db JSON blobs

Remediation

  • Update immediately to DivvyDrive 4.8.3.2 or later. No workaround is viable for the unauthenticated P2P surface short of firewall isolation.
  • If immediate upgrade is not possible, firewall TCP/47200 (and the local loopback API port) to trusted hosts only.
  • Disable P2P sync in DivvyDrive settings (Preferences → Sync → Disable peer-to-peer transfers) to eliminate the unauthenticated attack surface entirely until patched.
  • Audit all object_store.db files for unexpected acl_mask or flags values that may indicate prior exploitation against your fleet.
  • Vendors implementing similar JSON-to-struct dispatch: enforce an explicit allowlist at the protocol boundary, never at the struct-write level, and always cap allocation sizes with both a logical maximum and an integer-overflow-safe check (size > MAX || size == 0) before any malloc call.
CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →