]> git.ipfire.org Git - thirdparty/shadow.git/commitdiff
lib/chkname.c, src/: Strictly disallow really bad names
authorAlejandro Colomar <alx@kernel.org>
Mon, 23 Dec 2024 10:06:33 +0000 (11:06 +0100)
committerSerge Hallyn <serge@hallyn.com>
Fri, 26 Dec 2025 04:40:55 +0000 (22:40 -0600)
Some names are bad, and some names are really bad.  '--badname' should
only allow the mildly bad ones, which we can handle.  Some names are too
bad, and it's not possible to deal with them.  Reject them
unconditionally.

-  A leading '-' is too dangerous.  It breaks things like execve(2), and
   almost every command.

-  Spaces are used for delimiting lists of users and groups.

-  '"' is special in many languages, including the shell.  Having it in
   user names would be unnecessarily dangerous.

-  '#' is used for delimiting comments in several of our config files.
   Having it in usernames could result in incorrect configuration files.

-  "'" is special in many languages, including the shell.  Having it in
   user names would be unnecessarily dangerous.

-  ',' is used for delimiting lists of users and groups.

-  '/' is used for delimiting files, and thus could result in incorrect
   handling of users and groups.

-  ':' is the main delimiter in /etc/shadow and /etc/passwd.

-  ';' is special in many languages, including the shell.  Having it in
   user names would be unnecessarily dangerous.

There are other characters that we should disallow, but they need more
research to make sure we don't introduce regressions.  This set should
be less problematic.

Acked-by: Tobias Stoeckmann <tobias@stoeckmann.org>
Reviewed-by: Iker Pedrosa <ipedrosa@redhat.com>
Reviewed-by: Chris Hofstaedtler <zeha@debian.org>
Cc: Marc 'Zugschlus' Haber <mh+githubvisible@zugschlus.de>
Cc: Serge Hallyn <serge@hallyn.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
lib/chkname.c
src/newusers.c
src/pwck.c
src/useradd.c
src/usermod.c

index 6abd34fffa95cf604504cdee741c0138dcbd3a02..0abee4d2942aac9a7dee8a525a8fd2060000c518 100644 (file)
@@ -13,7 +13,8 @@
  *   true  - OK
  *   false - bad name
  * errors:
- *   EINVAL    Invalid name characters or sequences
+ *   EINVAL    Invalid name
+ *   EILSEQ    Invalid name character sequence (acceptable with --badname)
  *   EOVERFLOW Name longer than maximum size
  */
 
 #include <limits.h>
 #include <stdbool.h>
 #include <stddef.h>
+#include <string.h>
 #include <unistd.h>
 
 #include "defines.h"
 #include "chkname.h"
+#include "string/ctype/strchrisascii/strchriscntrl.h"
 #include "string/ctype/strisascii/strisdigit.h"
 #include "string/strcmp/streq.h"
+#include "string/strcmp/strcaseeq.h"
 
 
 #ifndef  LOGIN_NAME_MAX
@@ -59,6 +63,18 @@ login_name_max_size(void)
 static bool
 is_valid_name(const char *name)
 {
+       if (streq(name, "")
+        || streq(name, ".")
+        || streq(name, "..")
+        || strspn(name, "-")
+        || strpbrk(name, " \"#',/:;")
+        || strchriscntrl(name)
+        || strisdigit(name))
+       {
+               errno = EINVAL;
+               return false;
+       }
+
        if (allow_bad_names) {
                return true;
        }
@@ -69,25 +85,15 @@ is_valid_name(const char *name)
         *
         * as a non-POSIX, extension, allow "$" as the last char for
         * sake of Samba 3.x "add machine script"
-        *
-        * Also do not allow fully numeric names or just "." or "..".
         */
 
-       if (strisdigit(name)) {
-               errno = EINVAL;
-               return false;
-       }
-
-       if (streq(name, "") ||
-           streq(name, ".") ||
-           streq(name, "..") ||
-           !((*name >= 'a' && *name <= 'z') ||
+       if (!((*name >= 'a' && *name <= 'z') ||
              (*name >= 'A' && *name <= 'Z') ||
              (*name >= '0' && *name <= '9') ||
              *name == '_' ||
              *name == '.'))
        {
-               errno = EINVAL;
+               errno = EILSEQ;
                return false;
        }
 
@@ -101,7 +107,7 @@ is_valid_name(const char *name)
                      streq(name, "$")
                     ))
                {
-                       errno = EINVAL;
+                       errno = EILSEQ;
                        return false;
                }
        }
index 8af65b7a2bb1d58af5fe65d3aafdba4f18d5ba14..e9353fdc0aa6fb4759c71c5ce72d2b8b9ad17079 100644 (file)
@@ -398,7 +398,7 @@ static int add_user (const char *name, uid_t uid, gid_t gid)
 
        /* Check if this is a valid user name */
        if (!is_valid_user_name(name)) {
-               if (errno == EINVAL) {
+               if (errno == EILSEQ) {
                        fprintf(stderr,
                                _("%s: invalid user name '%s': use --badname to ignore\n"),
                                Prog, name);
index 0e7dfe7c9a31bd91ae1a8ef1c2a715882a80f2bd..c35f03e69595a6af6d63fcc2d05d48af22db4e97 100644 (file)
@@ -492,7 +492,7 @@ static void check_pw_file(bool *errors, bool *changed, const struct option_flags
                 */
 
                if (!is_valid_user_name(pwd->pw_name)) {
-                       if (errno == EINVAL) {
+                       if (errno == EILSEQ) {
                                printf(_("invalid user name '%s': use --badname to ignore\n"),
                                       pwd->pw_name);
                        } else {
index a71a049daebfcf24fc9daaecd2e54b1e4ce306e5..899efe3c84193019e0cefe0b8c9dccce761725f8 100644 (file)
@@ -1498,7 +1498,7 @@ static void process_flags (int argc, char **argv, struct option_flags *flags)
 
                user_name = argv[optind];
                if (!is_valid_user_name(user_name)) {
-                       if (errno == EINVAL) {
+                       if (errno == EILSEQ) {
                                fprintf(stderr,
                                        _("%s: invalid user name '%s': use --badname to ignore\n"),
                                        Prog, user_name);
index 2418514dd326b80dc3f797268c5e4198ca5b8a12..e8c9da6687ed3d59738b630d641b21c50ef4c3a7 100644 (file)
@@ -1137,7 +1137,7 @@ process_flags(int argc, char **argv, struct option_flags *flags)
                                /*@notreached@*/break;
                        case 'l':
                                if (!is_valid_user_name(optarg)) {
-                                       if (errno == EINVAL) {
+                                       if (errno == EILSEQ) {
                                                fprintf(stderr,
                                                        _("%s: invalid user name '%s': use --badname to ignore\n"),
                                                        Prog, optarg);