NetBSD-Bugs archive

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

kern/59523: posix_spawn(2) may fail eroneously trying to close an already closed file descriptor



>Number:         59523
>Category:       kern
>Synopsis:       posix_spawn(2) may fail eroneously trying to close an already closed file descriptor
>Confidential:   no
>Severity:       critical
>Priority:       high
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Wed Jul 09 11:30:01 +0000 2025
>Originator:     Martin Husemann
>Release:        NetBSD 10.99.14
>Organization:
The NetBSD Foundation, Inc.
>Environment:
System: NetBSD martins.aprisoft.de 10.99.14 NetBSD 10.99.14 (GENERIC) #270: Wed Jul 9 12:36:43 CEST 2025 martin%martins.aprisoft.de@localhost:/home/martin/current/src/sys/arch/amd64/compile/GENERIC amd64
Architecture: x86_64
Machine: amd64
>Description:

This came up due to syzkaller failing on NetBSD. Their code uses posix_spawnp(3)
and adds a "close" file action for every possible file descriptor starting one
higher than the highest descriptor they use themself up to the configured maximum.

This causes the posix_spawn(2) call to fail with EBADF, which is in violation
of posix:

  If the file_actions argument is not a null pointer, and specifies any
  chdir, close, dup2, fchdir, or open actions to be performed, and if
  posix_spawn() or posix_spawnp() fails for any of the reasons that would
  cause chdir(), close(), dup2(), fchdir(), or open() to fail, other than
							       ~~~~~~~~~~
  attempting a close() on a file descriptor that is in range but already
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  closed, an error value shall be returned [...]
  ~~~~~~

Original report:
	https://github.com/google/syzkaller/issues/6163

>How-To-Repeat:

Run code like this (from a new ATF test):

	int status, fd;
	pid_t pid;
	char * const args[2] = { __UNCONST("ls"), NULL };
	posix_spawn_file_actions_t fa;
	posix_spawnattr_t attr;

	/* get a free file descriptor number */
	fd = open("/dev/null", O_RDONLY);
	ATF_REQUIRE(fd >= 0);
	close(fd);
	RZ(posix_spawn_file_actions_init(&fa));
	// redirect output to /dev/null to not garble atf test results
	RZ(posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, "/dev/null",
	    O_WRONLY, 0));
	// known closed fd
	RZ(posix_spawn_file_actions_addclose(&fa, fd));
	// a random fd we know nothing about (cross fingers!
	RZ(posix_spawn_file_actions_addclose(&fa, fd+1));
	// high fd probably not ever been allocated, likely to trigger
	// a fd_getfile() failure in the kernel, which is another
	// path that originaly caused the fallout in syzkaller
	RZ(posix_spawn_file_actions_addclose(&fa, 560));

	RZ(posix_spawnattr_init(&attr));
	RZ(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETPGROUP));

	RZ(posix_spawn(&pid, "/bin/ls", &fa, &attr, args, NULL));
	RZ(posix_spawn_file_actions_destroy(&fa));

	/* ok, wait for the child to finish */
	RL(waitpid(pid, &status, 0));
	ATF_REQUIRE_MSG((WIFEXITED(status) &&
		WEXITSTATUS(status) == EXIT_SUCCESS),
	    "status=0x%x", status);


and watch it fail with posix_spawn returning EBADF.
There is another path in the kernel (where the parent process does not wait
due to no POSIX_SPAWN_SETPGROUP or the spawn attr pointer being NULL)
that makes the posix_spawn call return 127 in this case.

But it should return 0 (and actually run the client process) in both cases.

>Fix:
upcoming...



Home | Main Index | Thread Index | Old Index