Skip to content

Fix Int overflow in binary number lengths - #1760

Merged
stevedlawrence merged 1 commit into
apache:mainfrom
stevedlawrence:daffodil-3088-binary-number-int-overflow
Nov 6, 2026
Merged

stevedlawrence merged 1 commit into
apache:mainfrom
stevedlawrence:daffodil-3088-binary-number-int-overflow

Conversation

@stevedlawrence

Copy link
Copy Markdown
Member

getBitLength currently returns an Int, with implementations often using Long.toInt. This can lead to silent overflow/truncation (e.g. with dfdl:length runtime expressions). To resolve this, getBitLength is modified to return a Long so that no truncation needs to happen.

Ultimately these lengths need to be converted to integers for the various IO functions, so additional functions are added to ensure we also check the lengths prior to casting to an int. For types that have unlimited lengths (e.g. xs:integer, xs:decimal, packed decimals) a new tunable is added to restrict binary number lengths to reasonable values. This tunable has an xs:int base so if the checks pass the cast from long to int should never overflow.

Also replaces the various packed DelimiterUnparsers with the packed MinimumLengthUnparsers. The DelimiterUnparsers have getBitLength return 0 which fails the checks. The MimumLengthUnparers calculate the actual length of the packed fields, which can then be checked against the tunable.

DAFFODIL-3088

@stevedlawrence
stevedlawrence force-pushed the daffodil-3088-binary-number-int-overflow branch from 314072d to 40096aa Compare October 19, 2026 16:35

@jadams-tresys jadams-tresys left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Just a couple places where we are converting back to .toInt that I want to make sure aren't a problem


try {
val int = toPrimType(context, dis.getByteArray(nBits, start))
val int = toPrimType(context, dis.getByteArray(nBits.toInt, start))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't know if it matters or not, but there is still potential for an overflow/truncation here. For dis.getUnsignedLong it looks like there is a check to ensure that nBits is between 1 and 64, but there is no check for getByteArray

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it doesn't really matter in this specific case. The above checkBitLength will check that nBits fits within an int, so this nBits.toInt call shouldn't overflow and should create a valid int. And getByteArray works for any int value, we don't place any limits on how big of a byte array you can get, so shouldn't cause problems as long as it gets a valid int.


if (nBits > 64) {
dos.putBigInt(asBigInt(value), nBits, primNumeric.isSigned, finfo)
dos.putBigInt(asBigInt(value), nBits.toInt, primNumeric.isSigned, finfo)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this overflow as well or does checkMaxWidth ensure that nBits would be able to be stored in an int?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, checkMaxWidth ensures nBits.toInt won't overflow

if (binaryBooleanTrueRep.isDefined) binaryBooleanTrueRep.getULong
else ~binaryBooleanFalseRep
putNumber(dos, trueRep, nBits, state)
putNumber(dos, trueRep, nBits.toInt, state)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these putNumber calls need to worry about integer overflow/truncation?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I can tell, putNumber, or the functions they call, will make sure the number it is putting fits withing th nBits parameter. The check that were we sometimes missing was that the original nBits Long value can be safely converted to an Int. In this specific case, we check tht above with the nBits < 1 || nBits > 32, so nBits.toInt should be safe here.

@olabusayoT olabusayoT left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some questions about the toInts, and requests for comments...but looks good overall

getBitLength currently returns an Int, with implementations often using
Long.toInt. This can lead to silent overflow/truncation (e.g. with
dfdl:length runtime expressions). To resolve this, getBitLength is
modified to return a Long so that no truncation needs to happen.

Ultimately these lengths need to be converted to integers for the
various IO functions, so additional functions are added to ensure we
also check the lengths prior to casting to an int. For types that have
unlimited lengths (e.g. xs:integer, xs:decimal, packed decimals) a new
tunable is added to restrict binary number lengths to reasonable values.
This tunable has an xs:int base so if the checks pass the cast from long
to int should never overflow.

Also replaces the various packed DelimiterUnparsers with the
packed MinimumLengthUnparsers. The DelimiterUnparsers have getBitLength
return 0 which fails the checks. The MimumLengthUnparers calculate the
actual length of the packed fields, which can then be checked against
the tunable.

DAFFODIL-3088
@stevedlawrence
stevedlawrence force-pushed the daffodil-3088-binary-number-int-overflow branch from 40096aa to bfaa50e Compare November 6, 2026 18:43

@olabusayoT olabusayoT left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@stevedlawrence
stevedlawrence merged commit f0b9906 into apache:main Nov 6, 2026
11 checks passed
@stevedlawrence
stevedlawrence deleted the daffodil-3088-binary-number-int-overflow branch November 6, 2026 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants