Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20240823142435.GB10433@brightrain.aerifal.cx>
Date: Fri, 23 Aug 2024 10:24:36 -0400
From: Rich Felker <dalias@...c.org>
To: Ryan Rhee <ryanrhee@...oud.com>
Cc: musl@...ts.openwall.com
Subject: Re: Possible bug with fputs and fgets mix

On Thu, Aug 22, 2024 at 08:55:01PM -0700, Ryan Rhee wrote:
> Hello,
> 
> I'm running into a problem when calling fputs on stdout before fgets on stdin 
> results in the program calling the read syscall before the write syscall.
> 
> Example code:
> 
> 	#include <stdio.h>
> 	int main() {
> 		fputs("Enter in something: ", stdout);
> 
> 		char buf[32];
> 		fgets(buf, sizeof buf, stdin);
> 
> 		return 0;
> 	}
> 
> When built with musl-gcc[1] and run, it appears to first poll stdin before writing
> to stdout. For example, when built with musl and ran with strace, the following
> happens:
> 
> 	$ strace ./test
> 	execve("./test", ["./test"], 0x7ffcb2b5a5d0 /* 59 vars */) = 0
> 	arch_prctl(ARCH_SET_FS, 0x405b78)       = 0
> 	set_tid_address(0x405cb0)               = 7306
> 	read(0, hello
> 	"hello\n", 1024)                = 6
> 	ioctl(1, TIOCGWINSZ, {ws_row=38, ws_col=110, ws_xpixel=1210, ws_ypixel=1026}) = 0
> 	writev(1, [{iov_base="Enter in something: ", iov_len=20}, {iov_base=NULL, iov_len=0}], 2Enter in something: ) = 20
> 	exit_group(0)                           = ?
> 	+++ exited with 0 +++
> 	$
> 
> As you can see, the read happens before the writev for some reason.
> 
> When an fflush(stdout) is placed after the fputs, the output appears before the
> input as expected. When the fputs call is replaced with a puts call, the
> program works as expected (yet this is not viable, as puts includes a newline
> after the string is printed, which is undesired).
> 
> Is this a bug with musl, or is a fflush required with fputs unlike with puts?
> 
> I am not subscribed to the mailing list, so please CC me with any replies.

This is entirely expected; the fflush is needed.

The C languages allows, but does not require implementations, to have
a read attempt from an interactive line-buffered device auto-flush all
line-buffered output streams before performing the input. But taking
advantage of this allowance is a really, *really* bad idea in a modern
setting. Not only does it have a large performance cost if lots of
streams are open; it also, in any multi-threaded program, would
require obtaining a lock on each open output stream one-by-one every
time you perform a line-buffered input operation, to ensure that none
of them are line-buffered output with pending unwritten data. Under
any complex usage pattern, this almost surely leads to *deadlock*
scenarios, where lack of forward progress on the input operation
(because it's waiting to check output streams) causes an output
operation to block forever with its lock held. (Think of setups where
some of these streams are pipes or network sockets.)

As such, musl does not use the allowance to do this, and never
implicitly flushes output streams. Portable programs need to be
prepared for this, and explicitly flush whatever output stream(s) they
want flushed before doing input with a prompt like this.

(Note that if the prompt ended in a newline, though, just being
line-buffered would have ensured it got flushed. Explicit flushing is
only needed when it doesn't end in a newline or if you're using a
fully-buffered rather than line-buffered output stream.)

Rich

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.