Skip to content

JES spool access has no authorization model: JESOWNER is a client-set display filter, not a control #90

Description

@mgrossmann

Feature request, not a patch — the thing that is missing needs a design, and
part of it does not exist on the platform at all.

Where we are

Measured on mvsdev as MVSCE02 (group USER, no OPERATIONS), against
a5b1256:

> SITE FILETYPE=JES
> LIST                   ->   1 line (header only) — own jobs, correct default
> SITE JESOWNER=*        -> 200 SITE command was accepted
> LIST                   -> 160 lines, 159 of them jobs owned by someone else
> RETR STC00563.1        -> 250, 1028 bytes
                            SYSTEM.FTPD.STC00563.D0000002.JESMSGLG

An unprivileged FTP user read the JES2 job log of the FTPD started task. The
sequence is one SITE command; no error, no log entry, no denial.

Two code facts behind it:

  • sess->jes_owner is the only scoping there is, it defaults to the session
    user (good), and the client sets it (not good). job_matches_owner()
    (ftpd#jes.c:372) treats * as "match all".
  • find_job() (ftpd#jes.c:628) resolves a JOBID with FILTER_JOBID and no
    owner scoping whatsoever. It backs both the spool-retrieve path and the
    delete path, so the filter is not consulted at the point of access at all —
    only when building a listing.

Not tested, because it is destructive: whether DELE <jobid> can purge
another user's job. After find_job() the only remaining gate is whatever
jescanj() itself refuses (the CANJ_ICAN branch suggests JES2 declines
some cases). Worth establishing before this is designed.

Why this is a feature and not a bug fix

There is nothing to delegate to. RAKF has no JESSPOOL class — the profile set
on a stock system is DASDVOL, DATASET, FACILITY, TAPEVOL, TERMINAL — and
MVS 3.8j/JES2 do not protect spool by owner. So FTPD is not failing to call an
existing check; it is the only component in a position to have a policy, and
it currently has none beyond a default that the client can switch off.

That makes this a question of what policy to invent, which is exactly what
z/OS answers with JESINTERFACELEVEL:

  • Level 1 — a session may only reach jobs whose name is its userid plus at
    most one character. Crude, needs no security product, and would work here
    today.
  • Level 2 — access is delegated to SAF profiles in class JESSPOOL
    (nodeid.userid.jobname.jobid.dsid). Needs a class RAKF does not have.

Sketch, to be argued rather than adopted

  1. JESOWNER may only ever narrow: the effective owner filter stays the
    session user; a different value is refused rather than honoured.
  2. Enforce at the access, not in the listing — find_job()'s callers must
    check job->owner before RETR or DELE touches anything.
  3. An explicit widening path for operators: racf_auth() against a FACILITY
    resource (FTPD.JESSPOOL or similar), default deny. This is the piece that
    substitutes for a JESSPOOL class we do not have.
  4. Then JESINTERFACELEVEL means something again, and the inert FTPDPRM key
    of that name (and cfg->jes_level / sess->jes_level, written but never
    read) becomes the switch between 1 and 2 instead of dead weight.

Until then, ftpd no longer pretends: #89 makes SITE JESINTERFACELEVEL=n
answer "Unrecognized parameter", as z/OS does.

Operational note for anyone reading this before it is fixed

Any authenticated FTP user can read any job's spool on the system. On a
closed development LPAR that may be acceptable; anywhere else it is worth
knowing before FTPD is reachable from a network that matters.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions