HexBrain

curl · Report #3972196

34: `curl_mprintf` reads `double` for documented `long double` conversions — uninitialized value disclosure

Informativestatus
3upvotes
28 Aug 2026disclosed

How it was found

Summary

docs/libcurl/curl_mprintf.md documents that the L length modifier makes floating-point conversions (%f, %e, %E, %g, %G) accept a long double argument. The implementation in lib/mprintf.c recognizes L and sets FLAGS_LONGDOUBLE, but the conversion switch (%f/%e/%E/%g/%G) unconditionally selects MTYPE_DOUBLE. The argument is then extracted with va_arg(arglist, double).

On x86-64 (System V ABI), variadic double and long double use different register-save slots. When a caller passes a long double as documented, va_arg(..., double) reads an uninitialized floating-point register slot. Valgrind confirms an uninitialized-value read inside out_double().

Affected commit: c2676bf9e639e31e44b12a2cc82cf2046231b056 (current HEAD at time of report).

Root cause

  1. lib/mprintf.c:261-263 — parse_flags() sets FLAGS_LONGDOUBLE for L.
  2. lib/mprintf.c:397-415 — parse_conversion() maps f/e/E/g/G to MTYPE_DOUBLE without checking FLAGS_LONGDOUBLE.
  3. lib/mprintf.c:617-619 — parsefmt() extracts the argument as va_arg(arglist, double).
  4. lib/mprintf.c:100-108 — struct va_input has no long double member. The enum value MTYPE_LONGDOUBLE exists but is never assigned.

The bug affects all curl_mprintf family functions: curl_mprintf, curl_mfprintf, curl_msprintf, curl_msnprintf, curl_maprintf, and their va_list variants.

Reproduction

Observed output (x86-64 Linux)

sizeof(double)=8 sizeof(long double)=16 DBL_MANT_DIG=53 LDBL_MANT_DIG=64
%.17Lf curl=0.00000000000000000 libc=1.23456789012345679 match=no
%.17Le curl=6.95335579603750720e-310 libc=1.23456789012345679e+00 match=no
%.17LE curl=6.95335579603750720E-310 libc=1.23456789012345679E+00 match=no
%.17Lg curl=6.9533557960375072e-310 libc=1.2345678901234568 match=no
%.17LG curl=6.9533557960375072E-310 libc=1.2345678901234568 match=no
mismatches=5/5
RESULT: VULNERABLE
Conditional jump or move depends on uninitialised value(s)
   at 0x...: out_double (mprintf.c:681)
 Uninitialised value was created by a stack allocation

Suggested fix

In parse_conversion(), when FLAGS_LONGDOUBLE is set alongside a floating-point conversion specifier, assign MTYPE_LONGDOUBLE instead of MTYPE_DOUBLE. Add a long double member to struct va_input, extract with va_arg(arglist, long double), and pass it through the output formatting path.

Impact

A caller following the documented API on x86-64 gets a stale/uninitialized floating-point value formatted instead of the supplied long double. Valgrind confirms the value is uninitialized.

No in-tree production usage of %L floating-point conversions was found in curl or libcurl. The format string and argument originate with the calling application, not from remote input. The practical security impact depends on whether a downstream application formats a long double via curl_mprintf and exposes the result across a trust boundary. I did not identify such a scenario and leave the security classification to the curl team.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original