myxce/src/harness/cmdutil.c
Rongsong Shen da1a663f05
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>
2026-09-28 04:17:04 +00:00

146 lines
3.5 KiB
C

/*
* cmdutil.c -- xce_test command-argument and RPC-error helpers (see
* include/cmdutil.h).
*/
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cjson/cJSON.h>
#include "cmdutil.h"
#include "utils.h" /* xce_strerror_r */
#include "xce/errnox.h"
/* strtol over [s, s + len): the whole span must be digits (optional
* leading '-'), then range-checked. */
static int parse_span(const char* s, size_t len, long lo, long hi, long* out)
{
char buf[32];
char* end;
long v;
if ((s == NULL) || (len == 0)) {
return -EINVAL;
}
for (size_t i = 0; i < len; i++) {
if (!((s[i] >= '0' && s[i] <= '9') || (i == 0 && s[i] == '-'))) {
return -EINVAL;
}
}
if (len >= sizeof(buf)) {
return -ERANGE; /* more digits than any long holds */
}
memcpy(buf, s, len);
buf[len] = '\0';
errno = 0;
v = strtol(buf, &end, 10);
if ((end == buf) || (*end != '\0')) {
return -EINVAL;
}
if ((errno == ERANGE) || (v < lo) || (v > hi)) {
return -ERANGE;
}
*out = v;
return 0;
}
int cmdutil_parse_long(const char* s, long lo, long hi, long* out)
{
return parse_span(s, (s == NULL) ? 0 : strlen(s), lo, hi, out);
}
int cmdutil_split_host_tasks(const char* arg, char* host, size_t hostsz,
int* ntasks)
{
const char* colon = strchr(arg, ':');
size_t hlen = (colon != NULL) ? (size_t)(colon - arg) : strlen(arg);
long n = 1;
if (hlen == 0) {
return -EINVAL;
}
if (hlen >= hostsz) {
return -ENAMETOOLONG;
}
if ((colon != NULL) && (cmdutil_parse_long(colon + 1, 1, INT_MAX, &n) < 0)) {
return -EINVAL;
}
memcpy(host, arg, hlen);
host[hlen] = '\0';
*ntasks = (int)n;
return 0;
}
int cmdutil_parse_ntasks(const char* arg, uint32_t* min, uint32_t* max)
{
const char* comma = strchr(arg, ',');
size_t mlen = (comma != NULL) ? (size_t)(comma - arg) : strlen(arg);
long mn, mx;
int rc;
rc = parse_span(arg, mlen, 1, (long)UINT32_MAX, &mn);
if (rc < 0) {
return rc;
}
mx = mn;
if (comma != NULL) {
rc = cmdutil_parse_long(comma + 1, 1, (long)UINT32_MAX, &mx);
if (rc < 0) {
return rc;
}
if (mx < mn) {
return -EINVAL;
}
}
*min = (uint32_t)mn;
*max = (uint32_t)mx;
return 0;
}
int cmdutil_default_exdata(char** exdata, const char* path)
{
cJSON* root;
cJSON* envs;
if (*exdata != NULL) {
return 0;
}
root = cJSON_CreateObject();
envs = cJSON_CreateObject();
if ((root == NULL) || (envs == NULL)
|| (cJSON_AddStringToObject(envs, "PATH", path ? path : "") == NULL)) {
cJSON_Delete(root);
cJSON_Delete(envs);
return -ENOMEM;
}
cJSON_AddItemToObject(root, "sub_env_vars", envs);
*exdata = cJSON_Print(root);
cJSON_Delete(root);
return (*exdata != NULL) ? 0 : -ENOMEM;
}
const char* cmdutil_errstr(int rc, char* buf, size_t bufsize)
{
int code = (rc < 0) ? -rc : rc;
if (code >= XCE_ERRNO_BASE) {
xce_strerror_r(code, buf, bufsize);
} else {
snprintf(buf, bufsize, "%s", strerror(code));
}
return buf;
}
void cmdutil_rpc_error(const char* what, int rc)
{
char buf[128];
fprintf(stderr, "%s: %s\n", what, cmdutil_errstr(rc, buf, sizeof(buf)));
errno = (rc < 0) ? -rc : rc;
}