diff --git a/server/asyncserver.py b/server/asyncserver.py index 66b2b53f..35167a6b 100644 --- a/server/asyncserver.py +++ b/server/asyncserver.py @@ -29,7 +29,7 @@ __license__ = "GPL" from pickle import dumps, loads, HIGHEST_PROTOCOL from common import helpers -import asyncore, asynchat, socket, os, logging, sys, traceback +import asyncore, asynchat, socket, os, logging, sys, traceback, fcntl # Gets the instance of the logger. logSys = logging.getLogger("fail2ban.server") @@ -107,6 +107,7 @@ class AsyncServer(asyncore.dispatcher): except TypeError: logSys.warning("Type error") return + AsyncServer.__markCloseOnExec(conn) # Creates an instance of the handler class to handle the # request/response on the incoming connection. RequestHandler(conn, self.__transmitter) @@ -134,6 +135,7 @@ class AsyncServer(asyncore.dispatcher): self.bind(sock) except Exception: raise AsyncServerException("Unable to bind socket %s" % self.__sock) + AsyncServer.__markCloseOnExec(self.socket) self.listen(1) # Sets the init flag. self.__init = True @@ -159,6 +161,18 @@ class AsyncServer(asyncore.dispatcher): os.remove(self.__sock) logSys.debug("Socket shutdown") + ## + # Marks socket as close-on-exec to avoid leaking file descriptors when + # running actions involving command execution. + + # @param sock: socket file. + + #@staticmethod + def __markCloseOnExec(sock): + fd = sock.fileno() + flags = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, flags|fcntl.FD_CLOEXEC) + __markCloseOnExec = staticmethod(__markCloseOnExec) ## # AsyncServerException is used to wrap communication exceptions. diff --git a/server/filter.py b/server/filter.py index f842c269..88a5d861 100644 --- a/server/filter.py +++ b/server/filter.py @@ -554,7 +554,8 @@ class FileContainer: self.__handler = open(self.__filename) # Set the file descriptor to be FD_CLOEXEC fd = self.__handler.fileno() - fcntl.fcntl(fd, fcntl.F_SETFD, fd | fcntl.FD_CLOEXEC) + flags = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) firstLine = self.__handler.readline() # Computes the MD5 of the first line. myHash = md5sum(firstLine).digest() diff --git a/server/filtergamin.py b/server/filtergamin.py index cff5aa54..196396c5 100644 --- a/server/filtergamin.py +++ b/server/filtergamin.py @@ -27,7 +27,7 @@ from failmanager import FailManagerEmpty from filter import FileFilter from mytime import MyTime -import time, logging, gamin +import time, logging, gamin, fcntl # Gets the instance of the logger. logSys = logging.getLogger("fail2ban.filter") @@ -52,6 +52,9 @@ class FilterGamin(FileFilter): self.__modified = False # Gamin monitor self.monitor = gamin.WatchMonitor() + fd = self.monitor.get_fd() + flags = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, flags|fcntl.FD_CLOEXEC) logSys.debug("Created FilterGamin")