Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions doc/FTPD_RAKF_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,29 @@ under the default identity with a warning:
FTPD004W RACINIT ENVIR=CREATE failed RC=nn
```

### 5.1 SECURITY INVARIANT — `FTPD/USER` must be least-privilege

> **This is a security invariant, not a recommendation.**
>
> `FTPD/USER` **must not** be granted broad dataset authority (no
> `OPERATIONS`-equivalent access, no wide `ID(FTPD) ACCESS(ALTER)`
> profiles). Its only required authority is what the STC itself needs
> to run — not access to user data. Per-user data access is authorized
> against the **logged-in user's** identity, never `FTPD/USER`.
>
> **Why it is an invariant:** the per-command ABEND recovery handler
> resets the address-space-wide security environment (ASXBSENV) to the
> `FTPD/USER` ACEE after an unexpected ABEND. Because sessions run as
> concurrent subtasks sharing that one field, a concurrent session can
> be transiently pulled onto `FTPD/USER` during recovery. This is
> deliberately **fail-closed** *only while `FTPD/USER` is minimally
> privileged*: a session pulled onto it can lose authority, never gain
> it. If `FTPD/USER` is given broad dataset authority, that same
> transient switch becomes **fail-open** — a concurrent session's
> dataset OPEN could momentarily authorize with elevated access.
>
> Keep `FTPD/USER` scoped to the STC's own needs.

---

## 6. Reuse for Other STCs
Expand Down
20 changes: 20 additions & 0 deletions include/ftpd#ses.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

/* Forward declaration — full definition in libufs.h */
struct libufs_ufs;
struct libufs_file;

/* --- Session structure --- */
struct ftpd_session {
Expand Down Expand Up @@ -72,6 +73,25 @@ struct ftpd_session {
/* Command buffer */
char cmd[FTPD_MAX_CMD_LEN]; /* current command line */
int cmdlen; /* command length */
int dispatch_rc; /* rc stashed by the try()-wrapped
** command dispatch (try() itself
** returns the ABEND code, not the
** handler's rc) */

/* In-flight resources released by ABEND recovery */
FILE *cur_file; /* open dataset FILE* during a
** RETR/STOR/APPE transfer; NULL
** otherwise. fclose() releases the
** DCB + fopen's dynalloc DD. */
struct libufs_file *cur_ufs_file; /* open UFSD file during a UFS
** RETR/STOR transfer; NULL else.
** ufs_fclose() releases it. */
#ifdef FTPD_DEBUG_ABEND
int debug_abend_xfer; /* armed by SITE ABEND=XFER: the
** next MVS RETR ABENDs mid-transfer
** (cur_file set, data conn open) —
** debug builds only. */
#endif

/* Idle tracking (heap — immune to SVC stack corruption) */
time_t idle_start; /* getline idle timer start */
Expand Down
13 changes: 13 additions & 0 deletions include/ftpd.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "clibcib.h" /* console information blocks */
#include "socket.h" /* sockets via DYN75 */
#include "racf.h" /* security environment */
#include "clibtry.h" /* try(), tryrc() ESTAE recovery */

#include "ftpd#cfg.h" /* configuration */
#include "ftpd#log.h" /* logging & trace */
Expand Down Expand Up @@ -107,6 +108,12 @@ typedef unsigned char UCHAR;
#define FTPD_MAX_USER_LEN 8 /* max userid length */
#define FTPD_DATA_BUF_SIZE 4096 /* data transfer buffer size */

/* --- ABEND recovery --- */
#define FTPD_MAX_RECOVER 3 /* consecutive per-command ABEND
** recoveries before the session is
** closed (guards against a wedged
** session recovering forever) */

/* --- Allocation defaults for new datasets --- */
typedef struct ftpd_alloc {
char recfm[4]; /* RECFM for new datasets */
Expand Down Expand Up @@ -135,6 +142,9 @@ struct ftpd_server {
#define FTPD_QUIESCE 0x02 /* shutdown in progress */

ftpd_config_t config; /* server configuration */
ACEE *stc_acee; /* STC identity ACEE, captured at
** init; recovery resets ASXBSENV
** to this after an ABEND */
int listen_sock; /* listening socket fd */
CTHDMGR *mgr; /* thread manager */
CTHDTASK *sock_task; /* socket listener thread */
Expand All @@ -143,6 +153,9 @@ struct ftpd_server {
long total_sessions; /* total sessions since start */
long total_bytes_in; /* total bytes received */
long total_bytes_out;/* total bytes sent */
unsigned total_recover; /* ABEND recoveries since start
** (STC-lifetime; observability for
** documented residual leaks) */
};

extern ftpd_server_t *ftpd_server;
Expand Down
38 changes: 37 additions & 1 deletion src/ftpd#mvs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,10 @@ ftpd_mvs_retr(ftpd_session_t *sess, const char *arg)
return 0;
}

/* Track the open handle so ABEND recovery can release it (DCB +
** fopen's dynalloc DD) if the transfer below ABENDs. */
sess->cur_file = fp;

/* Read LRECL/RECFM from file handle — crent370 populates from DCB.
** For RECFM=U, lrecl is 0; use blksize instead (mvsmf pattern). */
is_fixed = (fp->recfm & _FILE_RECFM_TYPE) == _FILE_RECFM_F;
Expand All @@ -1178,6 +1182,7 @@ ftpd_mvs_retr(ftpd_session_t *sess, const char *arg)
recfm_label(fp->recfm), lrecl);

if (ftpd_data_open(sess) != 0) {
sess->cur_file = NULL;
fclose(fp);
ftpd_session_reply(sess, FTP_425,
"Cannot open data connection");
Expand All @@ -1186,6 +1191,19 @@ ftpd_mvs_retr(ftpd_session_t *sess, const char *arg)

total = 0;

#ifdef FTPD_DEBUG_ABEND
/* SITE ABEND=XFER injection (#63): ABEND here with cur_file set and the
** data connection open, to exercise recovery's fclose(cur_file) +
** clear-before-close idempotency. One-shot: disarm before firing. */
if (sess->debug_abend_xfer) {
volatile int *trap = (volatile int *)0;
sess->debug_abend_xfer = 0;
ftpd_log_wto("FTPD072W DEBUG ABEND=XFER firing mid-RETR socket=%d",
sess->ctrl_sock);
*trap = 0; /* force S0C4 */
}
#endif

ftpd_log(LOG_INFO, "RETR: dsn=%s type=%c lrecl=%d blksize=%d recfm=0x%02X",
dsn, sess->type == XFER_TYPE_I ? 'I' :
sess->type == XFER_TYPE_A ? 'A' : 'E',
Expand Down Expand Up @@ -1246,6 +1264,7 @@ ftpd_mvs_retr(ftpd_session_t *sess, const char *arg)
}

ftpd_log(LOG_INFO, "RETR: closing %s", fname);
sess->cur_file = NULL;
fclose(fp);
ftpd_data_close(sess);

Expand Down Expand Up @@ -1583,7 +1602,16 @@ ftpd_mvs_stor(ftpd_session_t *sess, const char *arg)
/* Allocate under the logged-in user's security environment, not
** the STC identity — the subsequent fopen()/OPEN runs under the
** same ACEE. Running SVC 99 under FTPD would authorize against
** the STC, then OPEN under the user ACEE would ABEND S130. */
** the STC, then OPEN under the user ACEE would ABEND S130.
**
** NOTE (ABEND-recovery residual): this __dsalcf/__dsfree pair
** completes before cur_file is set, so an ABEND in this narrow
** window is NOT caught by recovery's fclose(cur_file). It would
** leak the DD and, because DISP=(NEW,CATLG,DELETE) catalogs on
** normal disposition, leave an empty catalogued dataset behind.
** The window is a few instructions (two SVC 99 calls) and is not
** instrumented; total_recover in the operator log makes any such
** accumulation observable. */
{
ACEE *oldacee = sess->acee ? racf_set_acee(sess->acee) : NULL;
rc = __dsalcf(ddname, "%s", opts);
Expand Down Expand Up @@ -1622,10 +1650,15 @@ ftpd_mvs_stor(ftpd_session_t *sess, const char *arg)
return 0;
}

/* Track the open handle so ABEND recovery can release it (DCB +
** fopen's dynalloc DD) if the transfer below ABENDs. */
sess->cur_file = fp;

ftpd_session_reply(sess, FTP_125, "Storing data set %s",
member[0] ? arg : dsn);

if (ftpd_data_open(sess) != 0) {
sess->cur_file = NULL;
fclose(fp);
ftpd_session_reply(sess, FTP_425,
"Cannot open data connection");
Expand Down Expand Up @@ -1656,6 +1689,7 @@ ftpd_mvs_stor(ftpd_session_t *sess, const char *arg)
int recnum = 0;

if (eff_lrecl == 0) {
sess->cur_file = NULL;
fclose(fp);
ftpd_data_close(sess);
ftpd_session_reply(sess, FTP_550,
Expand All @@ -1665,6 +1699,7 @@ ftpd_mvs_stor(ftpd_session_t *sess, const char *arg)

record_buffer = calloc(1, eff_lrecl);
if (!record_buffer) {
sess->cur_file = NULL;
fclose(fp);
ftpd_data_close(sess);
ftpd_session_reply(sess, FTP_550,
Expand Down Expand Up @@ -1804,6 +1839,7 @@ ftpd_mvs_stor(ftpd_session_t *sess, const char *arg)
}

ftpd_log(LOG_INFO, "STOR: closing %s", fname);
sess->cur_file = NULL;
fclose(fp);
ftpd_data_close(sess);

Expand Down
Loading