Skip to content

Conversation

@erak
Copy link
Contributor

@erak erak commented Mar 28, 2018

Fixes #3327
Fixes #3759

bigint denominator = pow(m_value.denominator(), exponent);

// Limit size to 4096 bits
if (numerator >= pow(bigint(2), rationalMaxBits) - 1)
Copy link
Contributor

Choose a reason for hiding this comment

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

This pow should be evaluated in the const variable. Also you can use shift instead of pow for power-of-2.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it is enough to check for the value after it has ben evaluated. It should be guessed before evaluating with pow.

One of the main problems here is the DoS vector (which is just annoying for a user, for example running the compiler in the browser).

bigint denominator = pow(m_value.denominator(), exponent);

// Limit size to 4096 bits
if (numerator >= pow(bigint(2), rationalMaxBits) - 1)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it is enough to check for the value after it has ben evaluated. It should be guessed before evaluating with pow.

One of the main problems here is the DoS vector (which is just annoying for a user, for example running the compiler in the browser).

@erak
Copy link
Contributor Author

erak commented Mar 28, 2018

@axic I've started testing with two examples of those DoS cases and they're not hanging anymore. After the limit is reached, the function is still called, but m_value.denominator() is constant from there on and calling pow() results in the same large number.
Currently the error messages don't help to understand the issue I guess, but there might be some options for making them richer.

