curl · Report #3972196
34: `curl_mprintf` reads `double` for documented `long double` conversions — uninitialized value disclosure
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
lib/mprintf.c:261-263—parse_flags()setsFLAGS_LONGDOUBLEforL.lib/mprintf.c:397-415—parse_conversion()mapsf/e/E/g/GtoMTYPE_DOUBLEwithout checkingFLAGS_LONGDOUBLE.lib/mprintf.c:617-619—parsefmt()extracts the argument asva_arg(arglist, double).lib/mprintf.c:100-108—struct va_inputhas nolong doublemember. The enum valueMTYPE_LONGDOUBLEexists 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.