Follow @Openwall on Twitter for new release announcements and other news
[<prev] [day] [month] [year] [list]
Message-ID: <87bjjv3s9l.fsf@lin.moe>
Date: Fri, 19 Dec 2025 14:22:30 +0800
From: Linsen Zhou <i@....moe>
To: musl@...ts.openwall.com
Subject: Bug Report: getopt_long incorrectly permutes argv[argc] (NULL) into
 argv

       
While debugging a `smartctl -l` segfault on Alpine[1], I identified a
scenario where the NULL terminator at argv[argc] is incorrectly permuted
into the middle of the argv array.

The issue occurs when the following two conditions are met:
+ A long option is defined with the `required_argument` flag.
+ `getopt_long` is called with that flag, but the required argument is missing.

In this case, argv[argc] (the NULL terminator) is swapped into the
middle of the argv array.

Reproduction Example:
=====================
#include <getopt.h>
#include <stdio.h>

int argc = 3;
char *argv[] = {"cmd_name", "argument1", "-l", 0, "env1=val1"};

void show_argv() {
  for (int i = 0; i < 5; i++) {
	  printf("%s(%p) ", argv[i] == NULL ? "NULL":argv[i], argv[i] );
  }
  printf("\n");
}

int main() {
        static struct option long_opts[] = {
            {"log", required_argument, 0, 'l'},
            {0, 0, 0, 0},
        };
        int optidx = 0;
	show_argv(); getopt_long(argc, argv, "l:", long_opts, &optidx); show_argv();
}
=====================
$ ./main              
cmd_name(0x55617b5c8000) argument1(0x55617b5c8009) -l(0x55617b5c8013) NULL(0) env1=val1(0x55617b5c8016) 
cmd_name: option requires an argument: l
optind: 3
cmd_name(0x55617b5c8000) -l(0x55617b5c8013) NULL(0) argument1(0x55617b5c8009) env1=val1(0x55617b5c8016) 
=====================

The issue appears to stem from getopt_long.c. After calling
__getopt_long_core, optind can become greater than argc (e.g., reaching
4 when argc is 3).
=====================
static int __getopt_long(int argc, char *const *argv, const char *optstring, const struct option *longopts, int *idx, int longonly){
        ...
	ret = __getopt_long_core(argc, argv, optstring, longopts, idx, longonly);
	if (resumed > skipped) {
		int i, cnt = optind-resumed;
		for (i=0; i<cnt; i++)
			permute(argv, skipped, optind-1);
		optind = skipped + cnt;
	}
        ...
}
=====================
When optind exceeds argc, the permute function includes the NULL
terminator at argv[argc] in its rotation logic, pushing it into the
active indices of argv.

[1] https://gitlab.alpinelinux.org/alpine/aports/-/issues/15948
-- 
Regards, Linsen Zhou
https://lin.moe

Download attachment "signature.asc" of type "application/pgp-signature" (244 bytes)

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.