Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove unnecessary workaround for fclose() on OpenBSD #537

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

guijan
Copy link
Contributor

@guijan guijan commented Dec 7, 2023

Test program:

 #include <sys/utsname.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <fcntl.h>

static int closed;

int
close(int unused)
{
	closed = 1;
	return 0;
}

int main()
{
	struct utsname os;
	int fd;
	FILE *file;
	char *closed_str[] = {" not", ""};
	uname(&os);
	if ((fd = open("/dev/null", O_RDONLY)) == -1)
		return 1;
	if ((file = fdopen(fd, "rb")) == NULL)
		return 1;
	printf("Operating system: %s/%s %s %s\n", os.sysname, os.machine,
	    os.release, os.version);
	closed = 0;
	fclose(stdin);
	printf("close()%s called on stdin\n", closed_str[closed]);
	closed = 0;
	fclose(file);
	printf("close()%s called on nonstd stream\n", closed_str[closed]);
}

Output:

$ ./a.out
Operating system: OpenBSD/amd64 7.4 GENERIC.MP#0
close() not called on stdin
close() not called on nonstd stream

I tried an earlier version of the test program (it only tests stdin) on a bunch of others too to see if it's a thing elsewhere:

$ ./a.out
Operating system: SunOS/i86pc 5.11 omnios-r151046-1c2c17cce7
close() not called.

$ ./a.out
Operating system: Linux/x86_64 6.1.55-0-virt #1-Alpine SMP PREEMPT_DYNAMIC Sun, 24 Sep 2023 23:14:02 +0000
close() not called.

$ ./a.out
Operating system: FreeBSD/amd64 13.2-RELEASE FreeBSD 13.2-RELEASE GENERIC-BWN_GPL_PHY
close() not called.

$ ./a.out
Operating system: NetBSD/i386 9.3 NetBSD 9.3 (GENERIC) #0: Thu Aug  4 15:30:37 UTC 2022  [email protected]:/usr/src/sys/arch/i386/compile/GENERIC
close() not called.

@rofl0r
Copy link
Owner

rofl0r commented Dec 7, 2023

i'd like to believe this has been fixed, but the only certain way to test it imo is to set a breakpoint on libc close and then call fclose() - preferably on a FILE* that's been opened with fopen().

//edit: and in case it isn't clear, see whether the breakpoint is hit

//edit2: i just realized we actually had a configure check for this behaviour - which will (or should) make this work for any OpenBSD version - what's the disadvantage of keeping it ? (apart from having a runtime test that's not cross-compile compatible)

@guijan
Copy link
Contributor Author

guijan commented Dec 7, 2023

Turns out the test in proxychains-ng hasn't worked in 8 years. See this commit: https://cvsweb.openbsd.org/src/lib/libc/stdio/fclose.c?rev=1.10&content-type=text/x-cvsweb-markup

let internal calls resolve directly and
not be overridable

I modified the earlier test program to do nothing but fclose() stdin and a FILE it opened itself:

#include <sys/utsname.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

int main()
{
	struct utsname os;
	int fd;
	FILE *file;
	char *closed_str[] = {" not", ""};
	uname(&os);
	if ((fd = open("/dev/null", O_RDONLY)) == -1)
		return 1;
	if ((file = fdopen(fd, "rb")) == NULL)
		return 1;
	printf("Operating system: %s/%s %s %s\n", os.sysname, os.machine,
	    os.release, os.version);
	fclose(stdin);
	fclose(file);
}

Then I ran it in gdb with breakpoint in close().
close()
Yes, the code stepped into close() on OpenBSD.

NetBSD fclose() also calls close():
netbsd

Also, POSIX says:
https://pubs.opengroup.org/onlinepubs/9699919799.2008edition/

The fclose() function shall perform the equivalent of a close() on the file descriptor

I suppose whatever assumption proxychains-ng made need to be fixed, I just noticed the OpenBSD test wasn't detecting anything and didn't look further before making this PR, and now I'm going to bed.

proxychains-ng didn't call fclose() in a particular FILE on OpenBSD if a
test in the Makefile noticed that OpenBSD's fclose() called close() on
the underlying fd of the FILE.

The test hasn't worked for 8 years because an OpenBSD commit prevented
the test from overriding the libc close():
https://cvsweb.openbsd.org/src/lib/libc/stdio/fclose.c?rev=1.10&content-type=text/x-cvsweb-markup
>let internal calls resolve directly and not be overridable
For all this time, the workaround wasn't doing anything.

Additionally, behavior equivalent to calling close() on a fd is
mandatory in POSIX:
https://pubs.opengroup.org/onlinepubs/9699919799.2008edition/
>The fclose() function shall perform the equivalent of a close()
>on the file descriptor

And finally, at least NetBSD 9.3 also uses the close() function to do
the same, but there's no workaround, and no reported misbehavior when
running on NetBSD to work around.

Even if the test did work, all the workaround appears to do is leak a
FILE.
@guijan
Copy link
Contributor Author

guijan commented Dec 8, 2023

After reading a bit more of the source code, I don't see how whether the underlying fd of a FILE is closed by fclose() has any effect on the code. The only thing the workaround seems to do is leak a FILE on OpenBSD if the compile-time test succeeds.

Inside libproxychains.c's function get_chain_data(), the FILE *file variable is only used 3 times:

file = fopen(...);
fgets(..., file);
#ifndef BROKEN_FCLOSE
fclose(file);
#endif

There's no fdopen() or fileno() or anything of the sort.

I've changed the commit message and force-pushed a new version, the code changes are the same as before.

@guijan guijan changed the title remove outdated workaround for OpenBSD fclose() calling close() remove unnecessary workaround for fclose() on OpenBSD Dec 8, 2023
@rofl0r
Copy link
Owner

rofl0r commented Dec 8, 2023

see the original research here #95 and upstream bug report here:
http://marc.info/?l=openbsd-bugs&m=145280872431093&w=2

i'm aware that the current bugfix creates an fd leak, but it was deemed better than crashing.
you still didn't answer the question why the configure check seems like something that needs to be removed.

@guijan
Copy link
Contributor Author

guijan commented Dec 8, 2023

It's code that isn't doing anything, the close() function that libc calls hasn't been overridable in OpenBSD for a long time.

@rofl0r
Copy link
Owner

rofl0r commented Dec 9, 2023

so to conclude, recent openbsd runs the configure check but doesn't activate the hack, whereas old openbsd versions that need the hack properly recognize it and activate it (unless a crosscompiler is used) ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants