Skip to content

Modules must stop switching ambient identity: ASXBSENV is address-space-wide and 3.8j has no per-task ACEE #176

Description

@mgrossmann

Rewritten 2026-08-11. The first version proposed bracketing the ACEE inside httppcgi() around the LINK. That fix is wrong — or rather, insufficient in a way that matters. It closes the abend case and leaves the structural problem standing, and it would have sent the next reader down the same dead end. The corrected analysis is below; see also mvslovers/ftpd#64, which reached part of this earlier.

Surfaced while validating mvsmf#223 against the new storage reclaim. #154/#174 made a CGI abend survivable — the storage is released and the server keeps running. That is what made this visible: leftover ambient state used to be moot because the server was dead anyway.

The structural fact

racf_set_acee() does not set a task field. It writes ASXBSENV (ASXB+0xC8, libc370/src/racf/racsacee.c:15) — address-space scope. libc370 says so (racauth.c:116):

ASXBSENV is address-space-wide: writing the ACEE there and back is visible to every other TCB in the address space for the length of the call

And on MVS 3.8j there is no per-task alternative — the TCB has no ACEE anchor, and libc370 accordingly never references one (grep: zero hits repo-wide). This is the point that changes the conclusion: per-request identity is not merely unbracketed, it is being kept in the only field available, and that field belongs to the whole address space. There is nowhere better to put it.

Meanwhile httpd runs one TCB per session (ATTACH EP=CTHREAD), default MINTASK=3 (httpprm.c:200).

So: a save/restore of a shared field cannot be made correct by moving who performs it. Any fix that keeps switching is serialization under another name.

Two concrete defects that follow

1. Abend leaves the identity behind. mvsMF sets it in identity_middleware (mvsmf/src/mvsmf.c:51) and restores at quit: (:151). httppcgi() brackets the storage (reclaim(HTTP_CGI_SUBPOOL) at :72, unconditional since #174) but never touches the ACEE — zero hits for acee in src/httppcgi.c. Abend between the two lines and the client's ACEE stays in ASXBSENV, address-space-wide, indefinitely. The comment at httppcgi.c:55 already names the precondition: "nothing runs its @@ExitA, the worker TCB survives by design".

2. Interleaving between concurrent workers.

worker 1:  old1 = set(A)      ASXBSENV = A
worker 2:  old2 = set(B)      ASXBSENV = B,  old2 = A
worker 1:  set(old1)          ASXBSENV = old1   <-- request B still running
worker 2:  set(old2)          ASXBSENV = A      <-- a finished request's identity, left behind

Who is exposed, and how badly

racf_auth() serializes its own set/RACHECK/restore under lock(asxb) (libc370/src/racf/racauth.c:68,120,135,148), so any authorization decision made through it is race-free regardless of the above. That is the dividing line:

Consumer Pattern Consequence
httpd passes httpc->cred->acee explicitly (httpxauth.c:143) safe
ftpd explicit racf_auth() pre-check + an unserialized switch on the OPEN path entitlement safe, spurious OPEN failures — ftpd#64
mvsmf no racf_auth() call anywhere; plain fopen(dsname,"r") (dsapi.c:916) the ambient value is the authorization
httprexx / httplua never touch an ACEE (grep: 0 files each) inherit whatever is in ASXBSENV

ftpd#64 asked for exactly this enumeration — "any fopen not preceded by a racf_auth pre-check would rely on the racy OPEN check directly — higher concern; enumerate such sites". mvsMF is that site, across the whole dataset API. Nobody had gone through it.

The API for the right fix already exists, with zero consumers

httpxauth.c:137 already exports, through the HTTPX vector:

int http_check_auth(HTTPC *httpc, const char *classname,
                    const char *resource, int attr);

It calls racf_auth(httpc->cred->acee, ...) — ACEE in the parameter list, no ambient dependency, self-serializing — and normalizes SAF rc 4 to 0 so the published contract holds (0 permitted, 8+ refused, -1 unauthenticated). Its own comment notes: "no CGI calls http_check_auth()".

So the export landed and nothing adopted it; modules kept switching identity instead.

Proposed direction

Not "httpd performs the switch instead of the module" — that was the first draft and it leaves the shared field shared. Rather:

No module touches an ACEE. httpd answers "may this client access resource X with attribute Y."

With that, this issue does not get fixed so much as it becomes moot: nobody can leave an identity behind, because nobody sets one.

The trade-off, stated plainly, because it is a real one. Today the gate is implicit and universal — every fopen() goes through RACHECK, including the ones nobody thought about, just under an identity drawn from a shared field. Afterwards the gate is explicit and correct, but only where it was written. If a module opens under the server's identity, a forgotten http_check_auth() is not a weak gate, it is no gate. Whether to keep the ambient switch as a second, implicit net — accepting everything above as the price — is the decision this issue needs before code moves.

Work implied

  • mvsmf: adopt the pre-check model, drop the racf_set_acee() pair. Its own ticket; the largest of the three and the only one carrying real security weight. The fopen sites in dsapi.c are the work list.
  • ftpd#64: its "VERIFY FIRST — does OPEN consult TCBSENV" is answered, negatively. Noted there.
  • first consumer of http_check_auth(): per its comment, that is also the moment to verify the dbgf() path under DEBUG 1, which has never run from a CGI.

Scope of the evidence

Code reading, plus a live check of which build is running (/.dsrv?target=MOD shows no route->reclaim row, so the stand is post-#175). Neither the abend leftover nor the interleaving has been reproduced on a live system — that means deliberately abending a CGI, which is worth arranging rather than doing in passing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions