Skip to content

FTPD: cross-session ACEE race — OPEN-path racf_set_acee() switch is unserialized (ASXBSENV is address-space-wide) #64

Description

@mgrossmann

Summary

The per-session identity switch used around dataset OPEN
(racf_set_acee(sess->acee)fopenracf_set_acee(oldacee) in
ftpd#mvs.c) writes the address-space-wide ASXBSENV field, while FTP
sessions run as concurrent subtasks (separate TCBs) in one address space.
The OPEN-path switch is not serialized, so a concurrent worker's ASXBSENV
write can bleed into an in-flight OPEN's internal RACF check.

This is a pre-existing latent bug, independent of the ABEND-recovery work in the
linked PR (that PR only re-establishes the same field's normal resting value and
does not change the steady-state switch).

Evidence

  • libc370/src/racf/racsacee.c:34,53racf_set_acee() stores into
    ASXBSENV (PSA → ASCB+0x224 → ASXB+0x6C → ASXBSENV+0xC8); racf_get_acee()
    reads the same field. TCBSENV is never touched.
  • libc370/src/clib/@@ctcrtx.c:87 — workers are created with
    ATTACH EP=CTHREAD; each session is a distinct TCB in the same AS
    (@@cminit.c: pool pre-spawns mintask, grows to maxtask = max_sessions).
  • libc370/src/racf/racauth.c:68,120,135,148racf_auth() does serialize
    its own set/RACHECK/restore under lock(asxb)/unlock(asxb), so the
    primary authorization decision (FTPD's check_dataset_access() pre-check)
    is race-free. The OPEN-path switch in ftpd#mvs.c does not take that lock.

Severity

Hardening / defense-in-depth. Entitlement is gated race-free by the primary
racf_auth() pre-check under the correct identity, so for a pre-checked open the
OPEN-race yields a spurious OPEN failure (availability), not privilege
escalation. (Audit note: any fopen not preceded by a racf_auth pre-check
would rely on the racy OPEN check directly — higher concern; enumerate such
sites.)

Remedy — VERIFY FIRST, do not assume

The fix direction is not decided. First step:

  1. Verify whether MVS 3.8j OPEN + RAKF actually consult TCBSENV.
    "RACROUTE honors TCBSENV over ASXBSENV" is z/OS semantics; under RAKF it is
    the security layer that decides whether TCBSENV is consulted at all, and the
    MVS 3.8j OPEN path must actually propagate it. If it is honored, a per-task
    TCBSENV switch removes the race with no serialization cost.
  2. Alternative: serialize the set_acee/OPEN/restore sequence under the ASXB
    lock. Note the catch: racf_auth() proves the lock pattern for the fast
    RACHECK, but OPEN is I/O-bound — holding an AS-wide ENQ across every
    dataset OPEN would serialize all opens AS-wide and risks a throughput
    collapse / long holds. This is precisely why the current code locks only the
    RACHECK, not the OPEN.

Whichever path is chosen, ABEND recovery's ASXBSENV reset (linked PR) should be
revisited to match.

Note

Under a multi-CP Hercules configuration the interleaving becomes true
parallelism, which additionally removes any store-ordering guarantee on the
shared ASXBSENV field.

Security invariant dependency

The linked PR documents a SECURITY INVARIANT in doc/FTPD_RAKF_SETUP.md:
FTPD/USER must remain least-privilege so recovery's ASXBSENV reset stays
fail-closed. A fix here should preserve or supersede that invariant.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions