The branch main has been updated by bapt:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=92e8a76b87862aa2ee7e6c3f637034e60ffbc2e6

commit 92e8a76b87862aa2ee7e6c3f637034e60ffbc2e6
Author:     Baptiste Daroussin <[email protected]>
AuthorDate: 2026-09-04 10:17:02 +0000
Commit:     Baptiste Daroussin <[email protected]>
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 <[email protected]>
+ *
+ * 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
 }

Reply via email to