On Sun, Mar 15, 2020 at 12:21:02PM +0200, 0xef967...@gmail.com wrote:
> On Sun, Mar 15, 2020 at 09:30:22AM +0100, Martijn van Duren wrote:
> > > --- lib/libc/stdlib/getopt_long.c~
> > > +++ lib/libc/stdlib/getopt_long.c
> > > @@ -418,15 +418,8 @@
> > >   }
> > >  
> > >   if ((optchar = (int)*place++) == (int)':' ||
> > > -     (optchar == (int)'-' && *place != '\0') ||
> > > -     (oli = strchr(options, optchar)) == NULL) {
> > > -         /*
> > > -          * If the user specified "-" and  '-' isn't listed in
> > > -          * options, return -1 (non-option) as per POSIX.
> > > -          * Otherwise, it is an unknown option character (or ':').
> > > -          */
> > > -         if (optchar == (int)'-' && *place == '\0')
> > > -                 return (-1);
> > > +     ((oli = strchr(options, optchar)) == NULL ||
> > > +      (optchar == (int)'-' && oli == options))) {
> > >           if (!*place)
> > >                   ++optind;
> > >           if (PRINT_ERROR)

A "side-effect" of my patch is that it prevents the only case where
OpenBSD's getopt(3) was clinging to the stale state after having
finished parsing the arguments and returned -1.

The patch restore the older behaviour where you could just set
"optind = 1" in order to parse another list of arguments (instead of
the unportable "optreset = 1" or "optind = 0"), without exposing you
to crashes and possible exploits.

That used to work in the old getopt(3) from *BSD, and in the (original?)
SysV version, but was broken in the glibc version of getopt(3) and,
in a corner case, in the getopt_long implementation from OpenBSD.

You could test it with the getopt-test-relocate.c program at the end
of this message.

More info at https://stackoverflow.com/questions/60483737, and a glibc
patch at https://sourceware.org/bugzilla/show_bug.cgi?id=25658

OPTS=q ./getopt-test-relocate-unpatched  -q-
{./getopt-test-relocate-unpatched} <q> | {-q-}
getopt-test-relocate-unpatched: unknown option -- :
getopt-test-relocate-unpatched: unknown option -- O
getopt-test-relocate-unpatched: unknown option -- P
getopt-test-relocate-unpatched: unknown option -- T
getopt-test-relocate-unpatched: unknown option -- S
getopt-test-relocate-unpatched: unknown option -- =
{./getopt-test-relocate-unpatched} #U: #UO #UP #UT #US #U= <q> |

=====================================================================
getopt-test-relocate.c
=====================================================================
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

static int showargs(int ac, char **av, char *os){
        char *s; int c, i;
        if(ac < 1) return 1;
        printf("{%s}", av[0]);
        while((c = getopt(ac, av, os)) != -1)
                switch(c){
                case '?':
                        printf(" #U%c", optopt); break;
                case ':':
                        printf(" #A%c", optopt); break;
                case '\1':
                        printf(" <\\1>={%s}", optarg); break;
                default:
                        if((s = strrchr(os, c)) && s[1] == ':')
                                printf(" <%c>={%s}", c, optarg);
                        else
                                printf(" <%c>", c);
                }
        printf(" |");
        for(i = optind; i < ac; i++) printf(" {%s}", av[i]);
        printf("\n");
        return 0;
}
static void relocate_args(int ac, char **av){
        int i;
        for(i = 1; i < ac; i++){
                char *o = av[i]; size_t l = strlen(o);
                if(!(av[i] = strdup(o))) abort();
                memset(o, ':', l + 1);
        }
}
int main(int ac, char **av){
        char *os;
        if(!(os = getenv("OPTS"))) os = "";
        showargs(ac, av, os);
        relocate_args(ac, av);
        optind = 1;
        return showargs(ac, av, os);
}

Reply via email to