NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

lib/59828: getopt(3) GNU extension wrong behavior



>Number:         59828
>Category:       lib
>Synopsis:       getopt(3) GNU extension wrong behavior
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    lib-bug-people
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Wed Dec 10 07:10:01 +0000 2025
>Originator:     Simon Wollwage
>Release:        getopt(3) GNU extension wrong behavior
>Organization:
>Environment:
NetBSD  11.99.4 NetBSD 11.99.4 (GENERIC) #0: Mon Dec  8 23:00:22 UTC 2025  mkrepro%mkrepro.NetBSD.org@localhost:/usr/src/sys/arch/amd
64/compile/GENERIC amd64
>Description:
Originally posted in freebsd-bugs%freebsd.org@localhost (see https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=291374)

From the getopt(3) manual page:

     The option string optstring may contain the following elements:
     individual characters, and characters followed by a colon to indicate an
     option argument is to follow.  If an individual character is followed by
     two colons, then the option argument is optional; optarg is set to the
     rest of the current argv word, or NULL if there were no more characters
     in the current word.  This is a GNU extension.  For example, an option
     string "x" recognizes an option "-x", and an option string "x:"
     recognizes an option and argument "-x argument".  It does not matter to
     getopt() if a following argument has leading white space.

Note the last clause about leading white space. It does not match the code.
>How-To-Repeat:
For example, our date(1) utility uses getopt(argc, argv, "f:I::jnRr:uv:z:") and look at this:

$ date -Idate
2025-12-04
$ date -I date
date: illegal time format
usage: date [-jnRu] [-I[date|hours|minutes|seconds]] [-f input_fmt]
            [-r filename|seconds] [-v[+|-]val[y|m|w|d|H|M|S]]
            [[[[[[cc]yy]mm]dd]HH]MM[.SS] | new_date] [+output_fmt]

#include <stdio.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
    int         ch;

    while ((ch = getopt(argc, argv, "cCd::F:lnoOp:s:u:U:wW")) != -1) {
        printf("ch is %c optarg is %s\n",
            (char)ch, optarg ? optarg : " NULL");
    }

    return (0);
}

Option list taken from our tftpd(8) programm that uses "-s path" for a chroot and optional "-d value" for debugging.

Build it: cc -o getoptest -ansi -pedantic -Wall getoptest.c
Run it:

$ ./getoptest -l -w -d14 -s /usr/local/admin/tftproot -u admin -U 0
ch is l optarg is  NULL
ch is w optarg is  NULL
ch is d optarg is 14
ch is s optarg is /usr/local/admin/tftproot
ch is u optarg is admin
ch is U optarg is 0

So far, so good. And now:

$ ./getoptest -l -w -d 14 -s /usr/local/admin/tftproot -u admin -U 0
ch is l optarg is  NULL
ch is w optarg is  NULL
ch is d optarg is  NULL

Option list silently truncated. No -s, no chrooting for tftpd.
>Fix:
Index: common/lib/libc/stdlib/getopt.c
===================================================================
RCS file: /cvsroot/src/common/lib/libc/stdlib/getopt.c,v
retrieving revision 1.2
diff -u -r1.2 getopt.c
--- common/lib/libc/stdlib/getopt.c     29 Jun 2024 07:56:56 -0000      1.2
+++ common/lib/libc/stdlib/getopt.c     10 Dec 2025 07:06:08 -0000
@@ -128,13 +128,17 @@
                   entire next argument. */
                if (*place)
                        optarg = __UNCONST(place);
-               else if (oli[2] == ':')
+               else if (oli[2] == ':') {
                        /*
                         * GNU Extension, for optional arguments if the rest of
                         * the argument is empty, we return NULL
                         */
-                       optarg = NULL;
-               else if (nargc > ++optind)
+                       if (nargc > (optind + 1) && nargv[optind + 1][0] != '-') {
+                               optarg = nargv[++optind];
+                       } else {
+                               optarg = NULL;
+                       }
+               } else if (nargc > ++optind)
                        optarg = nargv[optind];
                else {
                        /* option-argument absent */



Home | Main Index | Thread Index | Old Index