#1078556 bash: 5.2.21-2.1 to 5.2.21-2.1+b1 breaks printf %.2f .1

Package:
bash
Source:
bash
Description:
GNU Bourne Again SHell
Submitter:
Xavier G.
Date:
2025-09-03 16:47:02 UTC
Severity:
normal
Tags:
#1078556#5
Date:
2024-08-12 13:31:20 UTC
From:
To:
Dear Maintainer,

Using bash 5.2.21-2.1, `printf '%.2f\n' .1` (or any other float value)
produces the expected result.

After upgrade to bash package 5.2.21-2.1+b1, that same statement outputs
either:
- 0.00
- -0.00
- a random float value, with a large number of digits to the left of the
  decimal separator.

I noticed this on my laptop and was able to reproduce the bug using a
Debian Sid-based podman container of mine:
--- 8< ------------------------------------------------------------------------
$ podman run --rm -i kindwolf/unstable-shell:2024080501 <<EOF
bash -c "printf '%.2f\n' 0.1"
bash -c "printf '%.2f\n' 0.1"
bash -c "printf '%.2f\n' 0.1"
bash -c "printf '%.2f\n' 0.1"
dpkg -l bash
apt update > /dev/null 2>&1 && apt install bash > /dev/null 2>&1
dpkg -l bash
bash -c "printf '%.2f\n' 0.1"
bash -c "printf '%.2f\n' 0.1"
bash -c "printf '%.2f\n' 0.1"
bash -c "printf '%.2f\n' 0.1"
EOF
0.10
0.10
0.10
0.10
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name           Version      Architecture Description
+++-==============-============-============-=================================
ii  bash           5.2.21-2.1   amd64        GNU Bourne Again SHell
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name           Version       Architecture Description
+++-==============-=============-============-=================================
ii  bash           5.2.21-2.1+b1 amd64        GNU Bourne Again SHell
6109438027055380006607751[abridged]65066323432594610641285551911364642996224.00
-584981660093124326370703[abridged]70191893948199565939443678471705365315584.00
5363123171977038804941351[abridged]23734249005160240020871689125814289301504.00
5601186210462057897385212[abridged]67104256981197088027398783407112107065344.00
--- 8< ------------------------------------------------------------------------

I got these results on:
- a 2016 Intel CPU
- a 2024 AMD CPU
and thus assume this regression is not hardware-specific.

#1078556#10
Date:
2024-08-13 04:54:32 UTC
From:
To:
Even worse when you try:

LC_ALL=C bash -c "printf '%.0f\n' 1.1"
-
173320084109970710593010218833648600600653512807741911907522891341063031
21173579220274175525236837814294280639028031066660232279567016412626035
72504349300740698967135845237369265827309950484943433842847565886086009
00108443816029639902225792442969167266533784395647837604831111403573151
23152271907670933866106779776348480753882012301707187264841972472015276
82906059388941882622456823311606990281918997616714720593937063030869632
78386805719447880404337602573318220362207722350807613718898914979976644
28313851725493628037341172759699758248359652652951769925935734763979993
72384831001757905989076727323976308919459523189506173723653693696254573
05624425684825377316058770608377615585484863898948223175641726481765716
2932794228265608541767666199199763245234021519977145511706624


or even larger output (3-10 times). Arch report:

https://gitlab.archlinux.org/archlinux/packaging/packages/bash/-/issues/3

#1078556#15
Date:
2024-08-14 02:27:54 UTC
From:
To:
There's a post on bug-bash talking about this issue:
https://lists.gnu.org/archive/html/bug-bash/2024-08/msg00087.html.
It's probably better to consider whatever patch upstream comes up with
rather than what arch linux did.

#1078556#20
Date:
2024-08-16 17:37:32 UTC
From:
To:
severity 1078556 grave
tags 1078556 + upstream patch
forwarded 1078556 https://lists.gnu.org/archive/html/bug-bash/2024-08/msg00087.html
forcemerge 1078556 1078583
thanks

