Question

I'm using this calculator in a public IRC bot. Given that Python uses arbitrary precision by default, this would allow any user to execute something like calc 10000**10000**10000 or calc factorial(1000000) and effectively "kill" the bot.

What I'd like to know is if there is some way of avoiding this. I've tried casting all the terms in the expression to float but float(factorial(1000000) still takes a long time to finish in the Python interpreter, and I'm not sure if a multithreading approach is the right way of doing this.

Was it helpful?

Solution 2

It looks like the float() cast was the solution after all.

First of all, the inverse trigonometric functions don't take values outside of their domain, so they're completely safe and the exception can be caught.

>>> acos(5e100)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: math domain error

The same thing happens with the fmod() function.

The "normal" trigonometric functions don't seem to have any problem with big values unless they're really big, which makes the function return ValueError again.

The rounding functions (ceil(), floor() and round()) work fine and return inf if the value is too big. The same goes for the degrees(), log(), log10(), pow(), sqrt(), fabs(), hypot() and radians() functions.

The hyperbolic trigonometric functions and the exp() function throw OverflowErrors or return inf.

The atan2() function works perfectly fine with big values.

For simple arithmetic operations, the float cast makes the function throw an OverflowError (or an inf) instead of doing the calculation.

>>> float(10) ** float(100) ** float(100) ** float(1000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: (34, 'Numerical result out of range')
>>> float(5e500) * float(4e1000)
inf

Lastly, the problematic factorial() function. All I had to do was redefining the function in an iterative way and adding it to safe_dict.

import sys

def factorial(n):
    fact = 1
    while (n > 0):
        fact = float(fact) * float(n)
        n -= float(1)
        if float(fact) > sys.float_info.max:
            return "Too big"
    return str(fact)

print factorial(50e500)

While this is a very ugly and grossly inefficient way of calculating a factorial, it's enough for my needs. In fact, I think I added a lot of unnecessary float()s.

Now I need to figure out how to put float()s around all the terms in an expression so this happens automatically.

OTHER TIPS

Not really an answer but I'd do it that way. Everything that you are going to run should be ran inside a different process. As far as I know, it's not possible to limit CPU usage or Memory usage of a single thread in a process.

That said, you have to create a new process which as with task to execute what the user entered and write it down to a file for exemple. You could do that with fork, create a new file using the PID and the main process will have to check until the child processes dies. Once the process dies, open the file "cool_calculator_[pid].out" and send it back to IRC.

Quite simple to do I guess.

Then using ulimit or other tools, you can limit the child process or even kill them using the master process. If the file with pid is empty just answer that there was an error or something. I guess you can even write some error like memory exceeded or cpu exceeded etc.

It all really depend on how you want to kill the bad processes.

In the end, your master process will have for job to spawn childs and kill them if needed and send back answers.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top