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>
146 lines
3.5 KiB
C
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;
|
|
}
|