git: 92e8a76b8786 - main - pw: use _PWDASH pseudo-fd for "-" in pw_checkfd
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Fri, 04 Sep 2026 10:43:38 UTC
The branch main has been updated by bapt:
URL: https://cgit.FreeBSD.org/src/commit/?id=92e8a76b87862aa2ee7e6c3f637034e60ffbc2e6
commit 92e8a76b87862aa2ee7e6c3f637034e60ffbc2e6
Author: Baptiste Daroussin <bapt@FreeBSD.org>
AuthorDate: 2026-09-04 10:17:02 +0000
Commit: Baptiste Daroussin <bapt@FreeBSD.org>
CommitDate: 2026-09-04 10:42:29 +0000
pw: use _PWDASH pseudo-fd for "-" in pw_checkfd
pw_checkfd() returned the character "-" (45) for the "-" argument,
which was ambiguous with a real file descriptor.
MFC After: 1 week
---
usr.sbin/pw/pw.h | 1 +
usr.sbin/pw/pw_group.c | 6 +-
usr.sbin/pw/pw_user.c | 6 +-
usr.sbin/pw/pw_utils.c | 2 +-
usr.sbin/pw/tests/Makefile | 10 +++-
usr.sbin/pw/tests/pw_unit_test.c | 107 +++++++++++++++++++++++++++++++++++
usr.sbin/pw/tests/pw_useradd_test.sh | 35 ++++++++++++
7 files changed, 159 insertions(+), 8 deletions(-)
diff --git a/usr.sbin/pw/pw.h b/usr.sbin/pw/pw.h
index 6be667f166fb..fcb5214a10e5 100644
--- a/usr.sbin/pw/pw.h
+++ b/usr.sbin/pw/pw.h
@@ -63,6 +63,7 @@ enum _which
#define _DEF_DIRMODE (S_IRWXU | S_IRWXG | S_IRWXO)
#define _PW_CONF "pw.conf"
+#define _PWDASH (-2) /* pseudo-fd returned by pw_checkfd for '-' */
#define _UC_MAXLINE 1024
#define _UC_MAXSHELLS 32
diff --git a/usr.sbin/pw/pw_group.c b/usr.sbin/pw/pw_group.c
index 2840e972af59..2df22e014b67 100644
--- a/usr.sbin/pw/pw_group.c
+++ b/usr.sbin/pw/pw_group.c
@@ -56,7 +56,7 @@ grp_set_passwd(struct group *grp, bool update, int fd, bool precrypted)
if (fd == -1)
return;
- if (fd == '-') {
+ if (fd == _PWDASH) {
grp->gr_passwd = "*"; /* No access */
return;
}
@@ -538,7 +538,7 @@ pw_group_add(int argc, char **argv, char *arg1)
"exclusive options");
fd = pw_checkfd(optarg);
precrypted = true;
- if (fd == '-')
+ if (fd == _PWDASH)
errx(EX_USAGE, "-H expects a file descriptor");
break;
case 'h':
@@ -636,7 +636,7 @@ pw_group_mod(int argc, char **argv, char *arg1)
"exclusive options");
fd = pw_checkfd(optarg);
precrypted = true;
- if (fd == '-')
+ if (fd == _PWDASH)
errx(EX_USAGE, "-H expects a file descriptor");
break;
case 'h':
diff --git a/usr.sbin/pw/pw_user.c b/usr.sbin/pw/pw_user.c
index abb8a09ce468..e2b84b2a83d3 100644
--- a/usr.sbin/pw/pw_user.c
+++ b/usr.sbin/pw/pw_user.c
@@ -171,7 +171,7 @@ pw_set_passwd(struct passwd *pwd, int fd, bool precrypted, bool update)
char line[_PASSWORD_LEN+1];
char *p;
- if (fd == '-') {
+ if (fd == _PWDASH) {
if (!pwd->pw_passwd || *pwd->pw_passwd != '*') {
pwd->pw_passwd = "*"; /* No access */
return (1);
@@ -1306,7 +1306,7 @@ pw_user_add(int argc, char **argv, char *arg1)
"exclusive options");
fd = pw_checkfd(optarg);
precrypted = true;
- if (fd == '-')
+ if (fd == _PWDASH)
errx(EX_USAGE, "-H expects a file descriptor");
break;
case 'h':
@@ -1629,7 +1629,7 @@ pw_user_mod(int argc, char **argv, char *arg1)
"exclusive options");
fd = pw_checkfd(optarg);
precrypted = true;
- if (fd == '-')
+ if (fd == _PWDASH)
errx(EX_USAGE, "-H expects a file descriptor");
break;
case 'h':
diff --git a/usr.sbin/pw/pw_utils.c b/usr.sbin/pw/pw_utils.c
index 87dd421ca8a3..363cceafec39 100644
--- a/usr.sbin/pw/pw_utils.c
+++ b/usr.sbin/pw/pw_utils.c
@@ -42,7 +42,7 @@ pw_checkfd(char *nptr)
int fd = -1;
if (strcmp(nptr, "-") == 0)
- return '-';
+ return (_PWDASH);
fd = strtonum(nptr, 0, INT_MAX, &errstr);
if (errstr != NULL)
errx(EX_USAGE, "Bad file descriptor '%s': %s",
diff --git a/usr.sbin/pw/tests/Makefile b/usr.sbin/pw/tests/Makefile
index 910a563d680d..b92897ab9208 100644
--- a/usr.sbin/pw/tests/Makefile
+++ b/usr.sbin/pw/tests/Makefile
@@ -2,8 +2,16 @@ PACKAGE= tests
BINDIR= ${TESTSDIR}
+.PATH: ${SRCTOP}/usr.sbin/pw
+
PROGS+= crypt
-LIBADD+= crypt
+LIBADD.crypt+= crypt
+
+ATF_TESTS_C= pw_unit_test
+SRCS.pw_unit_test= pw_unit_test.c \
+ pw_utils.c strtounum.c
+LIBADD.pw_unit_test= util
+CFLAGS.pw_unit_test= -I${SRCTOP}/usr.sbin/pw
ATF_TESTS_SH= pw_etcdir_test \
pw_lock_test \
diff --git a/usr.sbin/pw/tests/pw_unit_test.c b/usr.sbin/pw/tests/pw_unit_test.c
new file mode 100644
index 000000000000..f335f8e100a5
--- /dev/null
+++ b/usr.sbin/pw/tests/pw_unit_test.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2026 Baptiste Daroussin <bapt@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/types.h>
+
+#include <atf-c.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include "pw.h"
+#include "pwupd.h"
+
+struct pwconf conf;
+
+/* stub */
+struct userconf *
+read_userconfig(char const *file __unused)
+{
+
+ return (NULL);
+}
+
+ATF_TC_WITHOUT_HEAD(checkfd_dash);
+ATF_TC_BODY(checkfd_dash, tc)
+{
+ char dash[] = "-";
+
+ ATF_CHECK_EQ(pw_checkfd(dash), _PWDASH);
+}
+
+ATF_TC_WITHOUT_HEAD(checkfd_zero);
+ATF_TC_BODY(checkfd_zero, tc)
+{
+ char zero[] = "0";
+
+ ATF_CHECK_EQ(pw_checkfd(zero), 0);
+}
+
+ATF_TC_WITHOUT_HEAD(checkfd_valid);
+ATF_TC_BODY(checkfd_valid, tc)
+{
+ char five[] = "5";
+
+ ATF_CHECK_EQ(pw_checkfd(five), 5);
+}
+
+ATF_TC_WITHOUT_HEAD(checkfd_max);
+ATF_TC_BODY(checkfd_max, tc)
+{
+ char max[] = "2147483647";
+
+ ATF_CHECK_EQ(pw_checkfd(max), INT_MAX);
+}
+
+static void
+checkfd_invalid(const char *value, const char *errstr)
+{
+ pid_t pid;
+ char buf[32];
+ char experr[128];
+
+ strlcpy(buf, value, sizeof(buf));
+ snprintf(experr, sizeof(experr),
+ "pw_unit_test: Bad file descriptor '%s': %s\n", value, errstr);
+ pid = atf_utils_fork();
+ if (pid == 0) {
+ pw_checkfd(buf);
+ exit(1);
+ }
+ atf_utils_wait(pid, EX_USAGE, "", experr);
+}
+
+ATF_TC_WITHOUT_HEAD(checkfd_invalid);
+ATF_TC_BODY(checkfd_invalid, tc)
+{
+ checkfd_invalid("abc", "invalid");
+}
+
+ATF_TC_WITHOUT_HEAD(checkfd_negative);
+ATF_TC_BODY(checkfd_negative, tc)
+{
+ checkfd_invalid("-1", "too small");
+}
+
+ATF_TC_WITHOUT_HEAD(checkfd_overflow);
+ATF_TC_BODY(checkfd_overflow, tc)
+{
+ checkfd_invalid("999999999999", "too large");
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, checkfd_dash);
+ ATF_TP_ADD_TC(tp, checkfd_zero);
+ ATF_TP_ADD_TC(tp, checkfd_valid);
+ ATF_TP_ADD_TC(tp, checkfd_max);
+ ATF_TP_ADD_TC(tp, checkfd_invalid);
+ ATF_TP_ADD_TC(tp, checkfd_negative);
+ ATF_TP_ADD_TC(tp, checkfd_overflow);
+
+ return (atf_no_error());
+}
diff --git a/usr.sbin/pw/tests/pw_useradd_test.sh b/usr.sbin/pw/tests/pw_useradd_test.sh
index 4b0778759203..82c8d8b8524f 100755
--- a/usr.sbin/pw/tests/pw_useradd_test.sh
+++ b/usr.sbin/pw/tests/pw_useradd_test.sh
@@ -528,6 +528,38 @@ user_add_already_in_group_body()
grep testuser ${HOME}/group
}
+atf_test_case user_add_fd_dash
+user_add_fd_dash_body()
+{
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd fdtest -h -
+ atf_check -s exit:0 -o match:"^fdtest:\*:" \
+ grep "^fdtest:" ${HOME}/master.passwd
+}
+
+atf_test_case user_add_fd_numeric
+user_add_fd_numeric_body()
+{
+ populate_etc_skel
+
+ echo "testpass123" > ${HOME}/pwfile
+ fd=$(echo ${HOME}/pwfile)
+ echo "stdinpass" | atf_check -s exit:0 \
+ ${PW} useradd fdstdin -h 0
+ atf_check -s exit:0 -o not-match:"^fdstdin:\*:" \
+ grep "^fdstdin:" ${HOME}/master.passwd
+}
+
+atf_test_case user_add_H_dash_rejected
+user_add_H_dash_rejected_body()
+{
+ populate_etc_skel
+
+ atf_check -s exit:64 -e inline:"pw: -H expects a file descriptor\n" \
+ ${PW} useradd htest -H -
+}
+
atf_init_test_cases() {
atf_add_test_case user_add
atf_add_test_case user_add_noupdate
@@ -572,4 +604,7 @@ atf_init_test_cases() {
atf_add_test_case user_add_conf_defaultpasswd
atf_add_test_case user_add_existing_login_group
atf_add_test_case user_add_already_in_group
+ atf_add_test_case user_add_fd_dash
+ atf_add_test_case user_add_fd_numeric
+ atf_add_test_case user_add_H_dash_rejected
}