it's crazy to me that these were allowed to stay as normal
(I'd say it's critical, since it "makes unrelated software on the system [...] break";
 call it grave since actual usage of printf %f in system-provided
 programs is pretty rare),
or that bash with this bug was allowed to migrate to testing.

My analysis agrees with the mailing list:
  # valgrind bash -c 'printf %f 2'
  ==4136153== Memcheck, a memory error detector
  ==4136153== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
  ==4136153== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info
  ==4136153== Command: bash -c printf\ %f\ 2
  ==4136153==
  ==4136153== Conditional jump or move depends on uninitialised value(s)
  ==4136153==    at 0x48E5435: __printf_fp_buffer_1.isra.0 (printf_fp.c:227)
  ==4136153==    by 0x48E745B: __printf_fp_l_buffer (printf_fp.c:1122)
  ==4136153==    by 0x48EDE4C: __printf_fp_spec (vfprintf-internal.c:266)
  ==4136153==    by 0x48EDE4C: __printf_buffer (vfprintf-internal.c:999)
  ==4136153==    by 0x48EECA0: __vfprintf_internal (vfprintf-internal.c:1544)
  ==4136153==    by 0x49A16A6: __printf_chk (printf_chk.c:33)
  ==4136153==    by 0x1C00A4: printf (stdio2.h:86)
  ==4136153==    by 0x1C00A4: printf_builtin (printf.def:721)
  ==4136153==    by 0x14E969: execute_builtin (execute_cmd.c:4971)
  ==4136153==    by 0x154B24: execute_builtin_or_function (execute_cmd.c:5485)
  ==4136153==    by 0x154B24: execute_simple_command (execute_cmd.c:4737)
  ==4136153==    by 0x154B24: execute_command_internal (execute_cmd.c:866)
  ==4136153==    by 0x1AEF58: parse_and_execute (evalstring.c:539)
  ==4136153==    by 0x139FD9: run_one_command.isra.0 (shell.c:1473)
  ==4136153==    by 0x138AA1: main (shell.c:763)
  ==4136153==
  ==4136153== Conditional jump or move depends on uninitialised value(s)
  ==4136153==    at 0x48E538C: __printf_fp_buffer_1.isra.0 (printf_fp.c:238)
  ==4136153==    by 0x48E745B: __printf_fp_l_buffer (printf_fp.c:1122)
  ==4136153==    by 0x48EDE4C: __printf_fp_spec (vfprintf-internal.c:266)
  ==4136153==    by 0x48EDE4C: __printf_buffer (vfprintf-internal.c:999)
  ==4136153==    by 0x48EECA0: __vfprintf_internal (vfprintf-internal.c:1544)
  ==4136153==    by 0x49A16A6: __printf_chk (printf_chk.c:33)
  ==4136153==    by 0x1C00A4: printf (stdio2.h:86)
  ==4136153==    by 0x1C00A4: printf_builtin (printf.def:721)
  ==4136153==    by 0x14E969: execute_builtin (execute_cmd.c:4971)
  ==4136153==    by 0x154B24: execute_builtin_or_function (execute_cmd.c:5485)
  ==4136153==    by 0x154B24: execute_simple_command (execute_cmd.c:4737)
  ==4136153==    by 0x154B24: execute_command_internal (execute_cmd.c:866)
  ==4136153==    by 0x1AEF58: parse_and_execute (evalstring.c:539)
  ==4136153==    by 0x139FD9: run_one_command.isra.0 (shell.c:1473)
  ==4136153==    by 0x138AA1: main (shell.c:763)
  ==4136153==
  -nan==4136153==
  ==4136153== HEAP SUMMARY:
  ==4136153==     in use at exit: 53,517 bytes in 422 blocks
  ==4136153==   total heap usage: 459 allocs, 37 frees, 57,384 bytes allocated
  ==4136153==
  ==4136153== LEAK SUMMARY:
  ==4136153==    definitely lost: 0 bytes in 0 blocks
  ==4136153==    indirectly lost: 0 bytes in 0 blocks
  ==4136153==      possibly lost: 0 bytes in 0 blocks
  ==4136153==    still reachable: 53,517 bytes in 422 blocks
  ==4136153==         suppressed: 0 bytes in 0 blocks
  ==4136153== Rerun with --leak-check=full to see details of leaked memory
  ==4136153==
  ==4136153== Use --track-origins=yes to see where uninitialised values come from
  ==4136153== For lists of detected and suppressed errors, rerun with: -s
  ==4136153== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
