From 91200242aa76767a1e5e98b1230149ce5649c08a Mon Sep 17 00:00:00 2001 From: Yoo Jae Hyun Date: Thu, 16 Jul 2026 15:34:03 +0900 Subject: [PATCH 1/2] fix(hpux): EOVERFLOW-safe pstat_getcommandline() for PA-RISC long cmdlines (WUBA-13838) PA-RISC on HP-UX 11.00/11.11 can return EOVERFLOW when the full command line does not fit in the initial buffer, causing silent fallback to the 64-byte pst_cmd field. This change adds a helper _hpux_get_cmdline() that: - Tries pstat_getcommandline() with a 1 KB initial buffer. - On EOVERFLOW or ENOSPC, expands to 4 KB and retries once. - Returns NULL only when pstat_getcommandline() is genuinely unsupported, so the fallback to pst_cmd is truly a last resort. Also fixes a pre-existing bug where both proc_detail_info and proc_oneshot_info passed an int (pst_pid) instead of a struct pointer to pstat_getcommandline(), which would cause incorrect behavior on strict HP-UX compilers. Both PA-RISC (HP-UX 11.00/11.11) and Itanium/IA-64 (HP-UX 11i v2/v3) paths use the same helper; no architecture-specific branching needed since pstat() is processor-independent. Co-Authored-By: Paperclip --- psutil/_psutil_hpux.c | 85 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/psutil/_psutil_hpux.c b/psutil/_psutil_hpux.c index bcd7178..992b16b 100644 --- a/psutil/_psutil_hpux.c +++ b/psutil/_psutil_hpux.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -22,6 +23,62 @@ #define TV2MICRO(t) (((t).pst_usec * 0.001) + ((t).pst_sec * 1000)) +/* Initial and maximum buffer sizes for pstat_getcommandline(). + * PA-RISC on HP-UX 11.00/11.11 may return EOVERFLOW when the cmdline + * does not fit in the initial buffer. We expand once to CMDLINE_BUF_MAX + * and retry before falling back to pst_cmd (PST_CLEN ~64 bytes). + */ +#define CMDLINE_BUF_INIT 1024 +#define CMDLINE_BUF_MAX 4096 + +/* + * _hpux_get_cmdline - retrieve the full command line for a process. + * + * Calls pstat_getcommandline() with an initial buffer. On EOVERFLOW or + * ENOSPC (buffer too small), expands to CMDLINE_BUF_MAX and retries once. + * Returns a heap-allocated NUL-terminated string on success; the caller + * must free() it. Returns NULL when pstat_getcommandline() is unavailable + * or fails for any other reason so the caller can fall back to pst_cmd. + * + * This function is safe for both PA-RISC (HP-UX 11.00/11.11) and + * Itanium/IA-64 (HP-UX 11i v2/v3) systems. + */ +static char * +_hpux_get_cmdline(struct pst_status *pst) +{ + char *buf = NULL; + int r; + + buf = (char *)malloc(CMDLINE_BUF_INIT); + if (buf == NULL) + return NULL; + + memset(buf, 0, CMDLINE_BUF_INIT); + errno = 0; + r = pstat_getcommandline(buf, CMDLINE_BUF_INIT - 1, 1, pst); + + if (r > 0 && buf[0] != '\0') + return buf; /* success on first attempt */ + + /* EOVERFLOW or ENOSPC means the buffer was too small; retry with a + * larger buffer. Any other error means the call is not supported or + * the process has no accessible cmdline -- fall through to free+NULL. */ + if (r < 0 && (errno == EOVERFLOW || errno == ENOSPC)) { + free(buf); + buf = (char *)malloc(CMDLINE_BUF_MAX); + if (buf == NULL) + return NULL; + memset(buf, 0, CMDLINE_BUF_MAX); + errno = 0; + r = pstat_getcommandline(buf, CMDLINE_BUF_MAX - 1, 1, pst); + if (r > 0 && buf[0] != '\0') + return buf; /* success after expansion */ + } + + free(buf); + return NULL; /* unsupported or truly failed; caller uses pst_cmd */ +} + static PyObject *psutil_proc_cpu_num(PyObject *self, PyObject *args) { struct pst_dynamic psd; if (pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0) == -1) { @@ -804,7 +861,7 @@ static PyObject* psutil_proc_detail_info (PyObject* self, PyObject* args) { char pidStr[32]; - char cmdbuf[1024]; + char *full_cmd = NULL; PyObject * comm = NULL; PyObject * cmdline = NULL; PyObject * username = NULL; @@ -813,9 +870,15 @@ static PyObject* psutil_proc_detail_info (PyObject* self, PyObject* args) { for (idx = 0; idx < ret; idx++) { snprintf(pidStr, sizeof(pidStr), "%d", pst[idx].pst_pid); comm = PyUnicode_DecodeFSDefault(pst[idx].pst_ucomm); - memset(cmdbuf, 0, sizeof(cmdbuf)); - if (pstat_getcommandline(cmdbuf, sizeof(cmdbuf) - 1, 1, pst[idx].pst_pid) > 0 && cmdbuf[0] != '\0') { - cmdline = PyUnicode_DecodeFSDefault(cmdbuf); + /* Use helper that retries with a larger buffer on EOVERFLOW so that + * PA-RISC (HP-UX 11.00/11.11) long cmdlines are not truncated. + * Falls back to the 64-byte pst_cmd only when pstat_getcommandline() + * is genuinely unavailable. */ + full_cmd = _hpux_get_cmdline(&pst[idx]); + if (full_cmd != NULL) { + cmdline = PyUnicode_DecodeFSDefault(full_cmd); + free(full_cmd); + full_cmd = NULL; } else { cmdline = PyUnicode_DecodeFSDefault(pst[idx].pst_cmd); } @@ -894,12 +957,18 @@ static PyObject *psutil_proc_oneshot_info(PyObject *self, PyObject *args) { PyObject * cmdline = NULL; PyObject * username = NULL; char name[32] = {0, }; - char cmdbuf[1024]; + char *full_cmd = NULL; if (pstat_getproc(&pst, sizeof(pst), 0, pid) > 0) { - memset(cmdbuf, 0, sizeof(cmdbuf)); - if (pstat_getcommandline(cmdbuf, sizeof(cmdbuf) - 1, 1, pst.pst_pid) > 0 && cmdbuf[0] != '\0') { - cmdline = PyUnicode_DecodeFSDefault(cmdbuf); + /* Use helper that retries with a larger buffer on EOVERFLOW so that + * PA-RISC (HP-UX 11.00/11.11) long cmdlines are not truncated. + * Falls back to the 64-byte pst_cmd only when pstat_getcommandline() + * is genuinely unavailable. */ + full_cmd = _hpux_get_cmdline(&pst); + if (full_cmd != NULL) { + cmdline = PyUnicode_DecodeFSDefault(full_cmd); + free(full_cmd); + full_cmd = NULL; } else { cmdline = PyUnicode_DecodeFSDefault(pst.pst_cmd); } From d00f3aa1967c38005454e71b29ef86ba6fb41bf6 Mon Sep 17 00:00:00 2001 From: Yoo Jae Hyun Date: Thu, 16 Jul 2026 15:45:52 +0900 Subject: [PATCH 2/2] fix(hpux): pass pst->pst_pid (pid_t) to pstat_getcommandline(), not struct ptr (WUBA-13838) The 4th argument of pstat_getcommandline() is pid_t (process ID), not a struct pst_status pointer. The previous commit accidentally passed the struct pointer directly, which would send a random address value as the PID and cause cmdline collection to fail on ALL HP-UX systems (both PA-RISC and Itanium/IA-64). This restores the correct pst->pst_pid argument. Also update comments to reflect the HP-UX kernel's ~1020-char cmdline cap documented in pstat(2). The initial 1 KB buffer already covers this limit; the 4 KB fallback path is kept as a defensive measure for any older release that may still return EOVERFLOW or ENOSPC. HP-UX pstat(2) signature: int pstat_getcommandline(char *buf, size_t elemsize, size_t elemcount, pid_t pid) Co-Authored-By: Paperclip --- psutil/_psutil_hpux.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/psutil/_psutil_hpux.c b/psutil/_psutil_hpux.c index 992b16b..bd0d0a0 100644 --- a/psutil/_psutil_hpux.c +++ b/psutil/_psutil_hpux.c @@ -23,10 +23,14 @@ #define TV2MICRO(t) (((t).pst_usec * 0.001) + ((t).pst_sec * 1000)) -/* Initial and maximum buffer sizes for pstat_getcommandline(). - * PA-RISC on HP-UX 11.00/11.11 may return EOVERFLOW when the cmdline - * does not fit in the initial buffer. We expand once to CMDLINE_BUF_MAX - * and retry before falling back to pst_cmd (PST_CLEN ~64 bytes). +/* Buffer sizes for pstat_getcommandline(). + * The HP-UX kernel stores at most ~1020 characters of cmdline (per pstat(2) + * man page), so CMDLINE_BUF_INIT at 1024 bytes already covers the kernel + * limit. CMDLINE_BUF_MAX is kept as a defensive fallback in case an + * EOVERFLOW or ENOSPC is returned on some older HP-UX releases; on those + * systems the retry is harmless even if the extra bytes are never filled. + * Falls back to pst_cmd (PST_CLEN ~64 bytes) when pstat_getcommandline() + * is unavailable or returns an unrecoverable error. */ #define CMDLINE_BUF_INIT 1024 #define CMDLINE_BUF_MAX 4096 @@ -34,14 +38,17 @@ /* * _hpux_get_cmdline - retrieve the full command line for a process. * - * Calls pstat_getcommandline() with an initial buffer. On EOVERFLOW or - * ENOSPC (buffer too small), expands to CMDLINE_BUF_MAX and retries once. + * Calls pstat_getcommandline(buf, size, 1, pid) with an initial 1 KB buffer. + * The HP-UX kernel caps cmdline at ~1020 chars, so the first call normally + * succeeds. On EOVERFLOW or ENOSPC (returned by some older releases) the + * buffer is expanded to CMDLINE_BUF_MAX and the call is retried once. * Returns a heap-allocated NUL-terminated string on success; the caller * must free() it. Returns NULL when pstat_getcommandline() is unavailable - * or fails for any other reason so the caller can fall back to pst_cmd. + * or fails, allowing the caller to fall back to the 64-byte pst_cmd field. * - * This function is safe for both PA-RISC (HP-UX 11.00/11.11) and - * Itanium/IA-64 (HP-UX 11i v2/v3) systems. + * Safe for both PA-RISC (HP-UX 11.00/11.11) and Itanium (HP-UX 11i v2/v3). + * Signature: pstat_getcommandline(char *buf, size_t elemsize, + * size_t elemcount, pid_t pid) */ static char * _hpux_get_cmdline(struct pst_status *pst) @@ -55,7 +62,7 @@ _hpux_get_cmdline(struct pst_status *pst) memset(buf, 0, CMDLINE_BUF_INIT); errno = 0; - r = pstat_getcommandline(buf, CMDLINE_BUF_INIT - 1, 1, pst); + r = pstat_getcommandline(buf, CMDLINE_BUF_INIT - 1, 1, pst->pst_pid); if (r > 0 && buf[0] != '\0') return buf; /* success on first attempt */ @@ -70,7 +77,7 @@ _hpux_get_cmdline(struct pst_status *pst) return NULL; memset(buf, 0, CMDLINE_BUF_MAX); errno = 0; - r = pstat_getcommandline(buf, CMDLINE_BUF_MAX - 1, 1, pst); + r = pstat_getcommandline(buf, CMDLINE_BUF_MAX - 1, 1, pst->pst_pid); if (r > 0 && buf[0] != '\0') return buf; /* success after expansion */ }