Skip to content

Treat a malformed route line carrying an auth policy as a config error - #165

Merged
mgrossmann merged 1 commit into
mainfrom
issue-164-malformed-route-policy
Aug 9, 2026
Merged

Treat a malformed route line carrying an auth policy as a config error#165
mgrossmann merged 1 commit into
mainfrom
issue-164-malformed-route-policy

Conversation

@mgrossmann

Copy link
Copy Markdown
Contributor

Fixes #164

Follow-up to #161/#163, which established the rule: a route that carries an auth
policy is registered or the server does not start. That fixed the allocation
failures. This is the same fail-open reached from a typo.

The defect

parse_loc() rejected a line whose first token was an option instead of a path,
warned, and let the server start:

LOC=AUTH=BASIC RES=FACILITY:HTTPD.ADMIN     path forgotten

An unregistered route does not disappear — its requests are served under the
global LOGIN default. Under LOGIN NONE that line published exactly the
subtree it named, with one W message among the startup WTOs as the only trace.

parse_mod() was wrong in the other direction: it never inspected tok[0] at
all, so MOD=AUTH=BASIC /zosmf/* folded the option into an 8-character program
name (AUTH=BAS) and registered it against a derived *.auth=bas pattern — a
route built out of a typo that can never load. An empty MOD= returned silently
with no message whatsoever.

The change

Both parsers now reject the line and hand it to route_malformed(), which
classifies it through the machinery #163 added: the remaining tokens are parsed
only to decide whether the policy binds. A binding policy (RES=,
AUTH=FORM/BASIC) raises HTTPD419E and sets HTTPD_FLAG_CFGERR, so
http_config() stops the server with HTTPD420E before do_bind(). AUTH=NONE
or no policy stays a warning — the fallback can only be stricter. Nothing is
registered either way, so the RES= storage the classification parse allocated
is released again.

MOD= also gains HTTPD421W for the two cases that produced no message.

Testing

Unlike #161 this path needs no allocation failure, so it is reproducible — and
was reproduced on the live system. Three alternate Parmlib members
(S HTTPD,M=…, production HTTPPRM0 untouched, so S HTTPD was always the way
back), against the deployed and activated build:

1. The line from the issue — must not start.

HTTPD415W LOC= requires a path (e.g. LOC /admin/* AUTH=BASIC)
HTTPD419E LOC=AUTH=BASIC RES=FACILITY:HTTPD.ADMIN could not be registered -- its auth policy is lost
HTTPD420E Route authorization policy incomplete -- HTTPD will not start
HTTPD404E Errors occured processing (null)

Address space ended; port 8080 unreachable, i.e. never bound. Before this
change the same member started a server serving that subtree.

2. MOD=AUTH=BASIC /zosmf/* — must not start.

HTTPD421W MOD= requires a program name (e.g. MOD=MVSMF /zosmf/* AUTH=BASIC)
HTTPD413W ignoring unknown route option '/zosmf/*'
HTTPD419E MOD=AUTH=BASIC /zosmf/* could not be registered -- its auth policy is lost
HTTPD420E Route authorization policy incomplete -- HTTPD will not start

3. LOC=AUTH=NONE — must still start. The non-binding case, i.e. the check
that this does not simply make every malformed line fatal:

HTTPD415W LOC= requires a path (e.g. LOC /admin/* AUTH=BASIC)
...
HTTPD001I Server is READY

Port 8080 answered 200.

Then restarted on HTTPPRM0: all six production routes register, HTTPD001I Server is READY, /.dsrv and /zosmf/restfiles both 200, test members deleted,
production member verified byte-for-byte unchanged.

Also make modules / make test clean under -Wall -Werror, make test-host
4 suites / 63 assertions / 0 fail.

Docs

docs/configuration.md now lists all three routes to the refuse-to-start path —
allocation failure, untokenizable line, and this one — with the two typo examples
spelled out.

A LOC=/MOD= line whose positional token is missing and whose first
token is an AUTH=/RES= option was dropped with a warning, and the
server started:

    LOC=AUTH=BASIC RES=FACILITY:HTTPD.ADMIN

published exactly the subtree it named, because an unregistered route
is served under the global LOGIN default. Same fail-open as #161 but
reachable from a typo, no allocation failure needed.

MOD= was worse in a different direction: it never inspected tok[0], so
the option was folded into an 8-character program name ("AUTH=BAS")
and registered against a derived "*.auth=bas" pattern -- a route built
out of a typo that can never load. An empty MOD= said nothing at all.

Both now reject the line and classify what it carried through the
machinery #161 added: the remaining tokens are parsed only to decide
whether the policy binds, so a binding one raises HTTPD419E and stops
the server, while AUTH=NONE or no policy stays a warning. Nothing is
registered either way, so the RES= storage the classification parse
allocated is released again.

Fixes #164
@mgrossmann

Copy link
Copy Markdown
Contributor Author

Two notes from reviewing the test output.

Ordering is not forgiven, deliberately. LOC=RES=FACILITY:X /admin/* — path in second position — is rejected as malformed, classified binding, and stops the server; the path draws a HTTPD413W ignoring unknown route option on the way. The documented form is LOC=/url/prefix AUTH=mode RES=class:resource, and quietly reordering a line to guess what the administrator meant is the wrong instinct for a security gate. Same for the HTTPD413W on /zosmf/* visible in test 2 above: noisy, but it names every token that was thrown away, which is what you want when the server is about to refuse to start.

Out of scope, filed separately: both fatal tests end with HTTPD404E Errors occured processing (null)httpd.c:95 prints the CONFIG= PARM, which http_config() has ignored since the Parmlib migration ((void)member). Harmless here since HTTPD419E above it names the offending route, but it is dead text where the member name belongs. #166.

CI: build / build SUCCESS, mergeable clean.

@mgrossmann
mgrossmann merged commit 15fc518 into main Aug 9, 2026
1 check passed
@mgrossmann
mgrossmann deleted the issue-164-malformed-route-policy branch August 9, 2026 22:14
mgrossmann added a commit that referenced this pull request Aug 10, 2026
HTTPD404E printed the CONFIG= parm, which http_config() has ignored
since the Parmlib migration -- and which the shipped STC procedure
does not pass at all, so the one message naming what failed to process
always read "processing (null)". After #163 and #165 gave the parser
real reasons to refuse a startup, that message got a lot more
visible.

The configuration's real identity is the HTTPPRM DD, and the PROC
allocates it as &D(&M), so which member is in effect is a startup
choice an operator can genuinely need to confirm. parmlib_name()
resolves it from the DSAB -> TIOT entry -> JFCB, read-only pointer
chasing with no SVC and no allocation, falling back to the DD name if
any link is missing. It is reported before parsing, so a later error
is already attributed:

    HTTPD022I Configuration from SYS2.PARMLIB(HTTPPRM0)

HTTPD404E drops the operand accordingly.

CONFIG= is now answered rather than swallowed. Silently ignoring it is
worse than not accepting it -- an operator who passes it believes it
took effect, and migration.md still tells 3.x users to remove theirs,
so a leftover is exactly what this catches. HTTPD024W says it did
nothing and names the mechanism that does.

Fixes #166
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A LOC= line with the path omitted is dropped with a warning — the subtree it was protecting is then public

1 participant