Skip to content

Commit 6822ba7

Browse files
authored
Replace strlcat/strlcpy with safe str_copy/str_append (#2678)
This unifies string handling across platforms and reduces unsafe usage of strncpy/strlcat/strlcpy/strcpy, improving robustness.
1 parent 0a7465c commit 6822ba7

File tree

16 files changed

+157
-97
lines changed

16 files changed

+157
-97
lines changed

HISTORY.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ XXXX-XX-XX
1111
mandatory formatting style for all C sources.
1212
- 2672_, [macOS], [BSD]: increase the chances to recognize zombie processes and
1313
raise the appropriate exception (`ZombieProcess`_).
14+
- 2676_, 2678_: replace unsafe `sprintf` / `snprintf` / `sprintf_s` calls with
15+
`str_format()`. Replace `strlcat` / `strlcpy` with safe `str_copy` /
16+
`str_append`. This unifies string handling across platforms and reduces
17+
unsafe usage of standard string functions, improving robustness.
1418

1519
**Bug fixes**
1620

psutil/_psutil_aix.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ psutil_net_if_stats(PyObject *self, PyObject *args) {
633633
if (sock == -1)
634634
goto error;
635635

636-
PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
636+
str_copy(ifr.ifr_name, sizeof(ifr.ifr_name), nic_name);
637637

638638
// is up?
639639
ret = ioctl(sock, SIOCGIFFLAGS, &ifr);

psutil/arch/all/init.h

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,19 +117,16 @@ extern int PSUTIL_CONN_NONE;
117117
} while (0)
118118

119119

120-
// strncpy() variant which appends a null terminator.
121-
#define PSUTIL_STRNCPY(dst, src, n) \
122-
strncpy(dst, src, n - 1); \
123-
dst[n - 1] = '\0'
124-
125-
126120
PyObject *psutil_oserror(void);
127121
PyObject *psutil_oserror_ad(const char *msg);
128122
PyObject *psutil_oserror_nsp(const char *msg);
129123
PyObject *psutil_oserror_wsyscall(const char *syscall);
130124
PyObject *psutil_runtime_error(const char *msg, ...);
131125

126+
int str_append(char *dst, size_t dst_size, const char *src);
127+
int str_copy(char *dst, size_t dst_size, const char *src);
132128
int str_format(char *buf, size_t size, const char *fmt, ...);
129+
133130
int psutil_badargs(const char *funcname);
134131
int psutil_setup(void);
135132

psutil/arch/all/str.c

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,19 @@
99
#include <stdio.h>
1010
#include <stdarg.h>
1111
#include <stddef.h>
12+
#include <string.h>
1213

1314
#include "init.h"
1415

1516

17+
static int
18+
_error(const char *msg) {
19+
// print debug msg because we never check str_*() return value.
20+
psutil_debug("%s", msg);
21+
return -1;
22+
}
23+
24+
1625
// Safely formats a string into a buffer. Writes a printf-style
1726
// formatted string into `buf` of size `size`, always null-terminating
1827
// if size > 0. Returns the number of characters written (excluding the
@@ -23,10 +32,8 @@ str_format(char *buf, size_t size, const char *fmt, ...) {
2332
va_list args;
2433
int ret;
2534

26-
if (size == 0) {
27-
psutil_debug("str_format: invalid arg 'size' = 0");
28-
return -1;
29-
}
35+
if (size == 0)
36+
return _error("str_format: invalid arg 'size' = 0");
3037

3138
va_start(args, fmt);
3239
#if defined(PSUTIL_WINDOWS)
@@ -43,3 +50,52 @@ str_format(char *buf, size_t size, const char *fmt, ...) {
4350
}
4451
return ret;
4552
}
53+
54+
55+
// Safely copy `src` to `dst`, always null-terminating. Replaces unsafe
56+
// strcpy/strncpy.
57+
int
58+
str_copy(char *dst, size_t dst_size, const char *src) {
59+
if (dst_size == 0)
60+
return _error("str_copy: invalid arg 'dst_size' = 0");
61+
62+
#if defined(PSUTIL_WINDOWS)
63+
if (strcpy_s(dst, dst_size, src) != 0)
64+
return _error("str_copy: strcpy_s failed");
65+
#else
66+
strncpy(dst, src, dst_size - 1);
67+
dst[dst_size - 1] = '\0';
68+
#endif
69+
return 0;
70+
}
71+
72+
73+
// Safely append `src` to `dst`, always null-terminating. Returns 0 on
74+
// success, -1 on truncation.
75+
int
76+
str_append(char *dst, size_t dst_size, const char *src) {
77+
size_t dst_len;
78+
79+
if (!dst || !src || dst_size == 0)
80+
return _error("str_append: invalid arg");
81+
82+
#if defined(PSUTIL_WINDOWS)
83+
dst_len = strnlen_s(dst, dst_size);
84+
if (dst_len >= dst_size - 1)
85+
return _error("str_append: destination full or truncated");
86+
if (strcat_s(dst, dst_size, src) != 0)
87+
return _error("str_append: strcat_s failed");
88+
#elif defined(PSUTIL_MACOS) || defined(PSUTIL_BSD)
89+
dst_len = strlcat(dst, src, dst_size);
90+
if (dst_len >= dst_size)
91+
return _error("str_append: truncated");
92+
#else
93+
dst_len = strnlen(dst, dst_size);
94+
if (dst_len >= dst_size - 1)
95+
return _error("str_append: destination full or truncated");
96+
strncat(dst, src, dst_size - dst_len - 1);
97+
dst[dst_size - 1] = '\0';
98+
#endif
99+
100+
return 0;
101+
}

psutil/arch/bsd/disk.c

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -80,73 +80,71 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
8080

8181
// see sys/mount.h
8282
if (flags & MNT_RDONLY)
83-
strlcat(opts, "ro", sizeof(opts));
83+
str_append(opts, sizeof(opts), "ro");
8484
else
85-
strlcat(opts, "rw", sizeof(opts));
85+
str_append(opts, sizeof(opts), "rw");
8686
if (flags & MNT_SYNCHRONOUS)
87-
strlcat(opts, ",sync", sizeof(opts));
87+
str_append(opts, sizeof(opts), ",sync");
8888
if (flags & MNT_NOEXEC)
89-
strlcat(opts, ",noexec", sizeof(opts));
89+
str_append(opts, sizeof(opts), ",noexec");
9090
if (flags & MNT_NOSUID)
91-
strlcat(opts, ",nosuid", sizeof(opts));
91+
str_append(opts, sizeof(opts), ",nosuid");
9292
if (flags & MNT_ASYNC)
93-
strlcat(opts, ",async", sizeof(opts));
93+
str_append(opts, sizeof(opts), ",async");
9494
if (flags & MNT_NOATIME)
95-
strlcat(opts, ",noatime", sizeof(opts));
95+
str_append(opts, sizeof(opts), ",noatime");
9696
if (flags & MNT_SOFTDEP)
97-
strlcat(opts, ",softdep", sizeof(opts));
97+
str_append(opts, sizeof(opts), ",softdep");
9898
#ifdef PSUTIL_FREEBSD
9999
if (flags & MNT_UNION)
100-
strlcat(opts, ",union", sizeof(opts));
100+
str_append(opts, sizeof(opts), ",union");
101101
if (flags & MNT_SUIDDIR)
102-
strlcat(opts, ",suiddir", sizeof(opts));
103-
if (flags & MNT_SOFTDEP)
104-
strlcat(opts, ",softdep", sizeof(opts));
102+
str_append(opts, sizeof(opts), ",suiddir");
105103
if (flags & MNT_NOSYMFOLLOW)
106-
strlcat(opts, ",nosymfollow", sizeof(opts));
104+
str_append(opts, sizeof(opts), ",nosymfollow");
107105
#ifdef MNT_GJOURNAL
108106
if (flags & MNT_GJOURNAL)
109-
strlcat(opts, ",gjournal", sizeof(opts));
107+
str_append(opts, sizeof(opts), ",gjournal");
110108
#endif
111109
if (flags & MNT_MULTILABEL)
112-
strlcat(opts, ",multilabel", sizeof(opts));
110+
str_append(opts, sizeof(opts), ",multilabel");
113111
if (flags & MNT_ACLS)
114-
strlcat(opts, ",acls", sizeof(opts));
112+
str_append(opts, sizeof(opts), ",acls");
115113
if (flags & MNT_NOCLUSTERR)
116-
strlcat(opts, ",noclusterr", sizeof(opts));
114+
str_append(opts, sizeof(opts), ",noclusterr");
117115
if (flags & MNT_NOCLUSTERW)
118-
strlcat(opts, ",noclusterw", sizeof(opts));
116+
str_append(opts, sizeof(opts), ",noclusterw");
119117
#ifdef MNT_NFS4ACLS
120118
if (flags & MNT_NFS4ACLS)
121-
strlcat(opts, ",nfs4acls", sizeof(opts));
119+
str_append(opts, sizeof(opts), ",nfs4acls");
122120
#endif
123121
#elif PSUTIL_NETBSD
124122
if (flags & MNT_NODEV)
125-
strlcat(opts, ",nodev", sizeof(opts));
123+
str_append(opts, sizeof(opts), ",nodev");
126124
if (flags & MNT_UNION)
127-
strlcat(opts, ",union", sizeof(opts));
125+
str_append(opts, sizeof(opts), ",union");
128126
if (flags & MNT_NOCOREDUMP)
129-
strlcat(opts, ",nocoredump", sizeof(opts));
127+
str_append(opts, sizeof(opts), ",nocoredump");
130128
#ifdef MNT_RELATIME
131129
if (flags & MNT_RELATIME)
132-
strlcat(opts, ",relatime", sizeof(opts));
130+
str_append(opts, sizeof(opts), ",relatime");
133131
#endif
134132
if (flags & MNT_IGNORE)
135-
strlcat(opts, ",ignore", sizeof(opts));
133+
str_append(opts, sizeof(opts), ",ignore");
136134
#ifdef MNT_DISCARD
137135
if (flags & MNT_DISCARD)
138-
strlcat(opts, ",discard", sizeof(opts));
136+
str_append(opts, sizeof(opts), ",discard");
139137
#endif
140138
#ifdef MNT_EXTATTR
141139
if (flags & MNT_EXTATTR)
142-
strlcat(opts, ",extattr", sizeof(opts));
140+
str_append(opts, sizeof(opts), ",extattr");
143141
#endif
144142
if (flags & MNT_LOG)
145-
strlcat(opts, ",log", sizeof(opts));
143+
str_append(opts, sizeof(opts), ",log");
146144
if (flags & MNT_SYMPERM)
147-
strlcat(opts, ",symperm", sizeof(opts));
145+
str_append(opts, sizeof(opts), ",symperm");
148146
if (flags & MNT_NODEVMTIME)
149-
strlcat(opts, ",nodevmtime", sizeof(opts));
147+
str_append(opts, sizeof(opts), ",nodevmtime");
150148
#endif
151149
py_dev = PyUnicode_DecodeFSDefault(fs[i].f_mntfromname);
152150
if (!py_dev)

psutil/arch/freebsd/proc.c

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ psutil_proc_exe(PyObject *self, PyObject *args) {
129129
else if (ret == 0)
130130
return psutil_oserror_nsp("psutil_pid_exists -> 0");
131131
else
132-
strcpy(pathname, "");
132+
str_copy(pathname, sizeof(pathname), "");
133133
}
134134

135135
return PyUnicode_DecodeFSDefault(pathname);
@@ -330,20 +330,20 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) {
330330
(uintmax_t)kve->kve_end
331331
);
332332
psutil_remove_spaces(addr);
333-
strlcat(
333+
str_append(
334334
perms,
335-
kve->kve_protection & KVME_PROT_READ ? "r" : "-",
336-
sizeof(perms)
335+
sizeof(perms),
336+
kve->kve_protection & KVME_PROT_READ ? "r" : "-"
337337
);
338-
strlcat(
338+
str_append(
339339
perms,
340-
kve->kve_protection & KVME_PROT_WRITE ? "w" : "-",
341-
sizeof(perms)
340+
sizeof(perms),
341+
kve->kve_protection & KVME_PROT_WRITE ? "w" : "-"
342342
);
343-
strlcat(
343+
str_append(
344344
perms,
345-
kve->kve_protection & KVME_PROT_EXEC ? "x" : "-",
346-
sizeof(perms)
345+
sizeof(perms),
346+
kve->kve_protection & KVME_PROT_EXEC ? "x" : "-"
347347
);
348348

349349
if (strlen(kve->kve_path) == 0) {

psutil/arch/linux/net.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ psutil_net_if_duplex_speed(PyObject *self, PyObject *args) {
7171
sock = socket(AF_INET, SOCK_DGRAM, 0);
7272
if (sock == -1)
7373
return psutil_oserror_wsyscall("socket()");
74-
PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
74+
str_copy(ifr.ifr_name, sizeof(ifr.ifr_name), nic_name);
75+
7576

7677
// duplex and speed
7778
memset(&ethcmd, 0, sizeof ethcmd);

psutil/arch/netbsd/proc.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ psutil_proc_exe(PyObject *self, PyObject *args) {
9696
else if (ret == 0)
9797
return psutil_oserror_nsp("psutil_pid_exists -> 0");
9898
else
99-
strcpy(pathname, "");
99+
str_copy(pathname, sizeof(pathname), "");
100100
}
101101
102102
return PyUnicode_DecodeFSDefault(pathname);

psutil/arch/netbsd/socks.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,8 @@ psutil_net_connections(PyObject *self, PyObject *args) {
407407
->ki_src;
408408
struct sockaddr_un *sun_dst = (struct sockaddr_un *)&kp->kpcb
409409
->ki_dst;
410-
strcpy(laddr, sun_src->sun_path);
411-
strcpy(raddr, sun_dst->sun_path);
410+
str_copy(laddr, sizeof(sun_src->sun_path), sun_src->sun_path);
411+
str_copy(raddr, sizeof(sun_dst->sun_path), sun_dst->sun_path);
412412
status = PSUTIL_CONN_NONE;
413413
py_laddr = PyUnicode_DecodeFSDefault(laddr);
414414
if (!py_laddr)

psutil/arch/osx/disk.c

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -71,61 +71,61 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
7171

7272
// see sys/mount.h
7373
if (flags & MNT_RDONLY)
74-
strlcat(opts, "ro", sizeof(opts));
74+
str_append(opts, sizeof(opts), "ro");
7575
else
76-
strlcat(opts, "rw", sizeof(opts));
76+
str_append(opts, sizeof(opts), "rw");
7777
if (flags & MNT_SYNCHRONOUS)
78-
strlcat(opts, ",sync", sizeof(opts));
78+
str_append(opts, sizeof(opts), ",sync");
7979
if (flags & MNT_NOEXEC)
80-
strlcat(opts, ",noexec", sizeof(opts));
80+
str_append(opts, sizeof(opts), ",noexec");
8181
if (flags & MNT_NOSUID)
82-
strlcat(opts, ",nosuid", sizeof(opts));
82+
str_append(opts, sizeof(opts), ",nosuid");
8383
if (flags & MNT_UNION)
84-
strlcat(opts, ",union", sizeof(opts));
84+
str_append(opts, sizeof(opts), ",union");
8585
if (flags & MNT_ASYNC)
86-
strlcat(opts, ",async", sizeof(opts));
86+
str_append(opts, sizeof(opts), ",async");
8787
if (flags & MNT_EXPORTED)
88-
strlcat(opts, ",exported", sizeof(opts));
88+
str_append(opts, sizeof(opts), ",exported");
8989
if (flags & MNT_LOCAL)
90-
strlcat(opts, ",local", sizeof(opts));
90+
str_append(opts, sizeof(opts), ",local");
9191
if (flags & MNT_QUOTA)
92-
strlcat(opts, ",quota", sizeof(opts));
92+
str_append(opts, sizeof(opts), ",quota");
9393
if (flags & MNT_ROOTFS)
94-
strlcat(opts, ",rootfs", sizeof(opts));
94+
str_append(opts, sizeof(opts), ",rootfs");
9595
if (flags & MNT_DOVOLFS)
96-
strlcat(opts, ",dovolfs", sizeof(opts));
96+
str_append(opts, sizeof(opts), ",dovolfs");
9797
if (flags & MNT_DONTBROWSE)
98-
strlcat(opts, ",dontbrowse", sizeof(opts));
98+
str_append(opts, sizeof(opts), ",dontbrowse");
9999
if (flags & MNT_IGNORE_OWNERSHIP)
100-
strlcat(opts, ",ignore-ownership", sizeof(opts));
100+
str_append(opts, sizeof(opts), ",ignore-ownership");
101101
if (flags & MNT_AUTOMOUNTED)
102-
strlcat(opts, ",automounted", sizeof(opts));
102+
str_append(opts, sizeof(opts), ",automounted");
103103
if (flags & MNT_JOURNALED)
104-
strlcat(opts, ",journaled", sizeof(opts));
104+
str_append(opts, sizeof(opts), ",journaled");
105105
if (flags & MNT_NOUSERXATTR)
106-
strlcat(opts, ",nouserxattr", sizeof(opts));
106+
str_append(opts, sizeof(opts), ",nouserxattr");
107107
if (flags & MNT_DEFWRITE)
108-
strlcat(opts, ",defwrite", sizeof(opts));
108+
str_append(opts, sizeof(opts), ",defwrite");
109109
if (flags & MNT_UPDATE)
110-
strlcat(opts, ",update", sizeof(opts));
110+
str_append(opts, sizeof(opts), ",update");
111111
if (flags & MNT_RELOAD)
112-
strlcat(opts, ",reload", sizeof(opts));
112+
str_append(opts, sizeof(opts), ",reload");
113113
if (flags & MNT_FORCE)
114-
strlcat(opts, ",force", sizeof(opts));
114+
str_append(opts, sizeof(opts), ",force");
115115
if (flags & MNT_CMDFLAGS)
116-
strlcat(opts, ",cmdflags", sizeof(opts));
116+
str_append(opts, sizeof(opts), ",cmdflags");
117117
// requires macOS >= 10.5
118118
#ifdef MNT_QUARANTINE
119119
if (flags & MNT_QUARANTINE)
120-
strlcat(opts, ",quarantine", sizeof(opts));
120+
str_append(opts, sizeof(opts), ",quarantine");
121121
#endif
122122
#ifdef MNT_MULTILABEL
123123
if (flags & MNT_MULTILABEL)
124-
strlcat(opts, ",multilabel", sizeof(opts));
124+
str_append(opts, sizeof(opts), ",multilabel");
125125
#endif
126126
#ifdef MNT_NOATIME
127127
if (flags & MNT_NOATIME)
128-
strlcat(opts, ",noatime", sizeof(opts));
128+
str_append(opts, sizeof(opts), ",noatime");
129129
#endif
130130
py_dev = PyUnicode_DecodeFSDefault(fs[i].f_mntfromname);
131131
if (!py_dev)

0 commit comments

Comments
 (0)