Tcl Source Code

View Ticket
Login
Ticket UUID: cea2c63928b19c7ade84cbb5304cc2a33514912a
Title: Address -fsanitize=shift-exponent complaints in TclParseNumber()
Type: Patch Version: 8.6.12
Submitter: chrstphrchvz Created on: 2021-12-06 08:17:56
Subsystem: 11. Conversions from String Assigned To: jan.nijtmans
Priority: 5 Medium Severity: Minor
Status: Closed Last Modified: 2021-12-07 21:19:48
Resolution: Fixed Closed By: jan.nijtmans
    Closed on: 2021-12-07 21:19:48
Description:

(Note that I do not observe Tcl behaving unexpectedly despite this complaint.)

The C standard (at least since C89) says that shifting by the width of the left operand (after promotion) or more is undefined behavior. No exception is made for the left operand being 0. So I believe UBSan (-fsanitize=shift-exponent) is correct to complain about certain shifts in TclParseNumber() (generic/tclStrToD.c) when parsing numbers containing enough leading zeros. Example complaints for 64-bit Tcl_WideUInt (note that they may only appear once over the lifetime of the interpreter):

# tcl/generic/tclStrToD.c:714:27: runtime error: shift exponent 66 is too large for 64-bit type 'Tcl_WideUInt' (aka 'unsigned long')
scan "[join [lrepeat 22 0] {}]1" {%i}

# tcl/generic/tclStrToD.c:714:27: runtime error: shift exponent 66 is too large for 64-bit type 'Tcl_WideUInt' (aka 'unsigned long') scan "[join [lrepeat 22 0] {}]1" {%o}

# tcl/generic/tclStrToD.c:830:42: runtime error: shift exponent 64 is too large for 64-bit type 'Tcl_WideUInt' (aka 'unsigned long') scan "[join [lrepeat 16 0] {}]1" {%x}

# tcl/generic/tclStrToD.c:872:42: runtime error: shift exponent 64 is too large for 64-bit type 'Tcl_WideUInt' (aka 'unsigned long') scan "[join [lrepeat 64 0] {}]1" {%b}

# tcl/generic/tclStrToD.c:1205:23: runtime error: shift exponent 64 is too large for 64-bit type 'Tcl_WideUInt' (aka 'unsigned long') expr "0b[join [lrepeat 64 0] {}]"

# tcl/generic/tclStrToD.c:1226:23: runtime error: shift exponent 64 is too large for 64-bit type 'Tcl_WideUInt' (aka 'unsigned long') expr "0x[join [lrepeat 16 0] {}]"

# tcl/generic/tclStrToD.c:1248:28: runtime error: shift exponent 66 is too large for 64-bit type 'Tcl_WideUInt' (aka 'unsigned long') expr "[join [lrepeat 23 0] {}]"

# tcl/generic/tclStrToD.c:1248:28: runtime error: shift exponent 66 is too large for 64-bit type 'Tcl_WideUInt' (aka 'unsigned long') expr "0o[join [lrepeat 22 0] {}]"


To address these, I would replace e.g.:

octalSignificandWide = (octalSignificandWide << shift) + (c - '0')

with:

/*
 * When the significand is 0, it is possible for the
 * amount to be shifted to equal or exceed the width
 * of the significand. Do not shift when the
 * significand is 0 to avoid undefined behavior. 
 */

if (octalSignificandWide != 0) { octalSignificandWide <<= shift; } octalSignificandWide += c - '0';

I also suggest rewording the current comment for the too-large shift check (which appeared in [57a68b7c98]), because it is undefined behavior (not merely “de facto nonportable”) to shift, not just by more than width of a value, but also by the exact width of a value.

See attached patch.

User Comments: jan.nijtmans added on 2021-12-07 21:19:48:

Thanks for the report! Should be fixed now in core-8-6-branch, core-8-branch and trunk


Attachments: