syscallList, list()) allSyscalls = allSyscalls.union(leaves) funcLine = funcFile.readline() syscallList = list() for syscallStr in allSyscalls: syscallNum = int(syscallStr[8:-1]) bisect.insort(syscallList, syscallNum) print(str(len(syscallList))) print(syscallList) syscallMapper = syscall.Syscall(rootLogger) syscallMap = syscallMapper.createMap() blackList = [] i = 1 while i < 400: if (i not in syscallList and syscallMap.get(i, None) and syscallMap[i] not in exceptList): blackList.append(syscallMap[i]) i += 1 print("Num of black listed syscalls: " + str(len(blackList))) seccompProfile = seccomp.Seccomp(rootLogger) blackListProfile = seccompProfile.createProfile(blackList) outputPath = options.output if (options.output is None): outputPath = "seccomp.out" outputFile = open(outputPath, 'w') outputFile.write(blackListProfile) outputFile.flush() outputFile.close()
def createSeccompProfile(self, tempOutputFolder, resultsFolder): returnCode = 0 if os.geteuid() != 0: self.logger.error("This script must be run as ROOT only!") exit("This script must be run as ROOT only. Exiting.") self.logger.debug("tempOutputFolder: %s", tempOutputFolder) allSyscalls = set() muslSyscallList = list() glibcSyscallList = list() i = 0 while i < 400: muslSyscallList.append("syscall(" + str(i) + ")") glibcSyscallList.append("syscall(" + str(i) + ")") glibcSyscallList.append("syscall ( " + str(i) + " )") glibcSyscallList.append("syscall( " + str(i) + " )") i += 1 fineGrainCfgs = dict() glibcGraph = graph.Graph(self.logger) glibcGraph.createGraphFromInput(self.glibcCfgpath, ":") glibcWrapperListTemp = [] if (self.strictMode): for func in self.glibcFuncList: glibcWrapperListTemp.extend( glibcGraph.getSyscallFromStartNode(func)) else: i = 0 while i < 400: glibcWrapperListTemp.append(i) i += 1 glibcWrapperList = set(glibcWrapperListTemp) muslGraph = graph.Graph(self.logger) muslGraph.createGraphFromInput(self.muslCfgpath, "->") muslWrapperListTemp = [] if (self.strictMode): for func in self.muslFuncList: muslWrapperListTemp.extend( muslGraph.getSyscallFromStartNode(func)) else: i = 0 while i < 400: muslWrapperListTemp.append(i) i += 1 muslWrapperList = set(muslWrapperListTemp) # self.logger.debug("glibcWrapperList: %s", str(glibcWrapperList)) # self.logger.debug("muslWrapperList: %s", str(muslWrapperList)) #TODO Separate libaio-like CFGs from fine-grained CFGs #Go through extra CFGs such as libaio to extract lib->syscall mapping #for fileName in os.listdir(self.cfgFolderPath): # self.logger.debug("Adding cfg: %s", fileName) # glibcGraph.createGraphFromInput(self.cfgFolderPath + "/" + fileName, "->") # muslGraph.createGraphFromInput(self.cfgFolderPath + "/" + fileName, "->") #time.sleep(10) exceptList = [ "access", "arch_prctl", "brk", "close", "execve", "exit_group", "fcntl", "fstat", "geteuid", "lseek", "mmap", "mprotect", "munmap", "openat", "prlimit64", "read", "rt_sigaction", "rt_sigprocmask", "set_robust_list", "set_tid_address", "stat", "statfs", "write", "setns", "capget", "capset", "chdir", "fchown", "futex", "getdents64", "getpid", "getppid", "lstat", "openat", "prctl", "setgid", "setgroups", "setuid", "stat", "io_setup", "getdents", "clone", "readlinkat", "newfstatat", "getrandom", "sigaltstack", "getresgid", "getresuid", "setresgid", "setresuid", "alarm", "getsid", "getpgrp", "epoll_pwait", "vfork" ] javaExceptList = [ "open", "getcwd", "openat", "close", "fopen", "fclose", "link", "unlink", "unlinkat", "mknod", "rename", "renameat", "mkdir", "rmdir", "readlink", "realpath", "symlink", "stat", "lstat", "fstat", "fstatat", "chown", "lchown", "fchown", "chmod", "fchmod", "utimes", "futimes", "lutimes", "readdir", "read", "write", "access", "getpwuid", "getgrgid", "statvfs", "clock_getres", "get_mempolicy", "gettid", "getcpu", "fallocate", "memfd_create", "fstatat64", "newfstatat" ] binaryReady = False libFileReady = False languageReady = False try: self.logger.debug("Checking cache in %s", tempOutputFolder) myFile = open(tempOutputFolder + "/" + C.CACHE, 'r') binaryReady = True myFile = open(tempOutputFolder + "/" + C.LIBFILENAME, 'r') libFileReady = True # myFile = open(tempOutputFolder + "/" + C.LANGFILENAME, 'r') # languageReady = True except OSError as e: self.logger.info( "Cache doesn't exist, must extract binaries and libraries") self.logger.debug("binaryReady: %s libFileReady: %s", str(binaryReady), str(libFileReady)) myContainer = container.Container(self.imagePath, self.options, self.logger, self.args) self.containerName = myContainer.getContainerName() if (not myContainer.pruneVolumes()): self.logger.warning( "Pruning volumes failed, storage may run out of space\n") returncode, out, err = util.runCommand("mkdir -p " + tempOutputFolder) if (returncode != 0): self.logger.error("Failed to create directory: %s with error: %s", tempOutputFolder, err) else: self.logger.debug("Successfully created directory: %s", tempOutputFolder) ttr = 10 logSleepTime = 60 sysdigTotalRunCount = 3 if (binaryReady): sysdigTotalRunCount = 1 sysdigRunCount = 1 if (self.name == "softwareag-apigateway"): logSleepTime = 60 if (self.name == "cirros"): logSleepTime = 120 psListAll = set() self.logger.info("--->Starting MONITOR phase:") while (sysdigRunCount <= sysdigTotalRunCount): myMonitor = processMonitorFactory.Factory( self.logger, self.monitoringTool, psListFilePath=self.binLibList) #mySysdig = sysdig.Sysdig(self.logger) self.logger.debug( "Trying to kill and delete container which might not be running in loop... Not a problem if returns error" ) str(myContainer.kill()) str(myContainer.delete()) self.logger.info( "Running sysdig multiple times. Run count: %d from total: %d", sysdigRunCount, sysdigTotalRunCount) sysdigRunCount += 1 #sysdigResult = mySysdig.runSysdigWithDuration(logSleepTime) monitorResult = myMonitor.runWithDuration(logSleepTime) if (not monitorResult): self.logger.error( "Running sysdig with execve failed, not continuing for container: %s", self.name) self.logger.error( "Please make sure sysdig is installed and you are running the script with root privileges. If problem consists please contact our support team." ) self.errorMessage = "Running sysdig with execve failed" if (monitorResult and myContainer.runWithoutSeccomp()): #myContainer.run() ): self.status = True self.logger.info( "Ran container sleeping for %d seconds to generate logs and extract execve system calls", logSleepTime) time.sleep(logSleepTime) myMonitor.waitUntilComplete() originalLogs = myContainer.checkLogs() self.logger.debug("originalLog: %s", originalLogs) time.sleep(10) if (not myContainer.checkStatus()): self.logger.warning( "Container exited after running, trying to run in attached mode!" ) self.logger.debug(str(myContainer.delete())) if (not myContainer.runInAttachedMode()): self.errorMessage = "Container didn't run in attached mode either, forfeiting!" self.logger.error( "Container didn't run in attached mode either, forfeiting!" ) self.logger.error( "There is a problem launching a container for %s. Please validate you can run the container without Confine. If so, contact our support team.", self.name) self.logger.debug(str(myContainer.delete())) return C.NOATTACH else: time.sleep(10) if (not myContainer.checkStatus()): self.errorMessage = "Container got killed after running in attached mode as well!" self.logger.error( "Container got killed after running in attached mode as well, forfeiting!" ) self.logger.error( "There is a problem launching a container for %s. Please validate you can run the container without Confine. If so, contact our support team.", self.name) self.logger.debug(str(myContainer.kill())) self.logger.debug(str(myContainer.delete())) return C.CONSTOP self.runnable = True self.logger.debug( "Ran container %s successfully, sleeping for %d seconds", self.name, ttr) time.sleep(ttr) self.logger.debug( "Finished sleeping, extracting psNames for %s", self.name) self.logger.debug( "Starting to identify running processes and required binaries and libraries through dynamic analysis." ) if (not binaryReady): psList = myMonitor.extractPsNames( "execve", myContainer.getContainerName(), myContainer.getContainerId()) if (not psList): self.logger.error( "PS List is None from extractPsNames(). Retrying this container: %s", self.name) self.logger.debug(str(myContainer.kill())) self.logger.debug(str(myContainer.delete())) self.errorMessage = "PS List is None from extractPsNames(), error in sysdig, retrying this container" return C.SYSDIGERR if (len(psList) == 0): self.logger.error( "PS List is None from extractPsNames(). Retrying this container: %s", self.name) self.logger.debug(str(myContainer.kill())) self.logger.debug(str(myContainer.delete())) self.errorMessage = "PS List is None from extractPsNames(), error in sysdig, retrying this container" return C.NOPROCESS self.logger.info("len(psList) from sysdig: %d", len(psList)) # TODO: Do we need to do this? Or can we just rely on copyFromContainerWithLibs below psList = psList.union(myContainer.extractLibsFromProc()) self.logger.debug( "len(psList) after extracting proc list: %d", len(psList)) self.logger.debug("Container: %s PS List: %s", self.name, str(psList)) self.logger.debug( "Container: %s extracted psList with %d elements", self.name, len(psList)) self.logger.debug("Entering not binaryReady") if (not util.deleteAllFilesInFolder( tempOutputFolder, self.logger)): self.logger.error( "Failed to delete files in temporary output folder, exiting..." ) self.errorMessage = "Failed to delete files in temporary output folder" sys.exit(-1) psListAll.update(psList) self.logger.info( "Container: %s extracted psList with %d elements", self.name, len(psListAll)) if (self.status): if (not binaryReady): self.logger.info("Container: %s PS List: %s", self.name, str(psListAll)) self.logger.info( "Starting to copy identified binaries and libraries (This can take some time...)" ) #Will try to copy from different paths. Some might not exist. Errors are normal.") if (self.extractAllBinaries): psListAll.update(myContainer.extractAllBinaries()) for binaryPath in psListAll: if (binaryPath.strip() != ""): myContainer.copyFromContainerWithLibs( binaryPath, tempOutputFolder) #if ( not myContainer.copyFromContainerWithLibs(binaryPath, tempOutputFolder) ): # self.logger.error("Problem copying files from container!") binaryReady = True myFile = open(tempOutputFolder + "/" + C.CACHE, 'w') myFile.write("complete") myFile.flush() myFile.close() self.logger.info( "Finished copying identified binaries and libraries") self.logger.info("<---Finished MONITOR phase\n") self.logger.debug(str(myContainer.kill())) self.logger.debug(str(myContainer.delete())) if (binaryReady): self.logger.info("--->Starting Direct Syscall Extraction") self.logger.info("Extracting direct system call invocations") directSyscallSet = self.extractDirectSyscalls(tempOutputFolder) self.logger.info("<---Finished Direct Syscall Extraction\n") if (not libFileReady): self.logger.info("--->Starting ANALYZE phase") self.logger.info( "Extracting imported functions and storing in libs.out" ) self.extractAllImportedFunctions(tempOutputFolder, C.LIBFILENAME) self.logger.info("<---Finished ANALYZE phase\n") #if ( not languageReady ): self.extractBinaryType(tempOutputFolder) isMusl = self.usesMusl(tempOutputFolder) funcFilePath = tempOutputFolder + "/" + C.LIBFILENAME funcFile = open(funcFilePath, 'r') funcLine = funcFile.readline() if (not funcLine and not os.path.isfile( os.path.join(self.goFolderPath, self.name + ".syscalls")) and len(directSyscallSet) == 0): self.logger.info( "%s container can't be hardened because no functions can be extracted from binaries and no direct syscalls found", self.name) self.errorMessage = "container can't be hardened because no functions can be extracted from binaries and no direct syscalls found" return C.NOFUNCS self.logger.info( "--->Starting INTEGRATE phase, extracting the list required system calls" ) functionStartsOriginal = set() functionStartsFineGrain = set() if (self.fineGrain): #TODO Fix fine grained analysis #1. Create CFG for each library #2. Extract leaves from all imported functions in libs.out #3. Create a list of required functions for each library #4. Use fine grained version or all imported for libraries without CFG libsWithCfg = set() libsInLibc = set() for fileName in os.listdir(self.cfgFolderPath): libsWithCfg.add(fileName) libsInLibc.add("libcrypt.callgraph.out") libsInLibc.add("libdl.callgraph.out") libsInLibc.add("libnsl.callgraph.out") libsInLibc.add("libnss_compat.callgraph.out") libsInLibc.add("libnss_files.callgraph.out") libsInLibc.add("libnss_nis.callgraph.out") libsInLibc.add("libpthread.callgraph.out") libsInLibc.add("libm.callgraph.out") libsInLibc.add("libresolv.callgraph.out") libsInLibc.add("librt.callgraph.out") libsInLibc.add("libutil.callgraph.out") libsInLibc.add("libnss_dns.callgraph.out") cfgAvailable = False for fileName in os.listdir(tempOutputFolder): self.logger.debug("fileName: %s", fileName) tmpFileName = fileName functionList = set() if (fileName.startswith("lib") and fileName != "libs.out"): cfgAvailable = True tmpFileName = re.sub("-.*so", ".so", fileName) tmpFileName = tmpFileName[:tmpFileName.index(".so" )] tmpFileName = tmpFileName + ".callgraph.out" self.logger.debug("tmpFileName: %s", tmpFileName) if (tmpFileName in libsWithCfg): tmpGraph = graph.Graph(self.logger) tmpGraph.createGraphFromInput( self.cfgFolderPath + "/" + tmpFileName, "->") funcFile.seek(0) funcLine = funcFile.readline() while (funcLine): funcName = funcLine.strip() leaves = tmpGraph.getLeavesFromStartNode( funcName, list(), list()) if (len(leaves) != 0 and funcName not in leaves): #self.logger.debug("funcName: %s leaves: %s", funcName, str(leaves)) functionList.update(set(leaves)) funcLine = funcFile.readline() elif (tmpFileName in libsInLibc): continue else: self.logger.info("Adding function starts for %s", fileName) functionList = util.extractImportedFunctions( tempOutputFolder + "/" + fileName, self.logger) if (not functionList): self.logger.warning( "Function extraction for file: %s failed!", fileName) functionStartsFineGrain.update(set(functionList)) funcFile.seek(0) funcLine = funcFile.readline() while (funcLine): funcLine = funcLine.strip() functionStartsOriginal.add(funcLine) funcLine = funcFile.readline() funcFile.close() self.logger.info( "Traversing libc call graph to identify required system calls" ) tmpSet = set() allSyscallsOriginal = set() for function in functionStartsOriginal: if (isMusl): leaves = muslGraph.getLeavesFromStartNode( function, muslSyscallList, list()) else: leaves = glibcGraph.getLeavesFromStartNode( function, glibcSyscallList, list()) #self.logger.debug("function: %s, tmpSet: %s", function, tmpSet) tmpSet = tmpSet.union(leaves) for syscallStr in tmpSet: syscallStr = syscallStr.replace("syscall( ", "syscall(") syscallStr = syscallStr.replace("syscall ( ", "syscall(") syscallStr = syscallStr.replace(" )", ")") syscallNum = int(syscallStr[8:-1]) allSyscallsOriginal.add(syscallNum) self.logger.debug("allSyscallsOriginal: %s", str(allSyscallsOriginal)) allSyscallsFineGrain = set() if (self.fineGrain): tmpSet = set() for function in functionStartsFineGrain: #if ( function == "fork" ): # self.logger.debug("/////////////////////////////////////////FORK has been found///////////////////////////////////") if (isMusl): leaves = muslGraph.getLeavesFromStartNode( function, muslSyscallList, list()) else: leaves = glibcGraph.getLeavesFromStartNode( function, glibcSyscallList, list()) tmpSet = tmpSet.union(leaves) for syscallStr in tmpSet: syscallStr = syscallStr.replace( "syscall( ", "syscall(") syscallStr = syscallStr.replace( "syscall ( ", "syscall(") syscallStr = syscallStr.replace(" )", ")") syscallNum = int(syscallStr[8:-1]) allSyscallsFineGrain.add(syscallNum) #Check if we have go syscalls staticSyscallList = [] try: staticSyscallListFile = open( os.path.join(self.goFolderPath, self.name + ".syscalls"), 'r') syscallLine = staticSyscallListFile.readline() while (syscallLine): staticSyscallList.append(int(syscallLine.strip())) syscallLine = staticSyscallListFile.readline() except Exception as e: self.logger.debug( "Can't extract syscalls from: %s", os.path.join( self.goFolderPath, self.name + ".syscalls (probably not a golang developed application)" )) self.logger.debug( "After reading file: %s len(staticSyscallList): %d", os.path.join(self.goFolderPath, self.name + ".syscalls"), len(staticSyscallList)) syscallMapper = syscall.Syscall(self.logger) syscallMap = syscallMapper.createMap(self.maptype) self.logger.info("Generating final system call filter list") blackListOriginal = [] i = 1 while i < 400: if ((self.directSyscallCount == 0 and self.libcSyscallCount == 0) or (isMusl and i in muslWrapperList) or (i in glibcWrapperList)): if (i not in directSyscallSet and i not in staticSyscallList and i not in allSyscallsOriginal and syscallMap.get(i, None) and syscallMap[i] not in exceptList): if (("Java" in self.languageSet and syscallMap[i] not in javaExceptList) or ("Java" not in self.languageSet)): blackListOriginal.append(syscallMap[i]) i += 1 blackListFineGrain = [] if (self.fineGrain): i = 1 while i < 400: if ((self.directSyscallCount == 0 and self.libcSyscallCount == 0) or (isMusl and i in muslWrapperList) or (i in glibcWrapperList)): if (i not in directSyscallSet and i not in staticSyscallList and i not in allSyscallsFineGrain and syscallMap.get(i, None) and syscallMap[i] not in exceptList): if (("Java" in self.languageSet and syscallMap[i] not in javaExceptList) or ("Java" not in self.languageSet)): blackListFineGrain.append(syscallMap[i]) i += 1 self.logger.info( "************************************************************************************" ) self.logger.info( "Container Name: %s Num of filtered syscalls (original): %s", self.name, str(len(blackListOriginal))) self.logger.info( "************************************************************************************" ) self.logger.info("<---Finished INTEGRATE phase\n") self.blSyscallsOriginal = blackListOriginal self.blSyscallOriginalCount = len(blackListOriginal) if (self.fineGrain): self.logger.info( "Container Name: %s Num of filtered syscalls (fine grained): %s", self.name, str(len(blackListFineGrain))) self.blSyscallsFineGrain = blackListFineGrain self.blSyscallFineGrainCount = len(blackListFineGrain) seccompProfile = seccomp.Seccomp(self.logger) if (self.fineGrain): blackListProfile = seccompProfile.createProfile( blackListFineGrain) else: blackListProfile = seccompProfile.createProfile( blackListOriginal) if ("/" in self.name): outputPath = resultsFolder + "/" + self.name.replace( "/", "-") + ".seccomp.json" else: outputPath = resultsFolder + "/" + self.name + ".seccomp.json" outputFile = open(outputPath, 'w') outputFile.write(blackListProfile) outputFile.flush() outputFile.close() self.logger.info( "--->Validating generated Seccomp profile: %s", outputPath) if (myContainer.runWithSeccompProfile(outputPath)): time.sleep(logSleepTime) debloatedLogs = myContainer.checkLogs() if (len(originalLogs) == len(debloatedLogs)): time.sleep(3) if (myContainer.checkStatus()): self.logger.info( "************************************************************************************" ) self.logger.info( "Finished validation. Container for image: %s was hardened SUCCESSFULLY!", self.name) self.logger.info( "************************************************************************************" ) self.debloatStatus = True returnCode = 0 else: self.logger.warning( "Container for image: %s was hardened with problems. Dies after running!", self.name) self.errorMessage = "Container was hardened with problems. Dies after running!" returnCode = C.HSTOPS else: self.logger.warning( "Container for image: %s was hardened with problems: len(original): %d len(seccomp): %d original: %s seccomp: %s", self.name, len(originalLogs), len(debloatedLogs), originalLogs, debloatedLogs) self.errorMessage = "Unknown problem in hardening container!" returnCode = C.HLOGLEN if (self.isDependent): self.logger.info( "Not killing container: %s because it is a dependent for hardening another container", self.name) else: if (not myContainer.kill() and self.debloatStatus): self.logger.warning( "Container can't be killed even though successfully hardened! Hardening has been unsuccessfull!" ) self.errorMessage = "Container can't be killed even though successfully hardened! Hardening has been unsuccessfull!" self.debloatStatus = False returnCode = C.HNOKILL else: self.errorMessage = "Unknown problem in hardening container!" returnCode = C.HNORUN if (not self.isDependent): self.logger.debug(str(myContainer.delete())) return returnCode