Package: libc6
Version: 2.36-9+deb12u1
Severity: normal
X-Debbugs-Cc: tim.ba...@hitachivantara.com

Dear Maintainer,

Consider this test case:

#include <stdio.h>
int main(void) {
        char buf[3];
        int n;
        snprintf(buf, sizeof buf, "%s%n", "foobar", &n);
        printf("%d\n", n);
}

I believe it ought, as specified, to output "2"; glibc prints "6". The
difference is in whether %n keeps counting when snprintf has no room for
more output.

I quote below from a C23 draft[1] but the salient points are essentially
unchanged since C99. The wording in POSIX[2] is also equivalent, and no
relevant parts are flagged as deliberate deviations from the C standard.

[1] <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3096.pdf>
[2] <https://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html>

The %n conversion specifier is defined under fprintf (7.23.6.1/8 in the
abovementioned draft):

    The number of characters written to the output stream so far by this
    call to fprintf is stored into the integer object pointed to by the
    argument.

For snprintf we then have (7.23.6.5/2):

    The snprintf function is equivalent to fprintf, except that the
    output is written into an array (specified by argument s) rather
    than to a stream. If n is zero, nothing is written, and s may be
    a null pointer. Otherwise, output characters beyond the n-1st are
    discarded rather than being written to the array,

Assuming the text accurately reflects WG14's intent, the only consistent
interpretation here, it seems to me, is that: "nothing is written" or
"discarded rather than being written" implies not "written to the output
[array]". Therefore any bytes discarded ought not to be counted in the
value assigned by %n. It follows that that value should never exceed the
buffer length, and should be strictly less unless the latter is 0.

(Internally, glibc may implement snprintf by output to an in-memory
stream, as if using fmemopen, but that is purely an implementation
detail. It is not how C/POSIX define snprintf.)

The same obviously applies identically to vsnprintf; and similarly to
[v]swprintf, although the wording (7.31.2.3/2) is slightly different.

A related question also arises when output to a stream suffers an error.
In the event of an output error, glibc continues processing the format
string, correctly since fprintf "returns when the end of the format
string is encountered" (7.23.6.1/2); but what should %n do in that case?
It is much less clear whether "written" ought to be interpreted as
"successfully written". For example:

        FILE *f = fopen("/dev/full", "w");
        setbuf(f, 0);
        fprintf(f, "%d%n", 12345, &n);

Should %n assign 5, or 0? There is no such difficulty of interpretation
with snprintf etc., as the text is clear that the excess characters are
not written at all.

-- System Information:
Debian Release: 12.1
  APT prefers stable
  APT policy: (500, 'stable')
Architecture: amd64 (x86_64)

Kernel: Linux 6.1.0-10-amd64 (SMP w/12 CPU threads; PREEMPT)
Locale: LANG=en_GB.UTF-8, LC_CTYPE=en_GB.UTF-8 (charmap=UTF-8), 
LANGUAGE=en_GB.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

Versions of packages libc6 depends on:
ii  libgcc-s1  12.2.0-14

Versions of packages libc6 recommends:
ii  libidn2-0  2.3.3-1+b1

Versions of packages libc6 suggests:
ii  debconf [debconf-2.0]  1.5.82
pn  glibc-doc              <none>
ii  libc-l10n              2.36-9+deb12u1
pn  libnss-nis             <none>
pn  libnss-nisplus         <none>
ii  locales                2.36-9+deb12u1

-- debconf information:
  glibc/upgrade: true
  glibc/kernel-too-old:
  glibc/kernel-not-supported:
  glibc/restart-failed:
  glibc/disable-screensaver:
  libraries/restart-without-asking: false
  glibc/restart-services:

Reply via email to