fix(harness): xce_test argument validation, RPC error text and leaks (13 bugs)

Stack overflows on user input:
- bug-1702 jobrun -m: host segment strncpy'd into a 256-byte buffer
  unbounded (ASAN stack-buffer-overflow); now cmdutil_split_host_tasks()
  refuses an over-long host.
- bug-1703 jobsub -n: min strncpy'd into a 64-byte buffer; now
  cmdutil_parse_ntasks() parses in place, 1 <= min <= max.
- resupdate's 16-entry vals[] had no bound either; a 17th -n overflowed.

Wrong or silent results:
- bug-1574 resupdate type/model: set_resval() had no string case, left the
  entry's name uninitialized, and crashed the encoder. Moved to
  resval_typeresolve.c as resval_fill(), which fills every type.
- bug-1705 jobsub -x was overwritten (and leaked) by the default exdata;
  cmdutil_default_exdata() only fills an unset one. jobsub's
  execuser/exechome/execcwd copies leaked on every call.
- bug-1707 a trailing -n without -v was dropped silently; resupdate also
  returned 0 after its RPC failed and "succeeded" with nothing to send.
- bug-1708 jobkill -s SIGKILL became signal 0 and was sent.
- bug-1709 jobinfo exited 0 with no -j, a malformed -j, or no hits.
- bug-1710 hello -c 0 sent nothing and reported success.
- bug-1712 a duplicate -v was reported as "no res name found".
- bug-1777 workerlist showed join-time maxmem/maxswap/maxtmp, not the
  effective values (hosts.yaml overrides) the reply already carries in
  resvals_to_sched and the scheduler uses.
- bug-1793 join's maxswap/maxtmp defaults were 1 KB / 2 KB in bytes; now
  1 GiB / 2 GiB, and -m/-s/-t no longer overflow atoi() above 2 GB.

Error reporting and leaks:
- bug-1796 RPC failures printed perror() of a stale errno ("Connection
  refused" / "Invalid argument" / "Inappropriate ioctl for device" for
  the same failure). cmdutil_rpc_error() prints the RPC's own code
  (E_XCE_* included) at all 19 sites.
- bug-1797 qctrl leaked the queue name on the RPC-error path; repeated
  -q/-h leaked the earlier copy.

New src/harness/cmdutil.c (+ cmdutil_unit_test, 14 cases); 9 new
test_test_cli.c cases, 8 RED on the old xce_test; new resval_fill,
worker_effective_attr and join-default tests (join ones RED on the old
join_worker_args.c). Harness suite 6/6 (asan), harness_unit_test 141/0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-09-28 04:17:04 +00:00
parent eee76a75ab
commit da1a663f05
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
14 changed files with 1107 additions and 286 deletions

View file

@ -0,0 +1,41 @@
/*
* cmdutil.h -- xce_test command-argument and RPC-error helpers, extracted
* from test.c so they can be unit-tested without a live controller
* (testsuite/test_cmdutil.c).
*/
#ifndef CMDUTIL_H
#define CMDUTIL_H
#include <stddef.h> /* size_t */
#include <stdint.h> /* uint32_t */
/* Parse all of `s` as a base-10 integer in [lo, hi] into *out.
* Returns 0, -EINVAL (empty, NULL or not entirely numeric) or -ERANGE
* (outside [lo, hi]); *out is left unchanged on error. */
int cmdutil_parse_long(const char* s, long lo, long hi, long* out);
/* Split jobrun's "<host>[:<ntasks>]" into `host` (hostsz bytes including the
* NUL) and *ntasks (1 when omitted). Returns 0, -ENAMETOOLONG (host segment
* does not fit), or -EINVAL (empty host, or ntasks not an integer >= 1). */
int cmdutil_split_host_tasks(const char* arg, char* host, size_t hostsz,
int* ntasks);
/* Parse jobsub's "<min>[,<max>]" (max defaults to min). Returns 0, -EINVAL
* (not numeric, or max < min) or -ERANGE (min < 1, or beyond UINT32_MAX). */
int cmdutil_parse_ntasks(const char* arg, uint32_t* min, uint32_t* max);
/* jobsub's default exdata: when *exdata is NULL, set it to a newly
* allocated {"sub_env_vars":{"PATH":<path>}} JSON string; an exdata the
* caller already set (-x) is left alone. Returns 0 or -ENOMEM. */
int cmdutil_default_exdata(char** exdata, const char* path);
/* Text for an RPC's negative return code `rc` (libc errno or E_XCE_*), in
* `buf`; never reads the global errno. Returns buf. */
const char* cmdutil_errstr(int rc, char* buf, size_t bufsize);
/* Print "<what>: <cmdutil_errstr(rc)>" to stderr and set errno to -rc: the
* replacement for perror() on an RPC-failure path, where errno does not
* hold the RPC's result. */
void cmdutil_rpc_error(const char* what, int rc);
#endif /* CMDUTIL_H */

View file

@ -23,6 +23,9 @@
#include <stdint.h> /* uint32_t */
#include <time.h> /* time_t */
#include "controller.capnp.h" /* Value_u_which */
#include "xce/types.h" /* xce_worker_t */
/* Render a worker's status bitmask (xce_worker_t.status, XCE_WORKER_* bits
* in xce/types.h) as a short display string for cmd_workerlist's Status
* column.
@ -78,4 +81,12 @@ char* job_state_str(int state);
* `buf`. */
const char* format_job_time(time_t t, char* buf, size_t bufsize);
/* The value workerlist shows for a worker's byte-valued host attribute
* (`resname` = "maxmem", "maxswap" or "maxtmp"): the effective numeric
* value the controller reports in w->resvals_to_sched (it reflects a
* hosts.yaml override, and is what the scheduler uses), else
* `attr_value`, the join-time attrs field (bug-1777). */
double worker_effective_attr(const xce_worker_t* w, const char* resname,
double attr_value);
#endif /* JOBLIST_DISPLAY_H */

View file

@ -16,6 +16,7 @@
*/
#include "controller.capnp.h" /* enum Value_u_which */
#include "xce/types.h" /* xce_resval_t */
/* Sentinel: not a valid Value_u_which value. Used both as:
* - the RETURN value when `name` is not present in the known-resource
@ -50,4 +51,16 @@
*/
int resval_resolve_type(const char* name, int type_override);
/* Fill `rv` for one resupdate entry: rv->name = name and rv->host = host
* (both borrowed, not copied), and a newly allocated rv->val of the type
* resval_resolve_type() gives, holding `val` (NULL = the type's zero value;
* a string value is copied). Formerly test.c's set_resval(), which had no
* string case: a type/model entry kept an uninitialized name (bug-1574).
* Returns 1, 0 when `name` is NULL (nothing filled), or -ENOMEM. */
int resval_fill(xce_resval_t* rv, char* name, const char* val,
int type_override, char* host);
/* Free what resval_fill() allocated (rv->val and a string value in it). */
void resval_fill_release(xce_resval_t* rv);
#endif /* RESVAL_TYPERESOLVE_H */