namespace
{

uint32_t static const exponentLimit = 9999;
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't really use uint32_t explicitly in the rest of the codebase. I think I almost exclusively use size_t.

Copy link
Contributor

Choose a reason for hiding this comment

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

size_t or unsigned is most common in the codebase


TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
{
{
Copy link
Contributor

Choose a reason for hiding this comment

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

Surplus whitespace.

// parse the exponent and check limits
bigint exp = bigint(string(expPoint + 1, _literal.value().end()));

if (exp > numeric_limits<int32_t>::max() || exp < numeric_limits<int32_t>::min())
Copy link
Contributor

Choose a reason for hiding this comment

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

This can now be removed.

if (exp > numeric_limits<int32_t>::max() || exp < numeric_limits<int32_t>::min())
return make_tuple(false, rational(0));

if (abs(exp) >= exponentLimit)
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps it would be better to re-use the same helper in all functions that compute exp for rational numbers. Just pass the base and the exponent, and it will determine if the resulting number still fits our precision.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, if we know the base, we can better estimate the size of the resulting number.

@chriseth
Copy link
Contributor

Please also add a test for bit-shifts.

@erak erak self-assigned this Apr 3, 2018
@axic
Copy link
Contributor

axic commented Apr 4, 2018

After the limit is reached, the function is still called, but m_value.denominator() is constant from there on and calling pow() results in the same large number.

For those test cases it probably is the case, however for the case of doing a very large exp, it will just "hang" or take a lot of time.

Please add such a test case too.

@axic
Copy link
Contributor

axic commented Apr 4, 2018

Maybe useful to look at the test cases of #3348 (an earlier attempt of the same problem).

Additionally, some random notes:

  • uint a = 1E1000000 ** 1E1000000;
  • 100000000000E1250 = 1E1261

@erak erak force-pushed the rationalNumberLimit branch 5 times, most recently from 04fa673 to eeaca4a Compare April 6, 2018 16:32
uint a;
uint b;
a = 1e9999;
b = 13456232453246423445233516816351681345623245324642344523351681635168.7161354161363516163161354168154E1200;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Indentation

if(_base > baseMax)
return false;

bigfloat bitsNeeded = bigfloat(_exp) * ceil(log10(bigfloat(_base + 1)));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe log2 instead of log10?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was a defect ;) log10 is what I wanted but this formula calculated the number of digits and not the number of bits. I've added another formula which estimates the minimum number of bits needed.

{
using boost::multiprecision::log10;
using boost::multiprecision::ceil;
using bigfloat = boost::multiprecision::number<boost::multiprecision::cpp_dec_float<50>>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Does that mean it allows up to 50 decimal digits? What happens if _base + 1 is larger than 50 decimal digits?

According to boost these are also defined:

typedef number<cpp_dec_float<50> > cpp_dec_float_50;
typedef number<cpp_dec_float<100> > cpp_dec_float_100;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@axic The precision will be reduced after creating cpp_dec_float_50 with base + 1. That means a if base has more than 50 digits, any additional digit will be represented with a zero. This does not affect the size of it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a test for this? Boost had some bugs where an overflow (with shifts) caused the entire value to be 0.

@erak erak force-pushed the rationalNumberLimit branch from eeaca4a to 5d11596 Compare April 10, 2018 13:00
return false;

bigfloat digitsNeeded = bigfloat(_exp) * ceil(log10(bigfloat(_base + 1)));
bigfloat bitsNeeded = ceil((digitsNeeded - 1) * log2(bigfloat(10)));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why calculate digitsNeeded as an intermediate result?
You might as well just use log2 directly to get bitsNeeded instead of multiplying with log2(10) later...

log2(10) * log10(a) = log2(a)

I don't think using 10 as base has any advantage for choosing the correct way of rounding things...

What you want to have in the end is: 2 ^ 4096 - 1 >= _base^_exp or, since it's integers:
2^4096 > _base^_exp.

Hence you want log2(2^4096) > log2(_base^_exp).
Hence 4096 > _exp * log2(_base).

You only need to compensate for potential rounding error in log2(_base).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Safest thing would probably be bitsNeeded = _exp * (floor(log2(_base)) + 1).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Although you may want to treat _base <= 1 separately :-).

Copy link
Collaborator

@ekpyron ekpyron Apr 11, 2018

Choose a reason for hiding this comment

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

Also: do you only use fitsPrecision for _base >= 0? You'd probably need to treat negative _base differently...
And probably also add tests for that.

Copy link
Contributor

@axic axic left a comment

Choose a reason for hiding this comment

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

Also needs a test for a larger base with a smaller exponent, which still exceeds the limit.

@argotorg argotorg deleted a comment from erak Apr 12, 2018
@ekpyron ekpyron force-pushed the rationalNumberLimit branch from 7cd7fd9 to fc19981 Compare April 13, 2018 09:43
@ekpyron ekpyron self-assigned this Apr 13, 2018

size_t const bitsMax = 4096;
bigint const baseMax = (bigint(2) << bitsMax) - 1;
if(_base > baseMax)
Copy link
Contributor

Choose a reason for hiding this comment

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

Space after if

namespace
{

bool fitsPrecision(bigint const _base, bigint const _exp)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not const&?

@ekpyron ekpyron force-pushed the rationalNumberLimit branch 5 times, most recently from 788c456 to a3c08f2 Compare April 13, 2018 12:50

size_t const bitsMax = 4096;

auto mostSignificantBit = msb(_mantissa);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add an explicit type.

@ekpyron ekpyron force-pushed the rationalNumberLimit branch 10 times, most recently from fb462eb to 43cb2b0 Compare April 13, 2018 15:12
namespace
{

// checks whether (_base ^ _exp) fits into 4096 bits
Copy link
Contributor

Choose a reason for hiding this comment

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

I think ** would be better here. Also we generally use /// ("javadoc") to document functions.

@ekpyron ekpyron force-pushed the rationalNumberLimit branch from 43cb2b0 to 93b3507 Compare April 13, 2018 15:38
if (!_base)
return true;

// _base > 0
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not turn this into an assert and remove the one above?


size_t const bitsMax = 4096;

auto mostSignificantBaseBit = msb(_base);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use an explicit type here.

@ekpyron ekpyron force-pushed the rationalNumberLimit branch 3 times, most recently from 0573096 to 49186fb Compare April 13, 2018 16:33
@ekpyron
Copy link
Collaborator

ekpyron commented Apr 13, 2018

OK - boost::multiprecision::msb is only introduced with boost 1.55 and trusty is still on boost 1.54 (somehow I thought it'd be at least 1.57) :-D.

I'll try to find a good workaround over the weekend.

The ci/circleci: test_x86 result Too long with no output (exceeded 10m0s) is not a good sign as well, though...

@chriseth
Copy link
Contributor

@ekpyron were you able to find something? Otherwise I will pick this up later.

@ekpyron
Copy link
Collaborator

ekpyron commented Apr 16, 2018

@chriseth I was just about to look into it. Unfortunately I think we may have to use floor(log2(...)) instead of msb again and therefore also deal with some bigfloat type... I'll check whether there's a better option.

@ekpyron ekpyron force-pushed the rationalNumberLimit branch 3 times, most recently from b9f577c to dfe8c01 Compare April 16, 2018 09:44
@ekpyron ekpyron force-pushed the rationalNumberLimit branch from dfe8c01 to 33fbf88 Compare April 16, 2018 09:46
@ekpyron
Copy link
Collaborator

ekpyron commented Apr 16, 2018

@chriseth This now contains a workaround for boost < 1.55, which is a naive (but therefore sufficiently simple) implementation of msb. We have to keep an eye on the running times of the tests, though.

@ekpyron
Copy link
Collaborator

ekpyron commented Apr 16, 2018

Now the ci/circleci: test_x86 test works again as well and doesn't seem to take longer than before the PR - not sure what the problem was before...

@chriseth chriseth merged commit 533d085 into develop Apr 16, 2018
@axic axic deleted the rationalNumberLimit branch April 17, 2018 10:23
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.

5 participants