and this clearly shows that bash is actually using a normal-length
double (strtod()):
  strchr("#'-+ 0", 'f')                                                                                     = nil
  strchr("hjlLtz", 'f')                                                                                     = nil
  __errno_location()                                                                                        = 0x7fdd273a86c8
  strtod("2", "")                                                                                           = 2.000000
  strlen("%f")                                                                                              = 2
  malloc(1024)                                                                                              = 0x55ce477f5ee0
  memcpy(0x55ce477f5ee0, "%", 1)                                                                            = 0x55ce477f5ee0
  clearerr(0x7fdd275825c0, 76, 0, 1)                                                                        = 0xfbad2284
  __printf_chk(1, 0x55ce477f5ee0, 0, 1)                                                                     = 2094
and if it's giving that to %Lf then that's obviously wrong and the
other half of the long double is uninitialised.

This bug is only made visible because the configuration script
is /also/ broken, since bookworm bash uses strtold().
This was identified by upstream and a patch is given in
https://lists.gnu.org/archive/html/bug-bash/2024-08/msg00090.html

I've verified that on sid with 5.2.21-2.1 prior to this patch,
strtold() is not detected (and this triggers the bug, per valgrind):
  checking whether strtold is declared... yes
  checking for broken strtold... yes
but with this patch strtold() is detected
  checking whether strtold is declared... yes
  checking for broken strtold... no
and the bug is not triggered
  # valgrind ./debian/bash/usr/bin/bash -c 'printf %f 2'
  ==148139== Memcheck, a memory error detector
  ==148139== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
  ==148139== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info
  ==148139== Command: ./debian/bash/usr/bin/bash -c printf\ %f\ 2
  ==148139==
  2.000000==148139==
  ==148139== HEAP SUMMARY:
  ==148139==     in use at exit: 53,735 bytes in 425 blocks
  ==148139==   total heap usage: 460 allocs, 35 frees, 57,631 bytes allocated
  ==148139==
  ==148139== LEAK SUMMARY:
  ==148139==    definitely lost: 0 bytes in 0 blocks
  ==148139==    indirectly lost: 0 bytes in 0 blocks
  ==148139==      possibly lost: 0 bytes in 0 blocks
  ==148139==    still reachable: 53,735 bytes in 425 blocks
  ==148139==         suppressed: 0 bytes in 0 blocks
  ==148139== Rerun with --leak-check=full to see details of leaked memory
  ==148139==
  ==148139== For lists of detected and suppressed errors, rerun with: -s
  ==148139== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

I'm attaching upstream's configure-strtold-check (reformatted to unified
debian/patches/-compatible format, tested against 5.2.21-2.1).

Best,

#1078556#37
Date:
2024-11-11 11:39:03 UTC
From:
To:
A patch is available, and this bug is listed as "grave". However, it's
still not fixed yet.
Any reason that the patch can not applied and this bug can not be closed?
Thank you very much.

#1078556#42
Date:
2024-11-11 13:14:50 UTC
From:
To:
bash-5.3~alpha1$ grep strtold configure.ac
                [[long double r; char *foo, *bar; r = strtold(foo, &bar);]]

checking whether strtold is declared... yes
checking for broken strtold... no

(sid bash)root@tarta:/srv/bash-5.3~alpha1# ./build-bash/bash   -c 'printf %f\\n 2'
2.000000
(sid bash)root@tarta:/srv/bash-5.3~alpha1# ./build-static/bash -c 'printf %f\\n 2'
2.000000

#1078556#49
Date:
2024-12-06 08:11:55 UTC
From:
To:
Fixed.
#1078556#62
Date:
2025-09-03 16:45:34 UTC
From:
To: