Web: Prevent memory bomb when calling commands with an URL to a page sending crafted requests.

This commit is contained in:
Valentin Lorentz 2013-08-09 12:15:54 +02:00
parent 918b8a3c01
commit d8a4ef8421
1 changed files with 55 additions and 5 deletions

View File

@ -37,6 +37,7 @@ import supybot.conf as conf
import supybot.utils as utils import supybot.utils as utils
from supybot.commands import * from supybot.commands import *
import supybot.plugins as plugins import supybot.plugins as plugins
import supybot.commands as commands
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
import supybot.callbacks as callbacks import supybot.callbacks as callbacks
from supybot.i18n import PluginInternationalization, internationalizeDocstring from supybot.i18n import PluginInternationalization, internationalizeDocstring
@ -68,16 +69,55 @@ class Title(HTMLParser.HTMLParser):
if name in self.entitydefs: if name in self.entitydefs:
self.title += self.entitydefs[name] self.title += self.entitydefs[name]
class DelayedIrc:
def __init__(self, irc, msg):
self._irc = irc
self._msg = msg
self._replies = []
def reply(self, *args, **kwargs):
self._replies.append(callbacks.reply(self._msg, *args, **kwargs))
def error(self, *args, **kwargs):
self._replies.append(callbacks.error(self._msg, *args, **kwargs))
def __getattr__(self, name):
assert name not in ('reply', 'error', '_irc', '_msg', '_replies')
return getattr(self._irc, name)
def fetch_sandbox(f):
"""Runs a command in a forked process with limited memory resources
to prevent memory bomb caused by specially crafted http responses."""
def process(self, irc, msg, *args, **kwargs):
delayed_irc = DelayedIrc(irc, msg)
f(self, delayed_irc, msg, *args, **kwargs)
return delayed_irc._replies
def newf(self, irc, *args):
try:
replies = commands.process(process, self, irc, *args,
timeout=5, heap_size=1024*1024,
pn=self.name(), cn=f.func_name)
except commands.ProcessTimeoutError:
raise utils.web.Error(_('Page is too big.'))
else:
for reply in replies:
irc.queueMsg(reply)
newf.__doc__ = f.__doc__
return newf
def catch_web_errors(f):
"""Display a nice error instead of "An error has occurred"."""
def newf(self, irc, *args, **kwargs):
try:
f(self, irc, *args, **kwargs)
except utils.web.Error as e:
irc.reply(str(e))
newf.__doc__ = f.__doc__
return newf
class Web(callbacks.PluginRegexp): class Web(callbacks.PluginRegexp):
"""Add the help for "@help Web" here.""" """Add the help for "@help Web" here."""
threaded = True threaded = True
regexps = ['titleSnarfer'] regexps = ['titleSnarfer']
def callCommand(self, command, irc, msg, *args, **kwargs):
try:
super(Web, self).callCommand(command, irc, msg, *args, **kwargs)
except utils.web.Error, e:
irc.reply(str(e))
@fetch_sandbox
def titleSnarfer(self, irc, msg, match): def titleSnarfer(self, irc, msg, match):
channel = msg.args[0] channel = msg.args[0]
if not irc.isChannel(channel): if not irc.isChannel(channel):
@ -135,6 +175,8 @@ class Web(callbacks.PluginRegexp):
break break
return passed return passed
@catch_web_errors
@fetch_sandbox
@internationalizeDocstring @internationalizeDocstring
def headers(self, irc, msg, args, url): def headers(self, irc, msg, args, url):
"""<url> """<url>
@ -155,6 +197,8 @@ class Web(callbacks.PluginRegexp):
headers = wrap(headers, ['httpUrl']) headers = wrap(headers, ['httpUrl'])
_doctypeRe = re.compile(r'(<!DOCTYPE[^>]+>)', re.M) _doctypeRe = re.compile(r'(<!DOCTYPE[^>]+>)', re.M)
@catch_web_errors
@fetch_sandbox
@internationalizeDocstring @internationalizeDocstring
def doctype(self, irc, msg, args, url): def doctype(self, irc, msg, args, url):
"""<url> """<url>
@ -176,6 +220,8 @@ class Web(callbacks.PluginRegexp):
irc.reply(_('That URL has no specified doctype.')) irc.reply(_('That URL has no specified doctype.'))
doctype = wrap(doctype, ['httpUrl']) doctype = wrap(doctype, ['httpUrl'])
@catch_web_errors
@fetch_sandbox
@internationalizeDocstring @internationalizeDocstring
def size(self, irc, msg, args, url): def size(self, irc, msg, args, url):
"""<url> """<url>
@ -204,6 +250,8 @@ class Web(callbacks.PluginRegexp):
fd.close() fd.close()
size = wrap(size, ['httpUrl']) size = wrap(size, ['httpUrl'])
@catch_web_errors
@fetch_sandbox
@internationalizeDocstring @internationalizeDocstring
def title(self, irc, msg, args, optlist, url): def title(self, irc, msg, args, optlist, url):
"""[--no-filter] <url> """[--no-filter] <url>
@ -260,6 +308,8 @@ class Web(callbacks.PluginRegexp):
irc.reply(s) irc.reply(s)
urlunquote = wrap(urlunquote, ['text']) urlunquote = wrap(urlunquote, ['text'])
@catch_web_errors
@fetch_sandbox
@internationalizeDocstring @internationalizeDocstring
def fetch(self, irc, msg, args, url): def fetch(self, irc, msg, args, url):
"""<url> """<url>