Keep FTPD out of its own module storage (#101) - #108
Merged
Conversation
FTPD is link-edited AC(1). Fetched from an APF-authorized library the job
step is authorized before program fetch runs, so MVS obtains the job pack
area in subpool 252 key 0 -- authorized code must not be patchable by
problem-key code. The STC runs problem state key 8, which makes every
store into the module's own storage a protection exception.
FTPD's first such store was the first statement of main(), so a start on
an APF-authorized FTPD.LINKLIB produced one line and nothing else:
12.34.30 STC 1124 IEF450I FTPD FTPD - ABEND S0C4 U0000 - TIME=12.34.30
No FTPD000I, no banner: the abend lands ahead of the first WTO and long
before clib_apf_setup(). That route is unaffected because SVC 244 sets
JSCBAUTH after the fetch and cannot relabel storage already allocated,
which is why a stock TK4-/TK5 never sees this.
- ftpd_server, the module-scope anchor, is gone. It was never read --
all ~25 "references" the issue counted are the ftpd_server_t type.
The server is published in grtapp1 instead, as a dump anchor.
- The log level and the whole trace ring state move out of file scope
in ftpd#log.c into struct ftpd_server, published through grtapp2 by
ftpd_log_anchor(). The GRT is heap, process-level and inherited by
every subtask (@@CRTSET copies crtgrt from the mother task), so a
session worker resolves what main published. The API is unchanged;
with no anchor yet the callers fall back to LOG_INFO and no tracing.
- The four translate tables and the asc2ebc/ebc2asc pointers become
const. They are only read today, but a codepage switch assigning one
of the pointers is the same bug -- it is what killed HTTPD in
http_xlate_init (mvslovers/httpd#197). The pointer shape is kept
deliberately: libc370 and httpd export the same names as
unsigned char *, and an array here would silently become garbage to
anything that declared them the usual way.
TRACE ON now enables tracing when the ring is not yet allocated;
ftpd_trace_init() resets the flag, so the old order reported FTPD080I
with tracing still off. Unreachable today -- initialize() allocates the
ring at startup.
tools/check-module-data.py (from mvslovers/ufsd#66, which found this)
rejects mutable file-scope data and function-local statics in AC(1)
modules, and runs as its own CI job.
Verified: clean build, 72/72 host assertions, guard clean and still
flagging the pre-fix source. At assembler level (cc370 -S) the store
L 2,=A(FTPD@SER)
ST 4,0(2)
is gone from ftpd.c, ftpd#log.c has no module-storage store left, and
the ftpd#xlt.c tables are address constants resolved at load time. Not
verified on MVS: key-0 protection does not exist off-target.
Fixes #101
Both routes end in an authorized task and neither says a word on its own: clib_apf_setup() returns 0 whether it authorized us or found us already authorized, and libc370's diagnostics on that path are commented out. That silence is what made #101 hard to place. The route is what decides whether FTPD's own module storage is writable -- an APF list entry authorizes the job step before program fetch, so the module is fetched into subpool 252 key 0, while SVC 244 arrives after the fetch and leaves it key 8 -- so it belongs in the startup log next to the version banner: FTPD008I AUTHORIZED BY LIBRARY (MODULE KEY 0) FTPD008I AUTHORIZED BY SVC (MODULE KEY 8) __isauth() (one TESTAUTH, no supervisor state) is asked before clib_apf_setup() runs, since that call destroys the distinction. The key is inferred from the route rather than measured, and the installation guide says so, so nobody reads it as an ISK result. Where authorization fails outright FTPD003W already carries that and FTPD008I stays out of the way -- one line about authorization per start, always. Follows UFSD007I (mvslovers/ufsd#66).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #101.
What happens
FTPD is link-edited
AC(1). Fetched from an APF-authorized library the job step is already authorized when program fetch runs, so MVS obtains the job pack area in subpool 252, key 0 — authorized code must not be patchable by problem-key code. The STC runs problem state key 8, so any store into the module's own storage is a protection exception.FTPD's first such store was the first statement of
main(), which is why the reported JESMSGLG is one line:No
FTPD000I, no banner — the abend lands ahead of the first WTO and long beforeclib_apf_setup(). The SVC 244 route is unaffected: it setsJSCBAUTHafter the fetch and cannot relabel storage program fetch already allocated, which is why a stock TK4-/TK5 never sees this.Confirmed at assembler level.
cc370 -Son the pre-fixsrc/ftpd.c:One correction to the issue
The issue reads "
ftpd_serverisexternininclude/ftpd.h:182with ~25 references across three files, so threading it through parameters is the expensive option." That count is offtpd_server_t, the type. The pointer itself is written once and never read:So the store that kills FTPD is a deletion, not a migration. The GRT anchor is still here — but it is load-bearing for the log/trace state, not for this.
Changes
ftpd_serverdeleted. The server is published ingrt->grtapp1as a dump anchor (no code reads it), matching httpd'sgrtapp1 = httpd.struct ftpd_server— amain()local, hence key 8 — published throughgrt->grtapp2byftpd_log_anchor(). The eleven-function API inftpd#log.his unchanged, so the 84ftpd_log()/ftpd_trace()call sites are untouched.ftpd#log.cgainsclibgrt.hand stays offftpd.h: it has no business with sockets, RACF or the thread manager.ftpd#xlt.ctables and pointers becomeconst. Read-only today, but a codepage switch assigningasc2ebcis the same bug — it is what killed HTTPD inhttp_xlate_init(stc_identity_restore() stores into module storage in key 8 -- S0C4 on P HTTPD with an APF-authorized LINKLIB httpd#197). The pointer shape is kept deliberately: libc370 (src/dyn75/asc2ebc.c) and httpd export the same names asunsigned char *, and an array here would silently become garbage to anything declaring them the usual way. Nothing in libc370 references them today — bothexterns in@@75gabn.c/@@75ghba.care unused — but the name is ecosystem-wide.tools/check-module-data.py(from Keep the DD and session counters out of module storage (#64) ufsd#66, which found this) rejects mutable file-scope data and function-local statics inAC(1)modules, as its own CI job. Docstring and failure message point at ftpd's anchor rather thanUFSD_STC.doc/installation.mdexplaining why the two authorization routes differ in storage key, a troubleshooting row for the bareS0C4, and the constraint inCLAUDE.md.Why the GRT works from a worker thread
The one assumption the design rests on, checked rather than assumed.
CTHREAD, the subtask driver inlibc370/asm/@@crt1.asm, calls@@CRTSET— not@@GRTSET.@@CRTSETwalks the PPA for the mother task's CLIBCRT (TCBOTC) and copies itscrtgrt, so the GRT is one per process and inherited transitively: main → thdmgr dispatch thread → session workers. Independently,cthread_create_ex()fails a thread create outright when__grtget()returns NULL, so every level that successfully creates threads demonstrably has it.Had this not held,
ftpd_trace()from a worker would have become a silent no-op — trace enabled,TRACE DUMPshowing only main's entries, nothing in error.Two things a reviewer will ask about the anchor
Who else owns
grtapp1/grtapp2. Both are free here, checked beyond ftpd's own sources:grep -rn grtappfinds nothing in ufsd'sclient/(libufs is linked into FTPD and is designed to be linked into arbitrary consumers, so it is the one that could plausibly have claimed a slot) and nothing in libc370. httpd uses both, but httpd is a different address space. A collision would have been silent in both directions — trace going dead, or UFS commands failing as if UFSD were down.The PPA lock.
__grtget()→__crtget()takes a shared PPA lock (lock(ppa,1)), softpd_log()andftpd_trace()now take one where they read a static before. That is not new work on these paths:stdoutis(*(stdio->___gtout()))→__grtget(), so everyprintf()— including the oneftpd_log()ends in — already takes it, as does everyerrnoaccess via__errno()→__crtget(). The marginal cost is one extra shared acquisition on a path that already had at least one, and the two calls that can skip the work are cheap either way: there are exactly 2ftpd_log(LOG_DEBUG, ...)sites and 10ftpd_trace()sites, all per-command or per-connection, none in a per-block transfer loop. Nothing here goes near the exclusive-vs-shared hazard the comment four lines above the diff describes: that one is aboutmain()calling__cibset()while a thread is starting, which is why the ordering it guards is untouched.FTPD008I — which route authorized the STC
Both routes end in an authorized task and neither says a word on its own:
clib_apf_setup()returns 0 whether it authorized us or found us already authorized, and libc370's diagnostics on that path are commented out. That silence is a good part of why this issue was hard to place — the route is exactly what decides whether the module's own storage is writable — so it now goes in the startup log next to the version banner:__isauth()(one TESTAUTH, no supervisor state) is asked beforeclib_apf_setup(), since that call destroys the distinction. The key is inferred from the route rather than measured — an authorized job step has its module fetched key 0, an unauthorized one key 8, and SVC 244 arrives too late to change either — and the installation guide says so, so nobody reads it as an ISK result.Where authorization fails outright,
FTPD003Walready carries that andFTPD008Istays out of the way: one line about authorization per start, always. This is FTPD's counterpart toUFSD007I(mvslovers/ufsd#66).One latent bug fixed on the way past
ftpd_trace_enable(1)settrace_onand then calledftpd_trace_init(), which resets it — soTRACE ONon an unallocated ring answeredFTPD080I TRACE ENABLEDwith tracing off. Unreachable today becauseinitialize()allocates the ring at startup; now allocate-then-enable, so it stays correct if that changes.Exposure audit
FTPD's own module: the guard is clean across all 15 sources, and it is only a text proxy — it cannot tell a
constpointer from a pointer to const. The real check iscc370 -S. Post-fix,ftpd.chas no X-var at all,ftpd#log.chas one=A(@V1)and it is a read oflevel_names[], andftpd#xlt.c'sASC2EBC/EBC2ASCareDC A(@Vn)address constants resolved at load time with no runtime store. In the fourftpd_xlat_*loops the only store isSTC 2,0(15,3)into the caller's buffer.libufs.a, linked into FTPD and not covered by either repo's CI: scanned with this same detector,
client/libufs.chas no module-resident data.libc370, also linked in and out of reach of the guard. Its module-resident statics are real, so rather than inherit ufsd#66's hand audit I checked which members are actually in
build/FTPD(ESD names, cp037):STRTOKstatic_oldcrt->crtstrtkwhen a CRT exists; the static is the no-CRT fallback@@ERRNOstatic_errnocrt->crterrnoMALLOC__lastsup#if USE_MEMMGR, which is off —__LASTSUPis not in the module@@PERM,@@STDOUTFILE __perm[3]stdoutresolves via@@GTOUT→grt->grtout, on the heap@@USEREX__userex[]atexit()ISBUF,TOUPPER__isbufR[],__toupR[]+ pointers@@75VECT,ASCTIME,PERROR,SETLOCAL,STRFTIME,OSXCALCSo nothing on FTPD's paths stores into libc370's module storage either.
Verification
makeclean (-Wall -Werror),make test-host72/72 assertions.git archiveof HEAD~1 — so the CI job would have caught this.cc370 -Sscan above, before and after./S FTPDwith an APF-authorizedFTPD.LINKLIBreachingFTPD001I. The path worth exercising there is the console trace —/F FTPD,TRACE ON, a transfer,/F FTPD,TRACE DUMP— which has no automated coverage in either direction and is also the empirical answer to the GRT-inheritance question above.Follow-up, not in scope here
tools/check-module-data.pynow exists twice, here and in ufsd, and httpd needs it too (mvslovers/httpd#197 is the same defect). Its natural home is mbt, where the reusable build workflow would give every consumer the job without a copy. Worth a separate issue against mbt.