├── .clang-format ├── .clang-tidy ├── .dockerignore ├── .gitignore ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── boxes ├── CMakeLists.txt ├── README.md ├── download_boxes.sh ├── make_boxes.sh ├── manifest.txt └── utils.sh ├── debian ├── README ├── changelog ├── compat ├── control ├── copyright ├── rules ├── sio2jail-docs.docs └── source │ └── format ├── doc ├── CMakeLists.txt └── sio2jail.1.scd ├── external ├── libcap.cmake ├── libcap.patch ├── libseccomp.cmake ├── libtclap.cmake └── scdoc.cmake ├── src ├── CMakeLists.txt ├── common │ ├── Assert.h │ ├── EventProvider.h │ ├── Exception.cc │ ├── Exception.h │ ├── FD.cc │ ├── FD.h │ ├── Feature.h │ ├── Preprocessor.h │ ├── ProcFS.cc │ ├── ProcFS.h │ ├── TypeList.h │ ├── Utils.cc │ ├── Utils.h │ └── WithErrnoCheck.h ├── executor │ ├── ExecuteAction.h │ ├── ExecuteEvent.h │ ├── ExecuteEventListener.h │ ├── Executor.cc │ └── Executor.h ├── files │ ├── FilesListener.cc │ └── FilesListener.h ├── limits │ ├── MemoryLimitListener.cc │ ├── MemoryLimitListener.h │ ├── OutputLimitListener.cc │ ├── OutputLimitListener.h │ ├── ThreadsLimitListener.cc │ ├── ThreadsLimitListener.h │ ├── TimeLimitListener.cc │ └── TimeLimitListener.h ├── logger │ ├── FDLogger.cc │ ├── FDLogger.h │ ├── FileLogger.cc │ ├── FileLogger.h │ ├── Logger.cc │ ├── Logger.h │ ├── LoggerListener.cc │ ├── LoggerListener.h │ └── VoidLogger.h ├── ns │ ├── IPCNamespaceListener.cc │ ├── IPCNamespaceListener.h │ ├── MountEventListener.h │ ├── MountNamespaceListener.cc │ ├── MountNamespaceListener.h │ ├── NetNamespaceListener.cc │ ├── NetNamespaceListener.h │ ├── PIDNamespaceListener.cc │ ├── PIDNamespaceListener.h │ ├── UTSNamespaceListener.cc │ ├── UTSNamespaceListener.h │ ├── UserNamespaceListener.cc │ └── UserNamespaceListener.h ├── perf │ ├── PerfListener.cc │ └── PerfListener.h ├── printer │ ├── AugmentedOIOutputBuilder.cc │ ├── AugmentedOIOutputBuilder.h │ ├── HumanReadableOIOutputBuilder.cc │ ├── HumanReadableOIOutputBuilder.h │ ├── OIModelOutputBuilder.cc │ ├── OIModelOutputBuilder.h │ ├── OITimeToolOutputBuilder.cc │ ├── OITimeToolOutputBuilder.h │ ├── OutputBuilder.h │ ├── OutputSource.cc │ ├── OutputSource.h │ ├── RealTimeOIOutputBuilder.cc │ ├── RealTimeOIOutputBuilder.h │ ├── UserTimeOIOutputBuilder.cc │ └── UserTimeOIOutputBuilder.h ├── priv │ ├── PrivListener.cc │ └── PrivListener.h ├── s2japp │ ├── Application.cc │ ├── Application.h │ ├── ApplicationArguments.cc │ ├── ApplicationArguments.h │ ├── ApplicationException.h │ ├── ApplicationSettings.cc │ └── ApplicationSettings.h ├── seccomp │ ├── SeccompContext.cc │ ├── SeccompContext.h │ ├── SeccompException.h │ ├── SeccompListener.cc │ ├── SeccompListener.h │ ├── SeccompRule.cc │ ├── SeccompRule.h │ ├── action │ │ ├── ActionAllow.cc │ │ ├── ActionAllow.h │ │ ├── ActionErrno.cc │ │ ├── ActionErrno.h │ │ ├── ActionKill.cc │ │ ├── ActionKill.h │ │ ├── ActionTrace.cc │ │ ├── ActionTrace.h │ │ ├── SeccompAction.cc │ │ └── SeccompAction.h │ ├── filter │ │ ├── LibSeccompFilter.cc │ │ ├── LibSeccompFilter.h │ │ └── SyscallFilter.h │ └── policy │ │ ├── DefaultPolicy.cc │ │ ├── DefaultPolicy.h │ │ ├── PermissivePolicy.h │ │ └── SyscallPolicy.h ├── sio2jail.cc └── tracer │ ├── ProcessInfo.cc │ ├── ProcessInfo.h │ ├── TraceAction.h │ ├── TraceEvent.h │ ├── TraceEventListener.h │ ├── TraceExecutor.cc │ ├── TraceExecutor.h │ ├── Tracee.cc │ └── Tracee.h └── test ├── CMakeLists.txt ├── src ├── 1-sec-prog-th.cc ├── 1-sec-prog.c ├── CMakeLists.txt ├── infinite-loop.c ├── leak-dive.c ├── leak-huge.c ├── leak-tiny.c ├── stderr-write.c ├── sum_c.c ├── sum_cxx.cc ├── sum_python2.py ├── sum_python3.9.py ├── sum_python3.py └── zero └── testsuits ├── __init__.py ├── base ├── __init__.py ├── paths.py └── supervisor.py ├── test_executor.py ├── test_langs.py ├── test_memory_limit.py ├── test_threads_limit.py └── test_times.py /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | 3 | ColumnLimit: 80 4 | 5 | AccessModifierOffset: -4 6 | ConstructorInitializerIndentWidth: 8 7 | ContinuationIndentWidth: 8 8 | IndentCaseLabels: false 9 | IndentPPDirectives: None 10 | IndentWidth: 4 11 | IndentWrappedFunctionNames: false 12 | UseTab: Never 13 | 14 | NamespaceIndentation: None 15 | CompactNamespaces: false 16 | FixNamespaceComments: true 17 | 18 | MaxEmptyLinesToKeep: 2 19 | KeepEmptyLinesAtTheStartOfBlocks: false 20 | 21 | AlignConsecutiveAssignments: false 22 | AlignConsecutiveDeclarations: false 23 | AlignEscapedNewlines: false 24 | AlignOperands: true 25 | AlignTrailingComments: false 26 | 27 | AlignAfterOpenBracket: AlwaysBreak 28 | # AllowAllArgumentsOnNextLine: true 29 | # AllowAllConstructorInitializersOnNextLine: true 30 | AllowAllParametersOfDeclarationOnNextLine: false 31 | 32 | AllowShortBlocksOnASingleLine: false 33 | AllowShortCaseLabelsOnASingleLine: false 34 | AllowShortFunctionsOnASingleLine: Empty 35 | AllowShortIfStatementsOnASingleLine: false 36 | # AllowShortLambdasOnASingleLine: Always 37 | AllowShortLoopsOnASingleLine: false 38 | 39 | AlwaysBreakAfterReturnType: None 40 | AlwaysBreakBeforeMultilineStrings: false 41 | AlwaysBreakTemplateDeclarations: Yes 42 | 43 | BinPackArguments: false 44 | BinPackParameters: false 45 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 46 | 47 | BreakBeforeBraces: Custom 48 | # BrakeBeforeBraces: Attach 49 | BraceWrapping: 50 | # AfterCaseLabel: false 51 | AfterClass: false 52 | AfterControlStatement: false 53 | AfterEnum: false 54 | AfterFunction: false 55 | AfterNamespace: false 56 | AfterStruct: false 57 | AfterUnion: false 58 | AfterExternBlock: false 59 | BeforeCatch: true 60 | BeforeElse: true 61 | SplitEmptyFunction: false 62 | SplitEmptyRecord: false 63 | SplitEmptyNamespace: false 64 | BreakBeforeTernaryOperators: true 65 | BreakConstructorInitializers: BeforeComma 66 | BreakInheritanceList: BeforeComma 67 | 68 | BreakStringLiterals: true 69 | 70 | Cpp11BracedListStyle: true 71 | SpaceBeforeCpp11BracedList: false 72 | 73 | PointerAlignment: Left 74 | 75 | ForEachMacros: ['traverse'] 76 | 77 | IncludeBlocks: Preserve 78 | # IncludeCategories 79 | # IncludeIsMainRegex 80 | SortIncludes: true 81 | SortUsingDeclarations: true 82 | 83 | ReflowComments: true 84 | 85 | SpaceAfterCStyleCast: true 86 | # SpaceAfterLogicalNot: false 87 | SpaceAfterTemplateKeyword: false 88 | SpaceBeforeAssignmentOperators: true 89 | SpaceBeforeCtorInitializerColon: true 90 | SpaceBeforeInheritanceColon: true 91 | SpaceBeforeParens: ControlStatements 92 | SpaceBeforeRangeBasedForLoopColon: false 93 | SpaceInEmptyParentheses: false 94 | SpacesBeforeTrailingComments: 1 95 | SpacesInAngles: false 96 | SpacesInCStyleCastParentheses: false 97 | SpacesInContainerLiterals: false 98 | SpacesInParentheses: false 99 | SpacesInSquareBrackets: false 100 | 101 | Standard: Cpp11 102 | 103 | PenaltyBreakBeforeFirstCallParameter: 0 104 | PenaltyReturnTypeOnItsOwnLine: 300 105 | PenaltyBreakFirstLessLess: 0 106 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: ' 2 | *, 3 | -cert-err58-cpp, 4 | -cppcoreguidelines-pro-type-union-access, 5 | -fuchsia-default-arguments, 6 | -fuchsia-overloaded-operator, 7 | -fuchsia-statically-constructed-objects, 8 | -google-readability-todo, 9 | -hicpp-signed-bitwise 10 | ' 11 | 12 | WarningsAsErrors: ' 13 | clang-analyzer-*, 14 | modernize-*, 15 | performance-* 16 | ' 17 | 18 | FormatStyle: 'file' 19 | 20 | CheckOptions: 21 | - key: readability-magic-numbers.IgnoredIntegerValues 22 | value: '0;1;2;3;4;16;255;1024' 23 | - key: cppcoreguidelines-readability-magic-numbers.IgnoredIntegerValues 24 | value: '0;1;2;3;4;16;255;1024' 25 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | boxes/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | bin/ 3 | 4 | tags 5 | src/tags 6 | .vimrc 7 | 8 | boxes/*.tar.gz 9 | boxes/*.deb 10 | 11 | *.sw[mnopq] 12 | *~ 13 | pin.log 14 | *.xml 15 | 16 | *.tar.gz 17 | 18 | // Various pycharm test related outputs 19 | *.log 20 | *.pyc 21 | .idea 22 | .cache 23 | __pycache__ 24 | 25 | debian/.debhelper/ 26 | debian/debhelper-build-stamp 27 | debian/files 28 | debian/*.substvars 29 | debian/sio2jail/ 30 | obj-*/ 31 | ccache/ 32 | install/ 33 | obj-x86* 34 | out/ 35 | 36 | .vscode/ 37 | .devcontainer/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.0) 2 | 3 | INCLUDE(${CMAKE_ROOT}/Modules/ExternalProject.cmake) 4 | INCLUDE(${CMAKE_ROOT}/Modules/GNUInstallDirs.cmake) 5 | 6 | IF(NOT DEFINED LINK) 7 | SET(LINK "STATIC") 8 | ENDIF() 9 | IF(NOT LINK MATCHES "STATIC|DYNAMIC") 10 | MESSAGE(FATAL_ERROR "LINK should be one of STATIC, DYNAMIC") 11 | ENDIF() 12 | 13 | IF(NOT DEFINED ARCH) 14 | SET(ARCH "NATIVE") 15 | ENDIF() 16 | IF(NOT ARCH MATCHES "i386|x86_64|amd64|NATIVE") 17 | MESSAGE(FATAL_ERROR "ARCH should be one of i386, amd64, NATIVE") 18 | ENDIF() 19 | 20 | IF(NOT DEFINED WITH_CLANG_TIDY) 21 | SET(WITH_CLANG_TIDY "NO") 22 | ENDIF() 23 | IF(NOT WITH_CLANG_TIDY MATCHES "YES|NO") 24 | MESSAGE(FATAL_ERROR "WITH_CLANG_TIDY should be one of YES, NO") 25 | ENDIF() 26 | 27 | INCLUDE(external/libseccomp.cmake) 28 | INCLUDE(external/libcap.cmake) 29 | INCLUDE(external/libtclap.cmake) 30 | 31 | SET(CMAKE_C_FLAGS "-std=gnu99 -Wall") 32 | SET(CMAKE_C_FLAGS_DEBUG "-g -pedantic") 33 | SET(CMAKE_C_FLAGS_RELEASE "-O2") 34 | SET(CMAKE_CXX_FLAGS "-std=gnu++14 -Wall") 35 | SET(CMAKE_CXX_FLAGS_DEBUG "-g -pedantic") 36 | SET(CMAKE_CXX_FLAGS_RELEASE "-O3") 37 | 38 | IF(LINK STREQUAL "STATIC") 39 | SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static") 40 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static") 41 | ENDIF() 42 | 43 | IF(ARCH STREQUAL "i386") 44 | SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32") 45 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32") 46 | ELSEIF(ARCH MATCHES "amd64|x86_64") 47 | SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m64") 48 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64") 49 | ENDIF() 50 | 51 | EXECUTE_PROCESS(COMMAND uname -r 52 | OUTPUT_VARIABLE BUILD_KERNEL_RELEASE) 53 | ADD_DEFINITIONS('-DBUILD_KERNEL_RELEASE="${BUILD_KERNEL_RELEASE}"') 54 | 55 | ENABLE_TESTING() 56 | 57 | IF(WITH_CLANG_TIDY STREQUAL "YES") 58 | CMAKE_MINIMUM_REQUIRED(VERSION 3.6.3) 59 | SET(CMAKE_CXX_CLANG_TIDY clang-tidy) 60 | ENDIF() 61 | 62 | IF(NOT DEFINED WITH_DOCS) 63 | SET(WITH_DOCS "YES") 64 | ENDIF() 65 | IF(NOT WITH_DOCS MATCHES "YES|NO") 66 | MESSAGE(FATAL_ERROR "WITH_DOCS should be one of YES, NO") 67 | ENDIF() 68 | IF(WITH_DOCS STREQUAL "YES") 69 | INCLUDE(external/scdoc.cmake) 70 | ADD_SUBDIRECTORY(doc) 71 | ENDIF() 72 | 73 | ADD_SUBDIRECTORY(src) 74 | ADD_SUBDIRECTORY(boxes) 75 | ADD_SUBDIRECTORY(test) 76 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye 2 | 3 | RUN apt-get update && \ 4 | export DEBIAN_FRONTEND=noninteractive && \ 5 | apt-get install -y \ 6 | build-essential \ 7 | libcap-dev \ 8 | libtclap-dev \ 9 | libseccomp-dev \ 10 | libseccomp2 \ 11 | cmake \ 12 | g++-multilib \ 13 | gcc-multilib \ 14 | wget \ 15 | devscripts \ 16 | lintian \ 17 | debhelper \ 18 | ccache \ 19 | fakeroot 20 | 21 | ENV LIBRARY_PATH=/usr/lib/x86_64-linux-gnu 22 | ENV DEB_BUILD_OPTIONS "lang=en-US ccache nocheck" 23 | WORKDIR /app 24 | CMD ["/bin/bash"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Wojciech Dubiel, Tadeusz Dudkiewicz, Przemysław Jakub Kozłowski, Maciej Wachulec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC := g++ 2 | INSTALL_PREFIX = $(HOME)/.local 3 | CONTAINER_NAME = sio2jail-dev-container 4 | CCACHE := $(shell ccache --version 2>/dev/null) 5 | 6 | ifdef CCACHE 7 | CC := ccache 8 | endif 9 | 10 | define run_in_docker 11 | docker build -t sio2jail-dev . 12 | - docker run -v $(shell pwd)/ccache:/ccache -v $(shell pwd):/app -e CCACHE_DIR=/ccache --rm --name $(CONTAINER_NAME) -d -it sio2jail-dev bash 13 | docker exec $(CONTAINER_NAME) $(1) 14 | docker exec $(CONTAINER_NAME) rm -rf build 15 | docker stop $(CONTAINER_NAME) 16 | endef 17 | 18 | install: clean 19 | mkdir -p ccache 20 | mkdir -p ccache/tmp 21 | cmake -DCMAKE_CXX_COMPILER_LAUNCHER=$(CC) -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$(INSTALL_PREFIX) . -B ./build 22 | make -C build 23 | make -C build install 24 | 25 | deb: 26 | mkdir -p out 27 | fakeroot debuild -us -uc -b 28 | mv ../*.deb out/ 29 | 30 | release: install deb 31 | mkdir -p out 32 | mv $(INSTALL_PREFIX)/bin/sio2jail out/ 33 | 34 | release-docker: clean-docker 35 | $(call run_in_docker,make release) 36 | 37 | install-docker: clean-docker 38 | $(call run_in_docker,make install) 39 | 40 | deb-docker: clean-docker 41 | $(call run_in_docker,make deb) 42 | 43 | test: 44 | make -C build check 45 | 46 | clean: 47 | - rm -rf build 48 | - rm -rf out 49 | - rm -rf obj-* 50 | - rm -rf install 51 | - rm -rf bin 52 | - rm -rf debian/.debhelper 53 | - rm -rf debian/sio2jail 54 | - rm debian/files 55 | - rm debian/sio2jail.substvars 56 | 57 | clean-docker: 58 | docker build -t sio2jail-dev . 59 | - docker run -v $(shell pwd):/app --rm --name $(CONTAINER_NAME) -d -it sio2jail-dev bash 60 | docker exec $(CONTAINER_NAME) make clean -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sio2jail 2 | ======== 3 | 4 | Building in Docker 5 | -------- 6 | 7 | Available commands: 8 | 9 | * `make release-docker ` - build sio2jail binary and 10 | .deb package. 11 | 12 | * `make install-docker` - build sio2jail binary. 13 | 14 | * `make deb-docker` - build sio2jail .deb package. 15 | 16 | * `make clean-docker` - clean up all temporary files. 17 | 18 | The output files are placed in `./out/` directory. 19 | 20 | For further reference and configuration see `./Makefile`. 21 | 22 | Building manually 23 | -------- 24 | 25 | You need a CMake, a C/C++ compiler with multilib support and python2. Any 26 | external libraries that sio2jail uses (see below) can be either installed 27 | system-wide, or downloaded and built during the process. To build sio2jail and 28 | install files to ~/local directory run: 29 | 30 | mkdir build && cd build 31 | 32 | cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/local .. 33 | make && make install 34 | 35 | For building with default configuration, see scripts in 36 | `./Makefile`. 37 | 38 | Our sio2jail uses some external libraries and programs: 39 | * libcap 40 | * libseccomp (>= 2.3.0) 41 | * libtclap 42 | * scdoc (for generating man pages) 43 | * g++-multilib 44 | 45 | some of which you can install (e.g. on Debian) with: 46 | 47 | apt-get install libcap-dev libtclap-dev libseccomp-dev g++-multilib 48 | 49 | By default sio2jail searches for this libraries in system paths and in case they 50 | aren't found their sources are downloaded and libraries are built in working 51 | directory. You can tune this behaviour with cmake options: 52 | 53 | -D_PREFIX= 54 | -D_BUILD_OWN=YES|NO 55 | 56 | where DEPENDENCY is one of 57 | * LIBCAP 58 | * LIBSECCOMP 59 | * LIBTCLAP 60 | * SCDOC 61 | 62 | You can also control whether to generate man pages with option (YES by default): 63 | 64 | -DWITH_DOCS=YES|NO 65 | 66 | and whether to install boxes scripts (NO by default): 67 | 68 | -DWITH_BOXES=YES|NO 69 | 70 | To control whether sio2jail binary is statically or dynamically linked use 71 | option (STATIC by default): 72 | 73 | -DLINK=STATIC|DYNAMIC 74 | 75 | There is also a possibility to control whether output binary should run on other 76 | architecture than the default one (or force given architecture): 77 | 78 | -DARCH=i386|amd64 79 | 80 | Note, that when using ARCH other than build host architecture it may be necessary 81 | (depending on libraries installation) to build sio2jail with custom libseccomp (more 82 | precisely with flag -DLIBSECCOMP\_BUILD\_OWN=YES). 83 | 84 | For example, to skip man pages, use libtclap from /opt/tclap directory and 85 | ignore system libseccomp run: 86 | 87 | cmake -DWITH_DOCS=NO -DLIBTCLAP_PREFIX=/opt/tclap -DLIBSECCOMP_BUILD_OWN=YES .. 88 | 89 | Running 90 | ------- 91 | 92 | You may need to run 93 | 94 | sysctl -w kernel.perf_event_paranoid=-1 95 | 96 | Additionally, if you want to use sandboxing on older Debian kernels, you'll need to run 97 | 98 | sysctl -w kernel.unprivileged_userns_clone=1 99 | 100 | For both settings, you may also put these options in your /etc/sysctl.conf. 101 | This will make the settings persist across reboots. 102 | 103 | Running tests 104 | ------------- 105 | 106 | To run test suit use 'check' target, e.g in build directory run: 107 | 108 | make check 109 | 110 | Notes for developers 111 | -------------------- 112 | 113 | To manually run clang-format on each file run: 114 | 115 | make clang-format 116 | 117 | inside build directory. 118 | 119 | To manually run clang-tidy on each source file run: 120 | 121 | make clang-tidy 122 | 123 | or to use automatically fix errors: 124 | 125 | make clang-tidy-fix 126 | 127 | inside build directory. 128 | 129 | There is possibility to enable running clang-tidy automatically during 130 | compilation on each file (can significantly slow down compilation): 131 | 132 | -DWITH_CLANG_TIDY=YES 133 | -------------------------------------------------------------------------------- /boxes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ADD_CUSTOM_COMMAND( 2 | OUTPUT 3 | minimal.tar.gz busybox.tar.gz python2.tar.gz python3.tar.gz compiler-python3.9.2-numpy_amd64.tar.gz 4 | COMMAND 5 | ${CMAKE_SOURCE_DIR}/boxes/make_boxes.sh 6 | DEPENDS 7 | make_boxes.sh 8 | ) 9 | 10 | ADD_CUSTOM_COMMAND( 11 | OUTPUT 12 | boxes_extraction_stamp.txt 13 | COMMAND 14 | tar -xvf minimal.tar.gz 15 | && tar -xvf busybox.tar.gz 16 | && tar -xvf python2.tar.gz 17 | && tar -xvf python3.tar.gz 18 | && tar -xvf compiler-python3.9.2-numpy_amd64.tar.gz && mv compiler-python3.9.2-numpy_amd64 python3_9 19 | && touch boxes_extraction_stamp.txt 20 | DEPENDS 21 | minimal.tar.gz busybox.tar.gz python2.tar.gz python3.tar.gz compiler-python3.9.2-numpy_amd64.tar.gz 22 | ) 23 | 24 | ADD_CUSTOM_TARGET(boxes 25 | DEPENDS 26 | boxes_extraction_stamp.txt 27 | ) 28 | 29 | IF(NOT DEFINED WITH_BOXES) 30 | SET(WITH_BOXES "NO") 31 | ENDIF() 32 | IF(NOT WITH_BOXES MATCHES "YES|NO") 33 | MESSAGE(FATAL_ERROR "WITH_BOXES should be one of YES, NO") 34 | ENDIF() 35 | IF (WITH_BOXES STREQUAL "YES") 36 | INSTALL(FILES download_boxes.sh make_boxes.sh utils.sh 37 | DESTINATION "${CMAKE_INSTALL_FULL_DATADIR}/sio2jail/boxes" 38 | PERMISSIONS OWNER_READ OWNER_EXECUTE) 39 | ENDIF() 40 | -------------------------------------------------------------------------------- /boxes/README.md: -------------------------------------------------------------------------------- 1 | sio2jail boxes 2 | ============== 3 | 4 | Introduction 5 | ------------ 6 | TODO 7 | 8 | 9 | Specification 10 | ------------- 11 | Every root box must contain: 12 | * `./exe` empty file with exeuctable persmissions 13 | * `./proc/` empty directory 14 | 15 | Scripts 16 | ------- 17 | TODO 18 | -------------------------------------------------------------------------------- /boxes/download_boxes.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DEFAULT_URL="https://hitagi.dasie.mimuw.edu.pl/files/boxes/" 4 | 5 | if [ -z "$URL" ]; then 6 | echo "** URL not set, using default $DEFAULT_URL" 7 | URL="$DEFAULT_URL" 8 | fi 9 | 10 | if [ -z "$1" ]; then 11 | TARGET="." 12 | else 13 | TARGET="$1" 14 | fi 15 | 16 | mkdir -pv "$TARGET" 17 | 18 | curl -L -s $URL/manifest.txt | while read line; do 19 | box_name=`echo $line | awk '{ print $2 }'` 20 | box_csum=`echo $line | awk '{ print $1 }'` 21 | 22 | if [ -e "$TARGET/$box_name" ]; then 23 | csum=`sha256sum "$TARGET/$box_name" | awk '{ print $1 }'` 24 | if [ "$csum" != "$box_csum" ]; then 25 | echo "** Box $box_name has changed, removing it" 26 | rm "$TARGET/$box_name" 27 | else 28 | echo "** Box $box_name hasn't changed, using it" 29 | fi 30 | fi 31 | 32 | echo "** Downloading box $box_name" 33 | [ -e "$TARGET/$box_name" ] || wget -P "$TARGET" "$URL/$box_name" 34 | 35 | csum=`sha256sum "$TARGET/$box_name" | awk '{ print $1 }'` 36 | if [ "$csum" != "$box_csum" ]; then 37 | echo "** Verification of box $box_name failed" 38 | else 39 | echo "** Extracting box $box_name" 40 | tar -C "$TARGET" -xvf "$TARGET/$box_name" 41 | fi 42 | done 43 | -------------------------------------------------------------------------------- /boxes/make_boxes.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | source "`dirname "${BASH_SOURCE}"`/utils.sh" 6 | 7 | MANIFEST="manifest.txt" 8 | 9 | make_minimal() { 10 | export BOX="minimal" 11 | empty_box 12 | # nothing 13 | 14 | build_box 15 | clean_box 16 | manifest_box >> $MANIFEST 17 | } 18 | 19 | make_busybox() { 20 | export BOX="busybox" 21 | empty_box 22 | # busybox with dependencies 23 | extract_deb "busybox/busybox_1.22.0-19+b3_amd64.deb" 24 | extract_deb "glibc/libc6_2.24-11+deb9u4_amd64.deb" 25 | extract_deb "gcc-6/libgcc1_6.3.0-18+deb9u1_amd64.deb" 26 | extract_deb "gcc-6/gcc-6-base_6.3.0-18+deb9u1_amd64.deb" 27 | build_box 28 | clean_box 29 | manifest_box >> $MANIFEST 30 | } 31 | 32 | make_python2() { 33 | export BOX="python2" 34 | empty_box 35 | # python2.7 with dependencies 36 | extract_deb "python2.7/python2.7-minimal_2.7.13-2+deb9u3_amd64.deb" 37 | extract_deb "python2.7/libpython2.7-minimal_2.7.13-2+deb9u3_amd64.deb" 38 | extract_deb "zlib/zlib1g_1.2.8.dfsg-5_amd64.deb" 39 | extract_deb "glibc/libc6_2.24-11+deb9u4_amd64.deb" 40 | extract_deb "gcc-6/libgcc1_6.3.0-18+deb9u1_amd64.deb" 41 | extract_deb "gcc-6/gcc-6-base_6.3.0-18+deb9u1_amd64.deb" 42 | 43 | build_box 44 | clean_box 45 | manifest_box >> $MANIFEST 46 | } 47 | 48 | make_python3() { 49 | export BOX="python3" 50 | empty_box 51 | # python3.5 with dependencies 52 | extract_deb "python3.5/python3.5-minimal_3.5.3-1+deb9u1_amd64.deb" 53 | extract_deb "python3.5/libpython3.5-minimal_3.5.3-1+deb9u1_amd64.deb" 54 | extract_deb "expat/libexpat1_2.2.0-2+deb9u3_amd64.deb" 55 | extract_deb "zlib/zlib1g_1.2.8.dfsg-5_amd64.deb" 56 | extract_deb "openssl/libssl1.1_1.1.0l-1~deb9u1_amd64.deb" 57 | extract_deb "glibc/libc6_2.24-11+deb9u4_amd64.deb" 58 | extract_deb "gcc-6/libgcc1_6.3.0-18+deb9u1_amd64.deb" 59 | extract_deb "gcc-6/gcc-6-base_6.3.0-18+deb9u1_amd64.deb" 60 | 61 | create_file "/dev/urandom" 62 | 63 | build_box 64 | clean_box 65 | manifest_box >> $MANIFEST 66 | } 67 | 68 | make_python3_9() { 69 | short_box_name="python3_9" 70 | box_name="compiler-python3.9.2-numpy_amd64.tar.gz" 71 | box_csum=`cat $MANIFEST | grep $BOX | awk '{ print $1 }'` 72 | URL="https://downloads.sio2project.mimuw.edu.pl/sandboxes" 73 | 74 | if [ -e "$box_name" ]; then 75 | csum=`sha256sum "$box_name" | awk '{ print $1 }'` 76 | if [ "$csum" != "$box_csum" ]; then 77 | echo "** Box $box_name has changed, removing it" 78 | rm "$box_name" 79 | rm -r "$short_box_name" 80 | echo "** Downloading box $box_name" 81 | [ -e "$box_name" ] || wget "$URL/$box_name" 82 | else 83 | echo "** Box $box_name hasn't changed, using it" 84 | fi 85 | else 86 | echo "** Downloading box $box_name" 87 | [ -e "$box_name" ] || wget "$URL/$box_name" 88 | fi 89 | 90 | } 91 | 92 | echo -n > $MANIFEST 93 | make_minimal 94 | make_busybox 95 | make_python2 96 | make_python3 97 | make_python3_9 98 | -------------------------------------------------------------------------------- /boxes/manifest.txt: -------------------------------------------------------------------------------- 1 | 6a407eca8c07103a0b10b874978cf8ac9fa37aac94be04789ce564796a66ee6b minimal.tar.gz 2 | acaf98661235d1e88ebdc20b9381cbe3fc7341130e592bbda2277bedbb56834f busybox.tar.gz 3 | c451406e90a6a5d906c591c563fc49dd91e11579121858e4947145d9ec7749e7 python2.tar.gz 4 | b2e55c56c7ae4c1ede60681cbd5e883e4a893e1d842a63be453a63f76396e160 python3.tar.gz 5 | dd9645204fc7477130fef4ac12cdb2a7e27c73c894b52a2591aec7a1e78f2878 compiler-python3.9.2-numpy_amd64.tar.gz -------------------------------------------------------------------------------- /boxes/utils.sh: -------------------------------------------------------------------------------- 1 | # Various functions usefull during box creation 2 | 3 | empty_box() { 4 | mkdir -pv "$BOX"/{,proc} 5 | touch "$BOX/exe" 6 | chmod +wx "$BOX/exe" 7 | } 8 | 9 | build_box() { 10 | tar -cvf "$BOX.tar.gz" "$BOX" 11 | } 12 | 13 | clean_box() { 14 | rm -rfv "$BOX" 15 | } 16 | 17 | extract_deb() { 18 | repo="http://archive.debian.org/debian/pool/main" 19 | path="$1" 20 | dpkg=`basename "$path"` 21 | 22 | [ -e "$dpkg" ] || wget -O "$dpkg" "$repo/${path:0:1}/$path" 23 | dpkg -X "$dpkg" "$BOX" 24 | } 25 | 26 | create_file() { 27 | mkdir -pv `dirname "$BOX/$1"` 28 | touch "$BOX/$1" 29 | } 30 | 31 | manifest_box() { 32 | box_file="$BOX.tar.gz" 33 | sha256sum "$box_file" 34 | } 35 | -------------------------------------------------------------------------------- /debian/README: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | sio2jail (1.5.0) UNRELEASED; urgency=medium 2 | 3 | * Add support for Intel hybrid CPUs (https://github.com/sio2project/sio2jail/pull/46) 4 | * Added mremap tracing and fixed a bug in MemoryLimitListener (https://github.com/sio2project/sio2jail/pull/43) 5 | * Documentation improvements (https://github.com/sio2project/sio2jail/pull/37, https://github.com/sio2project/sio2jail/pull/44) 6 | 7 | -- Mateusz Masiarz Mon, 15 Apr 2024 14:08:58 +0100 8 | 9 | sio2jail (1.4.4) UNRELEASED; urgency=medium 10 | 11 | * Version 1.4.4 12 | 13 | -- Mateusz Masiarz Wed, 13 Sep 2023 12:23:31 +0100 14 | 15 | sio2jail (1.4.3) UNRELEASED; urgency=medium 16 | 17 | * Initial Release. 18 | 19 | -- Michał Sidor Sun, 12 Jan 2020 15:24:25 +0100 20 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: sio2jail 2 | Section: utils 3 | Priority: optional 4 | Maintainer: Oi Admins 5 | Build-Depends: debhelper (>= 11), cmake 6 | Standards-Version: 4.1.3 7 | Homepage: https://github.com/sio2project/sio2jail 8 | Vcs-Browser: https://github.com/sio2project/sio2jail 9 | Vcs-Git: https://github.com/sio2project/sio2jail.git 10 | 11 | Package: sio2jail 12 | Architecture: any 13 | Depends: ${shlibs:Depends}, ${misc:Depends} 14 | Description: Sandbox for programming contests. 15 | A tool for supervising execution of programs 16 | submitted in algorithmic competitions. 17 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Wojciech Dubiel, Tadeusz Dudkiewicz, Przemysław Jakub Kozłowski, Maciej Wachulec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | #export DH_VERBOSE = 1 4 | export DEB_BUILD_MAINT_OPTIONS = hardening=+all 5 | #export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic 6 | #export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed 7 | 8 | %: 9 | dh $@ --buildsystem=cmake 10 | 11 | CCACHE := $(shell ccache --version 2>/dev/null) 12 | ifdef CCACHE 13 | CC := ccache 14 | endif 15 | 16 | OPTIONS = -DCMAKE_BUILD_TYPE=Release -DARCH=$(DEB_TARGET_ARCH) -DCMAKE_CXX_COMPILER_LAUNCHER=$(CC) -DWITH_DOCS=YES 17 | DEPENDENCIES_CONFIG = -DLINK=STATIC -DLIBCAP_BUILD_OWN=YES -DLIBSECCOMP_BUILD_OWN=YES -DLIBTCLAP_BUILD_OWN=YES -DSCDOC_BUILD_OWN=YES 18 | 19 | override_dh_auto_configure: 20 | dh_auto_configure -- $(OPTIONS) $(DEPENDENCIES_CONFIG) 21 | 22 | override_dh_auto_test: 23 | echo Tests skipped 24 | -------------------------------------------------------------------------------- /debian/sio2jail-docs.docs: -------------------------------------------------------------------------------- 1 | README 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /doc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ADD_CUSTOM_COMMAND(OUTPUT sio2jail.1 2 | COMMAND ${scdoc_BINARY_PATH} <${CMAKE_CURRENT_SOURCE_DIR}/sio2jail.1.scd >sio2jail.1 3 | DEPENDS sio2jail.1.scd) 4 | 5 | ADD_CUSTOM_TARGET(doc ALL DEPENDS sio2jail.1 scdoc) 6 | 7 | INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/sio2jail.1 DESTINATION "${CMAKE_INSTALL_FULL_MANDIR}/man1") 8 | -------------------------------------------------------------------------------- /external/libcap.cmake: -------------------------------------------------------------------------------- 1 | IF(DEFINED LIBCAP_BUILD_OWN AND NOT "${LIBCAP_BUILD_OWN}" MATCHES "YES|NO") 2 | MESSAGE(FATAL_ERROR "LIBCAP_BUILD_OWN should be one of: YES, NO") 3 | ENDIF() 4 | 5 | ADD_CUSTOM_TARGET(libcap) 6 | 7 | IF(NOT DEFINED LIBCAP_BUILD_OWN OR LIBCAP_BUILD_OWN STREQUAL "NO") 8 | FIND_PATH( 9 | libcap_INC_PATH 10 | NAMES sys/capability.h 11 | PATHS "${LIBCAP_PREFIX}" "${LIBCAP_PREFIX}/usr/include" 12 | ) 13 | ENDIF() 14 | 15 | IF((NOT DEFINED LIBCAP_BUILD_OWN AND NOT EXISTS "${libcap_INC_PATH}") OR LIBCAP_BUILD_OWN STREQUAL "YES") 16 | ExternalProject_Add(libcap_project 17 | URL https://mirrors.edge.kernel.org/pub/linux/libs/security/linux-privs/libcap2/libcap-2.25.tar.xz 18 | URL_HASH SHA256=693c8ac51e983ee678205571ef272439d83afe62dd8e424ea14ad9790bc35162 19 | 20 | PATCH_COMMAND 21 | patch -p1 -i ${CMAKE_CURRENT_LIST_DIR}/libcap.patch 22 | CONFIGURE_COMMAND 23 | true 24 | BUILD_IN_SOURCE true 25 | BUILD_COMMAND 26 | make FAKEROOT= 27 | INSTALL_COMMAND 28 | make FAKEROOT= RAISE_SETFCAP=no install 29 | ) 30 | 31 | ExternalProject_Get_Property(libcap_project 32 | INSTALL_DIR) 33 | SET(libcap_INC_PATH "${INSTALL_DIR}/usr/include") 34 | 35 | ADD_DEPENDENCIES(libcap libcap_project) 36 | ENDIF() 37 | 38 | IF(libcap_INC_PATH MATCHES NOTFOUND) 39 | MESSAGE(FATAL_ERROR "libcap not found, run with -DLIBCAP_BUILD_OWN=YES to build it from source") 40 | ENDIF() 41 | 42 | MESSAGE("-- Libcap configuration:") 43 | MESSAGE("-- - include directory: ${libcap_INC_PATH}") 44 | 45 | INCLUDE_DIRECTORIES("${libcap_INC_PATH}") 46 | -------------------------------------------------------------------------------- /external/libcap.patch: -------------------------------------------------------------------------------- 1 | diff -u -r libcap_project/libcap/Makefile libcap-2.25/libcap/Makefile 2 | --- a/libcap/Makefile 2016-01-31 01:01:41.000000000 +0100 3 | +++ b/libcap/Makefile 2018-10-24 00:35:57.084332362 +0200 4 | @@ -41,7 +41,7 @@ 5 | ./_makenames > cap_names.h 6 | 7 | $(GPERF_OUTPUT): cap_names.list.h 8 | - perl -e 'print "struct __cap_token_s { const char *name; int index; };\n%{\nconst struct __cap_token_s *__cap_lookup_name(const char *, unsigned int);\n%}\n%%\n"; while ($$l = <>) { $$l =~ s/[\{\"]//g; $$l =~ s/\}.*// ; print $$l; }' < $< | gperf --ignore-case --language=ANSI-C --readonly --null-strings --global-table --hash-function-name=__cap_hash_name --lookup-function-name="__cap_lookup_name" -c -t -m20 $(INDENT) > $@ 9 | + perl -e 'print "struct __cap_token_s { const char *name; int index; };\n%%\n"; while ($$l = <>) { $$l =~ s/[\{\"]//g; $$l =~ s/\}.*// ; print $$l; }' < $< | gperf --ignore-case --language=ANSI-C --includes --readonly --null-strings --global-table --hash-function-name=__cap_hash_name --lookup-function-name="__cap_lookup_name" -c -t -m20 $(INDENT) > $@ 10 | 11 | cap_names.list.h: Makefile $(KERNEL_HEADERS)/linux/capability.h 12 | @echo "=> making $@ from $(KERNEL_HEADERS)/linux/capability.h" 13 | -------------------------------------------------------------------------------- /external/libseccomp.cmake: -------------------------------------------------------------------------------- 1 | IF(DEFINED LIBSECCOMP_BUILD_OWN AND NOT "${LIBSECCOMP_BUILD_OWN}" MATCHES "YES|NO") 2 | MESSAGE(FATAL_ERROR "LIBSECCOMP_BUILD_OWN should be one of: YES, NO") 3 | ENDIF() 4 | 5 | ADD_LIBRARY(seccomp STATIC IMPORTED) 6 | 7 | IF(NOT DEFINED LIBSECCOMP_BUILD_OWN OR LIBSECCOMP_BUILD_OWN STREQUAL "NO") 8 | IF(LINK STREQUAL "STATIC") 9 | SET(libseccomp_LIB_FILE_NAME "libseccomp.a") 10 | ELSE() 11 | SET(libseccomp_LIB_FILE_NAME "libseccomp.so") 12 | ENDIF() 13 | FIND_FILE( 14 | libseccomp_LIB_PATH 15 | NAMES "${libseccomp_LIB_FILE_NAME}" 16 | PATHS "${LIBSECCOMP_PREFIX}" "${LIBSECCOMP_PREFIX}/lib" "${LIBSECCOMP_PREFIX}/usr/lib" 17 | ) 18 | FIND_PATH( 19 | libseccomp_INC_PATH 20 | NAMES seccomp.h 21 | PATHS "${LIBSECCOMP_PREFIX}" "${LIBSECCOMP_PREFIX}/usr/include" 22 | ) 23 | IF(libseccomp_LIB_FILE_NAME MATCHES "NOTFOUND") 24 | MESSAGE("-- Libseccomp not found") 25 | ELSE() 26 | EXECUTE_PROCESS( 27 | COMMAND 28 | bash -c " 29 | exe=`mktemp` 30 | echo -e '#include\n#include\nint main(){printf(\"%d.%d\",SCMP_VER_MAJOR,SCMP_VER_MINOR);}' \ 31 | | gcc -I ${libseccomp_INC_PATH} -xc /dev/stdin -o $exe >/dev/null 2>&1 && $exe 32 | rc=$? 33 | rm -f $exe 34 | exit $rc" 35 | OUTPUT_VARIABLE libseccomp_VERSION 36 | RESULT_VARIABLE libseccomp_VERSION_RC 37 | ) 38 | IF(NOT libseccomp_VERSION_RC EQUAL 0 OR libseccomp_VERSION VERSION_LESS 2.3) 39 | SET(libseccomp_LIB_PATH "libseccomp_LIB_PATH-NOTFOUND") 40 | SET(libseccomp_INC_PATH "libseccomp_INC_PATH-NOTFOUND") 41 | IF (NOT libseccomp_VERSION_RC EQUAL 0) 42 | MESSAGE("-- failed to compile Libseccomp test program") 43 | ELSE() 44 | MESSAGE("-- found Libseccomp in version ${libseccomp_VERSION}, but minimal required version is 2.3") 45 | ENDIF() 46 | ENDIF() 47 | ENDIF() 48 | ENDIF() 49 | 50 | IF((NOT DEFINED LIBSECCOMP_BUILD_OWN AND (NOT EXISTS "${libseccomp_LIB_PATH}" OR NOT EXISTS "${libseccomp_LIB_PATH}")) 51 | OR LIBSECCOMP_BUILD_OWN STREQUAL "YES") 52 | 53 | IF(ARCH STREQUAL "i386") 54 | SET(EXTRA_FLAGS "-m32") 55 | ELSEIF(ARCH STREQUAL "x86_64") 56 | SET(EXTRA_FLAGS "-m64") 57 | ELSE() 58 | SET(EXTRA_FLAGS "") 59 | ENDIF() 60 | 61 | ExternalProject_Add(seccomp_project 62 | URL https://github.com/seccomp/libseccomp/releases/download/v2.5.4/libseccomp-2.5.4.tar.gz 63 | URL_HASH SHA256=d82902400405cf0068574ef3dc1fe5f5926207543ba1ae6f8e7a1576351dcbdb 64 | 65 | CONFIGURE_COMMAND 66 | CFLAGS=${EXTRA_FLAGS} CXXFLAGS=${EXTRA_FLAGS} /configure 67 | --prefix= 68 | --enable-static 69 | BUILD_COMMAND 70 | make 71 | INSTALL_COMMAND 72 | make install 73 | 74 | BUILD_BYPRODUCTS 75 | /lib/libseccomp.a 76 | ) 77 | 78 | ExternalProject_Get_Property(seccomp_project 79 | INSTALL_DIR) 80 | IF(LINK STREQUAL "STATIC") 81 | SET(libseccomp_LIB_PATH "${INSTALL_DIR}/lib/libseccomp.a") 82 | ELSE() 83 | MESSAGE(FATAL_ERROR "-- Can't dynamically link to custom Libseccomp build") 84 | ENDIF() 85 | SET(libseccomp_INC_PATH "${INSTALL_DIR}/include") 86 | 87 | ADD_DEPENDENCIES(seccomp seccomp_project) 88 | ENDIF() 89 | 90 | IF(libseccomp_LIB_PATH MATCHES NOTFOUND OR libseccomp_INC_PATH MATCHES NOTFOUND) 91 | MESSAGE(FATAL_ERROR "libseccomp not found, run with -DLIBSECCOMP_BUILD_OWN=YES to build it from source") 92 | ENDIF() 93 | 94 | MESSAGE("-- Libseccomp configuration:") 95 | MESSAGE("-- - library: ${libseccomp_LIB_PATH}") 96 | MESSAGE("-- - include directory: ${libseccomp_INC_PATH}") 97 | 98 | SET_PROPERTY(TARGET seccomp 99 | PROPERTY IMPORTED_LOCATION 100 | "${libseccomp_LIB_PATH}" 101 | ) 102 | INCLUDE_DIRECTORIES("${libseccomp_INC_PATH}") 103 | -------------------------------------------------------------------------------- /external/libtclap.cmake: -------------------------------------------------------------------------------- 1 | IF(DEFINED LIBTCLAP_BUILD_OWN AND NOT "${LIBTCLAP_BUILD_OWN}" MATCHES "YES|NO") 2 | MESSAGE(FATAL_ERROR "LIBTCLAP_BUILD_OWN should be one of: YES, NO") 3 | ENDIF() 4 | 5 | ADD_CUSTOM_TARGET(libtclap) 6 | 7 | IF(NOT DEFINED LIBTCLAP_BUILD_OWN OR LIBTCLAP_BUILD_OWN STREQUAL "NO") 8 | FIND_PATH( 9 | libtclap_INC_PATH 10 | NAMES tclap/CmdLine.h 11 | PATHS "${LIBTCLAP_PREFIX}" "${LIBTCLAP_PREFIX}/usr/include" 12 | ) 13 | ENDIF() 14 | 15 | IF((NOT DEFINED LIBTCLAP_BUILD_OWN AND NOT EXISTS "${libtclap_INC_PATH}") OR LIBTCLAP_BUILD_OWN STREQUAL "YES") 16 | ExternalProject_Add(libtclap_project 17 | URL https://netcologne.dl.sourceforge.net/project/tclap/tclap-1.2.2.tar.gz 18 | URL_HASH SHA256=f5013be7fcaafc69ba0ce2d1710f693f61e9c336b6292ae4f57554f59fde5837 19 | 20 | CONFIGURE_COMMAND 21 | /configure --prefix= 22 | BUILD_COMMAND 23 | true 24 | INSTALL_COMMAND 25 | make -C /include install 26 | ) 27 | 28 | ExternalProject_Get_Property(libtclap_project 29 | INSTALL_DIR) 30 | SET(libtclap_INC_PATH "${INSTALL_DIR}/include") 31 | 32 | ADD_DEPENDENCIES(libtclap libtclap_project) 33 | ENDIF() 34 | 35 | IF(libtclap_INC_PATH MATCHES NOTFOUND) 36 | MESSAGE(FATAL_ERROR "libtclap not found, run with -DLIBTCLAP_BUILD_OWN=YES to build it from source") 37 | ENDIF() 38 | 39 | MESSAGE("-- Libtclap configuration:") 40 | MESSAGE("-- - include directory: ${libtclap_INC_PATH}") 41 | 42 | INCLUDE_DIRECTORIES("${libtclap_INC_PATH}") 43 | -------------------------------------------------------------------------------- /external/scdoc.cmake: -------------------------------------------------------------------------------- 1 | IF(DEFINED SCDOC_BUILD_OWN AND NOT "${SCDOC_BUILD_OWN}" MATCHES "YES|NO") 2 | MESSAGE(FATAL_ERROR "SCDOC_BUILD_OWN should be one of: YES, NO") 3 | ENDIF() 4 | 5 | ADD_CUSTOM_TARGET(scdoc) 6 | 7 | IF(NOT DEFINED SCDOC_BUILD_OWN OR SCDOC_BUILD_OWN STREQUAL "NO") 8 | FIND_FILE( 9 | scdoc_BINARY_PATH 10 | NAMES scdoc 11 | PATHS "${SCDOC_PREFIX}" "${SCDOC_PREFIX}/usr/bin" "${SCDOC_PREFIX}/bin" 12 | ) 13 | ENDIF() 14 | 15 | IF((NOT DEFINED SCDOC_BUILD_OWN AND NOT EXISTS "${scdoc_BINARY_PATH}") OR SCDOC_BUILD_OWN STREQUAL "YES") 16 | ExternalProject_Add(scdoc_project 17 | URL https://git.sr.ht/~sircmpwn/scdoc/archive/1.5.2.tar.gz 18 | URL_HASH SHA256=86591de3741bea5443e7fbc11ff9dc22da90621105b06be524422efd5dec3a29 19 | 20 | CONFIGURE_COMMAND 21 | true 22 | BUILD_IN_SOURCE true 23 | BUILD_COMMAND 24 | make PREFIX= 25 | INSTALL_COMMAND 26 | make PREFIX= install 27 | ) 28 | 29 | ExternalProject_Get_Property(scdoc_project 30 | INSTALL_DIR) 31 | SET(scdoc_BINARY_PATH ${INSTALL_DIR}/bin/scdoc) 32 | 33 | ADD_DEPENDENCIES(scdoc scdoc_project) 34 | ENDIF() 35 | 36 | IF(scdoc_BINARY_PATH MATCHES NOTFOUND) 37 | MESSAGE(FATAL_ERROR "scdoc binary not found, run with -DSCDOC_BUILD_OWN=YES to build one from source") 38 | ENDIF() 39 | 40 | MESSAGE("-- Scdoc configuration:") 41 | MESSAGE("-- - binary: ${scdoc_BINARY_PATH}") 42 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | INCLUDE_DIRECTORIES(./) 2 | 3 | FILE(GLOB_RECURSE sources *.cc) 4 | 5 | ADD_EXECUTABLE(sio2jail ${sources}) 6 | TARGET_LINK_LIBRARIES(sio2jail seccomp rt pthread) 7 | ADD_DEPENDENCIES(sio2jail libcap libtclap) 8 | 9 | INSTALL(TARGETS sio2jail DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}") 10 | 11 | FILE(GLOB_RECURSE all_sources *.cc *.h *.hpp) 12 | 13 | ADD_CUSTOM_TARGET( 14 | clang-format 15 | COMMAND 16 | clang-format -style=file -i ${all_sources} 17 | WORKING_DIRECTORY 18 | ${CMAKE_CURRENT_SOURCE_DIR}) 19 | 20 | SET(clang_tidy_cxx_flags -x;c++;-std=gnu++14) 21 | 22 | GET_PROPERTY(include_directories DIRECTORY PROPERTY INCLUDE_DIRECTORIES) 23 | FOREACH(include_directory IN LISTS include_directories) 24 | SET(clang_tidy_cxx_flags ${clang_tidy_cxx_flags};-I${include_directory}) 25 | ENDFOREACH() 26 | 27 | ADD_CUSTOM_TARGET( 28 | clang-tidy 29 | COMMAND 30 | clang-tidy ${sources} -- ${clang_tidy_cxx_flags} 31 | WORKING_DIRECTORY 32 | ${CMAKE_CURRENT_SOURCE_DIR}) 33 | 34 | ADD_CUSTOM_TARGET( 35 | clang-tidy-fix 36 | COMMAND 37 | clang-tidy ${sources} -fix -fix-errors -- ${clang_tidy_cxx_flags} 38 | WORKING_DIRECTORY 39 | ${CMAKE_CURRENT_SOURCE_DIR}) 40 | -------------------------------------------------------------------------------- /src/common/Assert.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Exception.h" 4 | #include "Preprocessor.h" 5 | 6 | #include "logger/Logger.h" 7 | 8 | #ifdef assert 9 | #undef assert 10 | #endif 11 | 12 | #define assert(...) CAT(assert_, VA_SIZE(__VA_ARGS__))(__VA_ARGS__) 13 | 14 | #define assert_1(assertion) \ 15 | if (!(assertion)) { \ 16 | throw s2j::AssertionException( \ 17 | "Assertion failed at " __FILE__ ":" + \ 18 | std::to_string(__LINE__) + " " #assertion); \ 19 | } 20 | 21 | #define assert_2(assertion, comment) \ 22 | if (!(assertion)) { \ 23 | throw s2j::AssertionException( \ 24 | "Assertion " + std::string(comment) + \ 25 | " failed at " __FILE__ ":" + std::to_string(__LINE__) + \ 26 | " " #assertion); \ 27 | } 28 | 29 | namespace s2j { 30 | 31 | class AssertionException : public Exception { 32 | public: 33 | AssertionException(const std::string& msg) : Exception(msg) { 34 | logger::error(msg); 35 | } 36 | }; 37 | 38 | } // namespace s2j 39 | -------------------------------------------------------------------------------- /src/common/EventProvider.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace s2j { 7 | 8 | template 9 | class EventProvider { 10 | public: 11 | void addEventListener(std::shared_ptr eventListener) { 12 | if (eventListener != nullptr) { 13 | eventListeners_.push_back(std::move(eventListener)); 14 | } 15 | } 16 | 17 | protected: 18 | std::vector> eventListeners_; 19 | }; 20 | 21 | } // namespace s2j 22 | -------------------------------------------------------------------------------- /src/common/Exception.cc: -------------------------------------------------------------------------------- 1 | #include "Exception.h" 2 | 3 | #include "logger/Logger.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace s2j { 9 | 10 | Exception::Exception(const std::string& msg) : msg_(msg) { 11 | logger::error("Exception: ", msg); 12 | } 13 | 14 | const char* Exception::what() const noexcept { 15 | return msg_.c_str(); 16 | } 17 | 18 | SystemException::SystemException(const std::string& msg) 19 | : SystemException(msg, errno) {} 20 | 21 | SystemException::SystemException(const std::string& msg, int errnoNumber) 22 | : Exception( 23 | "System error occured: " + msg + ": error " + 24 | std::to_string(errnoNumber) + ": " + strerror(errnoNumber)) 25 | , errno_(errnoNumber) {} 26 | 27 | uint32_t SystemException::getErrno() const { 28 | return errno_; 29 | } 30 | 31 | } // namespace s2j 32 | -------------------------------------------------------------------------------- /src/common/Exception.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace s2j { 7 | 8 | class Exception : public std::exception { 9 | public: 10 | Exception(const std::string& msg); 11 | 12 | const char* what() const noexcept; 13 | 14 | private: 15 | const std::string msg_; 16 | }; 17 | 18 | class SystemException : public Exception { 19 | public: 20 | SystemException(const std::string& msg); 21 | SystemException(const std::string& msg, int errnoNumber); 22 | 23 | uint32_t getErrno() const; 24 | 25 | private: 26 | const uint32_t errno_; 27 | }; 28 | 29 | class NotImplementedException : public Exception { 30 | public: 31 | NotImplementedException(const std::string& msg) : Exception(msg){}; 32 | }; 33 | 34 | } // namespace s2j 35 | 36 | #define NOT_IMPLEMENTED() \ 37 | throw s2j::NotImplementedException( \ 38 | "Not implemented: " __FILE__ ":" + std::to_string(__LINE__)) 39 | -------------------------------------------------------------------------------- /src/common/FD.cc: -------------------------------------------------------------------------------- 1 | #include "FD.h" 2 | 3 | #include "Exception.h" 4 | #include "WithErrnoCheck.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace s2j { 10 | 11 | FD::FD(int fd, bool close) : close_(close) { 12 | if (fd < 0) { 13 | throw Exception("tried to wrap a negative fd"); 14 | } 15 | fd_ = fd; 16 | } 17 | 18 | FD::~FD() { 19 | if (close_) { 20 | try { 21 | close(); 22 | } 23 | catch (...) { 24 | } 25 | } 26 | } 27 | 28 | void FD::close() { 29 | if (fd_ >= 0) { 30 | s2j::withErrnoCheck("close", ::close, fd_); 31 | fd_ = -1; 32 | } 33 | } 34 | 35 | FD& FD::operator<<(const std::string& str) { 36 | write(str, false); 37 | return *this; 38 | } 39 | 40 | void FD::write(const std::string& str, bool allowPartialWrites) { 41 | for (size_t written = 0; written < str.size();) { 42 | ssize_t result = s2j::withErrnoCheck( 43 | "write", 44 | {EAGAIN, EINTR}, 45 | ::write, 46 | fd_, 47 | str.c_str() + written, 48 | str.size() - written); 49 | if (result > 0) { 50 | written += result; 51 | } 52 | if (!allowPartialWrites && written != str.size()) { 53 | throw SystemException( 54 | "Partial write: " + std::to_string(written) + "/" + 55 | std::to_string(str.size())); 56 | } 57 | } 58 | } 59 | 60 | FD FD::open(const std::string& path, int flags) { 61 | return FD::withErrnoCheck("open " + path, ::open, path.c_str(), flags); 62 | } 63 | 64 | FD FD::open(const std::string& path, int flags, mode_t mode) { 65 | return FD::withErrnoCheck( 66 | "open " + path, ::open, path.c_str(), flags, mode); 67 | } 68 | 69 | FD::operator int() const { 70 | return fd_; 71 | } 72 | 73 | bool FD::good() const { 74 | int errnoCode = 0; 75 | do { 76 | errnoCode = s2j::withErrnoCheck( 77 | "fcntl", 78 | std::initializer_list{EBADF, EINTR, EAGAIN}, 79 | ::fcntl, 80 | fd_, 81 | F_GETFD) 82 | .getErrnoCode(); 83 | if (errnoCode == EBADF) { 84 | return false; 85 | } 86 | } while (errnoCode < 0); 87 | return true; 88 | } 89 | 90 | } // namespace s2j 91 | -------------------------------------------------------------------------------- /src/common/FD.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Exception.h" 4 | #include "WithErrnoCheck.h" 5 | 6 | #include 7 | 8 | namespace s2j { 9 | 10 | class FD { 11 | public: 12 | FD(int fd, bool close = false); 13 | FD(const FD&) = delete; 14 | FD(FD&&) = default; 15 | virtual ~FD(); 16 | 17 | void close(); 18 | 19 | void write(const std::string& str, bool allowPartialWrites = true); 20 | FD& operator<<(const std::string& str); 21 | 22 | operator int() const; 23 | 24 | bool good() const; 25 | 26 | static FD open(const std::string& path, int flags); 27 | static FD open(const std::string& path, int flags, mode_t mode); 28 | 29 | private: 30 | template 31 | static FD withErrnoCheck( 32 | const std::string& description, 33 | Operation operation, 34 | Args... args) { 35 | int fd = s2j::withErrnoCheck(description, operation, args...); 36 | return FD(fd, true); 37 | } 38 | 39 | int fd_; 40 | bool close_; 41 | }; 42 | 43 | } // namespace s2j 44 | -------------------------------------------------------------------------------- /src/common/Feature.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace s2j { 4 | 5 | enum class Feature { 6 | PTRACE, 7 | PERF, 8 | SECCOMP, 9 | PID_NAMESPACE, 10 | NET_NAMESPACE, 11 | IPC_NAMESPACE, 12 | UTS_NAMESPACE, 13 | USER_NAMESPACE, 14 | MOUNT_NAMESPACE, 15 | MOUNT_PROCFS, 16 | CAPABILITY_DROP 17 | }; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/common/Preprocessor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Simple preprocessor helper library based on various 5 | * tutorials and ansers from StackOverflow. 6 | */ 7 | 8 | #define EVAL(...) _EVAL_5(_EVAL_5(_EVAL_5(_EVAL_5(__VA_ARGS__)))) 9 | #define _EVAL_5(...) _EVAL_4(_EVAL_4(_EVAL_4(_EVAL_4(__VA_ARGS__)))) 10 | #define _EVAL_4(...) _EVAL_3(_EVAL_3(_EVAL_3(_EVAL_3(__VA_ARGS__)))) 11 | #define _EVAL_3(...) _EVAL_2(_EVAL_2(_EVAL_2(_EVAL_2(__VA_ARGS__)))) 12 | #define _EVAL_2(...) _EVAL_1(_EVAL_1(_EVAL_1(_EVAL_1(__VA_ARGS__)))) 13 | #define _EVAL_1(...) _EVAL_0(_EVAL_0(_EVAL_0(_EVAL_0(__VA_ARGS__)))) 14 | #define _EVAL_0(...) __VA_ARGS__ 15 | 16 | #define CAT(a, ...) _CAT(a, __VA_ARGS__) 17 | #define _CAT(a, ...) a##__VA_ARGS__ 18 | 19 | #define EMPTY() 20 | #define DEFER(...) __VA_ARGS__ EMPTY() 21 | #define DEFER2(...) __VA_ARGS__ DEFER(EMPTY)() 22 | 23 | /** 24 | * Evaluates 1 when argument list is empty, to 0 otherwise 25 | */ 26 | #define VA_EMPTY(...) \ 27 | _VA_EMPTY(_, ##__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1) 28 | #define _VA_EMPTY( \ 29 | _, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e, X, ...) \ 30 | X 31 | 32 | /** 33 | * Evaluates to list size in hex, from 0 to e. 34 | */ 35 | #define VA_SIZE(...) \ 36 | _VA_SIZE(_, ##__VA_ARGS__, e, d, c, b, a, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 37 | #define _VA_SIZE( \ 38 | _, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e, X, ...) \ 39 | X 40 | 41 | /** 42 | * Simple map function. 43 | */ 44 | #define MAP(...) EVAL(_MAP(__VA_ARGS__)) 45 | #define _MAP(ACTION, ...) \ 46 | DEFER2(CAT(_MAP_, VA_EMPTY(__VA_ARGS__)))(ACTION, __VA_ARGS__) 47 | #define _MAP_1(...) 48 | #define _MAP_0(ACTION, X, ...) ACTION(X) DEFER2(_MAP)(ACTION, __VA_ARGS__) 49 | -------------------------------------------------------------------------------- /src/common/ProcFS.cc: -------------------------------------------------------------------------------- 1 | #include "ProcFS.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace { 10 | 11 | struct FieldReader { 12 | std::string label; 13 | std::function read; 14 | }; 15 | 16 | auto stoull = static_cast< 17 | unsigned long long (*)(const std::string&, std::size_t*, int)>( 18 | std::stoull); 19 | const std::map FIELD_READERS = { 20 | {s2j::procfs::Field::VM_PEAK, 21 | FieldReader{ 22 | "VmPeak", 23 | std::bind(stoull, std::placeholders::_1, nullptr, 10)}}, 24 | {s2j::procfs::Field::VM_SIZE, 25 | FieldReader{ 26 | "VmSize", 27 | std::bind(stoull, std::placeholders::_1, nullptr, 10)}}, 28 | {s2j::procfs::Field::SIG_CGT, 29 | FieldReader{ 30 | "SigCgt", 31 | std::bind(stoull, std::placeholders::_1, nullptr, 16)}}}; 32 | 33 | } // namespace 34 | 35 | namespace s2j { 36 | namespace procfs { 37 | 38 | uint64_t readProcFS(pid_t pid, Field field, uint64_t defaultValue) { 39 | // Read tracee vmpeak from /proc/pid/status 40 | std::ifstream status("/proc/" + std::to_string(pid) + "/status"); 41 | if (!status.good()) { 42 | return defaultValue; 43 | } 44 | 45 | const auto& fieldReader = FIELD_READERS.at(field); 46 | 47 | std::string statusLine; 48 | while (std::getline(status, statusLine)) { 49 | if (statusLine.compare( 50 | 0, fieldReader.label.size(), fieldReader.label) == 0) { 51 | auto valuePos = statusLine.find_first_not_of( 52 | " \t", fieldReader.label.size() + 1); 53 | return fieldReader.read(statusLine.substr(valuePos)); 54 | } 55 | } 56 | 57 | return defaultValue; 58 | } 59 | 60 | } // namespace procfs 61 | } // namespace s2j 62 | -------------------------------------------------------------------------------- /src/common/ProcFS.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | 8 | namespace s2j { 9 | namespace procfs { 10 | 11 | /** 12 | * Supported /proc/$PID/status fields 13 | */ 14 | enum class Field { VM_PEAK, VM_SIZE, SIG_CGT }; 15 | 16 | /** 17 | * Read a field from /proc/$PID/status file 18 | */ 19 | uint64_t readProcFS(pid_t pid, Field field, uint64_t defaultValue = 0); 20 | 21 | } // namespace procfs 22 | } // namespace s2j 23 | -------------------------------------------------------------------------------- /src/common/TypeList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace s2j { 4 | 5 | template 6 | struct TypeList; 7 | 8 | template<> 9 | struct TypeList<> { 10 | const static bool empty = true; 11 | }; 12 | 13 | template 14 | struct TypeList { 15 | const static bool empty = false; 16 | }; 17 | 18 | } // namespace s2j 19 | -------------------------------------------------------------------------------- /src/common/Utils.cc: -------------------------------------------------------------------------------- 1 | #include "Utils.h" 2 | #include "Exception.h" 3 | #include "WithErrnoCheck.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace s2j { 13 | 14 | char* stringToCStr(const std::string& str) { 15 | char* cStr = new char[str.size() + 1]; 16 | std::copy(str.begin(), str.end(), cStr); 17 | cStr[str.size()] = '\0'; 18 | return cStr; 19 | } 20 | 21 | std::vector split( 22 | const std::string& str, 23 | const std::string& delimeter) { 24 | std::vector tokens; 25 | std::string::size_type pos = 0; 26 | std::string::size_type end; 27 | while ((end = str.find(delimeter, pos)) != std::string::npos) { 28 | tokens.emplace_back(str.substr(pos, end - pos)); 29 | pos = end + delimeter.size(); 30 | } 31 | if (pos < str.size() && pos != std::string::npos) { 32 | tokens.emplace_back(str.substr(pos)); 33 | } 34 | return tokens; 35 | } 36 | 37 | std::string createTemporaryDirectory(const std::string& directoryTemplate) { 38 | char directory[directoryTemplate.size() + 1]; 39 | strncpy(directory, directoryTemplate.c_str(), sizeof(directory)); 40 | char* res = mkdtemp(directory); 41 | if (res == nullptr) { 42 | throw SystemException("mkdtemp failed"); 43 | } 44 | return directory; 45 | } 46 | 47 | bool checkKernelVersion(int major, int minor) { 48 | std::ifstream verfile("/proc/sys/kernel/osrelease"); 49 | if (!verfile.good()) { 50 | return false; 51 | } 52 | 53 | std::string verstr; 54 | if (!std::getline(verfile, verstr, '-')) { 55 | return false; 56 | } 57 | 58 | auto versplit = split(verstr, "."); 59 | std::vector verints; 60 | verints.reserve(versplit.size()); 61 | for (const auto& s: versplit) { 62 | verints.push_back(std::stoi(s)); 63 | } 64 | 65 | if (verints[0] < major) { 66 | return false; 67 | } 68 | if (verints[0] > major) { 69 | return true; 70 | } 71 | 72 | return verints[1] >= minor; 73 | } 74 | 75 | } // namespace s2j 76 | -------------------------------------------------------------------------------- /src/common/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace s2j { 10 | 11 | /** 12 | * Common typedefs for factories and their maps 13 | */ 14 | template 15 | using Factory = std::function()>; 16 | 17 | template 18 | using FactoryMap = std::map>; 19 | 20 | /** 21 | * Creates a c-style string and returns pointer it. Caller is responsible for 22 | * calling delete on returned pointer. 23 | */ 24 | char* stringToCStr(const std::string& str); 25 | 26 | /** 27 | * Splits string using given delimeter. 28 | */ 29 | std::vector split( 30 | const std::string& str, 31 | const std::string& delimeter); 32 | 33 | /** 34 | * Creates a temporary directory and returns path to it. 35 | * 36 | * As it uses mktemp, last six characters of template must be XXXXXX. 37 | */ 38 | std::string createTemporaryDirectory( 39 | const std::string& directoryTemplate = "/tmp/sio2jail-XXXXXX"); 40 | 41 | /** 42 | * Default to_string in s2j namespace, usefull for various overloads. 43 | */ 44 | template 45 | std::string to_string(const T& t) { 46 | return std::to_string(t); 47 | } 48 | 49 | bool checkKernelVersion(int major, int minor); 50 | 51 | } // namespace s2j 52 | -------------------------------------------------------------------------------- /src/common/WithErrnoCheck.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Exception.h" 4 | 5 | #include "logger/Logger.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace s2j { 13 | 14 | template 15 | class ResultErrnoPair { 16 | public: 17 | ResultErrnoPair(T&& t, int errnoCode) 18 | : value_(std::forward(t)), errnoCode_(errnoCode) {} 19 | 20 | operator T() { 21 | return value_; 22 | } 23 | 24 | template 25 | U as() { 26 | return reinterpret_cast(value_); 27 | } 28 | 29 | int getErrnoCode() const { 30 | return errnoCode_; 31 | } 32 | 33 | private: 34 | T value_; 35 | int errnoCode_; 36 | }; 37 | 38 | template< 39 | typename Operation, 40 | typename ErrnoCollection, 41 | typename ResultChecker, 42 | typename... Args> 43 | auto withErrnoCheck( 44 | const std::string& description, 45 | const ErrnoCollection& ignoredErrnos, 46 | ResultChecker&& resultChecker, 47 | Operation&& operation, 48 | Args&&... args) -> ResultErrnoPair { 49 | TRACE(description); 50 | 51 | errno = 0; 52 | auto result = operation(std::forward(args)...); 53 | auto returnedErrno = errno; 54 | logger::trace( 55 | "Operation returned ", result, " errno ", strerror(returnedErrno)); 56 | 57 | if (!resultChecker(result)) { 58 | if (std::find( 59 | ignoredErrnos.begin(), 60 | ignoredErrnos.end(), 61 | returnedErrno) == ignoredErrnos.end()) 62 | throw SystemException( 63 | description + " failed: " + strerror(returnedErrno), 64 | returnedErrno); 65 | } 66 | return ResultErrnoPair( 67 | std::move(result), returnedErrno); 68 | } 69 | 70 | template 71 | auto withErrnoCheck( 72 | const std::string& description, 73 | const ErrnoCollection& ignoredErrnos, 74 | Operation&& operation, 75 | Args&&... args) -> ResultErrnoPair { 76 | using CompareType = typename std::conditional< 77 | std::numeric_limits< 78 | std::result_of_t>::is_integer, 79 | std::result_of_t, 80 | int64_t>::type; 81 | 82 | return withErrnoCheck( 83 | description, 84 | ignoredErrnos, 85 | [](auto&& result) -> bool { 86 | return reinterpret_cast(result) >= 0; 87 | }, 88 | operation, 89 | args...); 90 | } 91 | 92 | template 93 | auto withErrnoCheck( 94 | const std::string& description, 95 | std::initializer_list ignoredErrnos, 96 | Operation&& operation, 97 | Args&&... args) { 98 | return withErrnoCheck, Args...>( 99 | description, 100 | ignoredErrnos, 101 | std::forward(operation), 102 | std::forward(args)...); 103 | } 104 | 105 | template 106 | auto withGuardedErrnoCheck( 107 | const std::string& description, 108 | ResultChecker&& resultChecker, 109 | Operation&& operation, 110 | Args&&... args) { 111 | return withErrnoCheck< 112 | Operation, 113 | std::initializer_list, 114 | ResultChecker, 115 | Args...>( 116 | description, 117 | {}, 118 | std::forward(resultChecker), 119 | std::forward(operation), 120 | std::forward(args)...); 121 | } 122 | 123 | template 124 | auto withErrnoCheck( 125 | const std::string& description, 126 | Operation&& operation, 127 | Args&&... args) { 128 | return withErrnoCheck, Args...>( 129 | description, 130 | {}, 131 | std::forward(operation), 132 | std::forward(args)...); 133 | } 134 | 135 | template 136 | auto withErrnoCheck(Operation operation, Args&&... args) 137 | -> decltype(operation(args...)) { 138 | return withErrnoCheck( 139 | "linux api call", 140 | std::forward(operation), 141 | std::forward(args)...); 142 | } 143 | 144 | } // namespace s2j 145 | -------------------------------------------------------------------------------- /src/executor/ExecuteAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace s2j { 4 | namespace executor { 5 | 6 | enum class ExecuteAction { CONTINUE = 0, KILL = 1 }; 7 | 8 | } 9 | } // namespace s2j 10 | -------------------------------------------------------------------------------- /src/executor/ExecuteEvent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace s2j { 6 | namespace executor { 7 | 8 | struct ExecuteEvent { 9 | pid_t pid{-1}; 10 | int exitStatus{0}; 11 | int signal{0}; 12 | bool exited{false}; 13 | bool killed{false}; 14 | bool stopped{false}; 15 | bool trapped{false}; 16 | }; 17 | 18 | } // namespace executor 19 | } // namespace s2j 20 | -------------------------------------------------------------------------------- /src/executor/ExecuteEventListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ExecuteAction.h" 4 | #include "ExecuteEvent.h" 5 | 6 | #include 7 | 8 | namespace s2j { 9 | namespace executor { 10 | 11 | class ExecuteEventListener { 12 | public: 13 | virtual ~ExecuteEventListener() = default; 14 | 15 | virtual void onPreFork() {} 16 | virtual void onPostForkChild() {} 17 | virtual void onPostForkParent(pid_t childPid) {} 18 | virtual ExecuteAction onExecuteEvent(const ExecuteEvent& executeEvent) { 19 | return ExecuteAction::CONTINUE; 20 | } 21 | virtual ExecuteAction onSigioSignal() { 22 | return ExecuteAction::CONTINUE; 23 | } 24 | virtual ExecuteAction onSigalrmSignal() { 25 | return ExecuteAction::CONTINUE; 26 | } 27 | virtual void onPostExecute() {} 28 | }; 29 | 30 | } // namespace executor 31 | } // namespace s2j 32 | -------------------------------------------------------------------------------- /src/executor/Executor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ExecuteEventListener.h" 4 | 5 | #include "common/EventProvider.h" 6 | #include "ns/MountEventListener.h" 7 | #include "printer/OutputSource.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace s2j { 13 | namespace executor { 14 | 15 | class Executor 16 | : public s2j::printer::OutputSource 17 | , public s2j::ns::MountEventListener 18 | , public EventProvider { 19 | public: 20 | Executor( 21 | std::string childProgramName, 22 | std::vector childProgramArgv, 23 | std::string childProgramWorkingDir, 24 | bool supportThreads = false); 25 | 26 | template 27 | void setChildProgramName(ProgramNameType&& programName) { 28 | childProgramName_ = std::forward(programName); 29 | } 30 | 31 | template 32 | void setChildProgramArgv(ProgramArgvType&& programArgv) { 33 | childProgramArgv_ = std::forward(programArgv); 34 | } 35 | 36 | void onProgramNameChange(const std::string& newProgramName); 37 | 38 | void execute(); 39 | 40 | private: 41 | void executeChild(); 42 | void executeParent(); 43 | void setupSignalHandling(); 44 | void killChild(); 45 | ExecuteAction checkSignals(); 46 | 47 | std::string childProgramName_; 48 | std::vector childProgramArgv_; 49 | std::string childProgramWorkingDir_; 50 | 51 | pid_t childPid_; 52 | const bool supportThreads_; 53 | }; 54 | 55 | } // namespace executor 56 | } // namespace s2j 57 | -------------------------------------------------------------------------------- /src/files/FilesListener.cc: -------------------------------------------------------------------------------- 1 | #include "FilesListener.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace s2j { 7 | namespace files { 8 | 9 | const std::string FilesListener::DEV_NULL = "/dev/null"; 10 | const std::string FilesListener::FDS_PATH = "/proc/self/fd/"; 11 | 12 | FilesListener::FilesListener(bool suppressStderr) 13 | : suppressStderr_(suppressStderr) {} 14 | 15 | void FilesListener::onPreFork() { 16 | TRACE(); 17 | 18 | // Gather all open fds we will want to close 19 | std::unique_ptr fdsDirectory( 20 | withErrnoCheck("open fds directory", opendir, FDS_PATH.c_str()), 21 | closedir); 22 | 23 | if (fdsDirectory == nullptr) { 24 | throw Exception("Can't open fds directory " + FDS_PATH); 25 | } 26 | 27 | for (struct dirent* entry = readdir(fdsDirectory.get()); entry != nullptr; 28 | entry = readdir(fdsDirectory.get())) { 29 | int fd; 30 | try { 31 | fd = std::stoi(entry->d_name); 32 | } 33 | catch (const std::exception&) { 34 | continue; 35 | } 36 | 37 | // Don't close stdin, stdout and logs output fd 38 | if ((fd >= 0 && fd <= 2) || s2j::logger::isLoggerFD(fd)) { 39 | continue; 40 | } 41 | fds_.push_back(fd); 42 | } 43 | 44 | // Open /dev/null for child 45 | devnull_ = 46 | withErrnoCheck("open /dev/null", open, DEV_NULL.c_str(), O_WRONLY); 47 | } 48 | 49 | void FilesListener::onPostForkChild() { 50 | TRACE(); 51 | 52 | // Redirect stderr to /dev/null 53 | if (suppressStderr_) { 54 | withErrnoCheck("redirect stderr to /dev/null", dup2, devnull_, 2); 55 | } 56 | withErrnoCheck("close /dev/null", close, devnull_); 57 | 58 | for (size_t fdsIndex = 0; fdsIndex < fds_.size();) { 59 | try { 60 | withErrnoCheck("close fd", close, fds_[fdsIndex]); 61 | } 62 | catch (const SystemException& ex) { 63 | if (ex.getErrno() == EINTR) { 64 | continue; 65 | } 66 | if (ex.getErrno() != EBADF) { 67 | throw; 68 | } 69 | } 70 | ++fdsIndex; 71 | } 72 | } 73 | 74 | } // namespace files 75 | } // namespace s2j 76 | -------------------------------------------------------------------------------- /src/files/FilesListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/WithErrnoCheck.h" 4 | #include "executor/ExecuteEventListener.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace s2j { 13 | namespace files { 14 | 15 | class FilesListener : public executor::ExecuteEventListener { 16 | public: 17 | FilesListener(bool suppressStderr = true); 18 | 19 | void onPostForkChild() override; 20 | void onPreFork() override; 21 | 22 | const static std::string DEV_NULL; 23 | const static std::string FDS_PATH; 24 | 25 | private: 26 | const bool suppressStderr_; 27 | std::vector fds_; 28 | int devnull_{-1}; 29 | }; 30 | 31 | } // namespace files 32 | } // namespace s2j 33 | -------------------------------------------------------------------------------- /src/limits/MemoryLimitListener.cc: -------------------------------------------------------------------------------- 1 | #include "MemoryLimitListener.h" 2 | 3 | #include "common/ProcFS.h" 4 | #include "common/WithErrnoCheck.h" 5 | #include "logger/Logger.h" 6 | #include "seccomp/SeccompRule.h" 7 | #include "seccomp/action/ActionAllow.h" 8 | #include "seccomp/action/ActionTrace.h" 9 | #include "seccomp/filter/LibSeccompFilter.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #ifndef MREMAP_DONTUNMAP 16 | // Due to this flag being introduced in Linux 5.7 there are many system which do 17 | // not define it 18 | #define MREMAP_DONTUNMAP 4 19 | #endif 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace s2j { 27 | namespace limits { 28 | 29 | const uint64_t MemoryLimitListener::MEMORY_LIMIT_MARGIN = 8 * 1024 * 1024; 30 | 31 | MemoryLimitListener::MemoryLimitListener(uint64_t memoryLimitKb) 32 | : memoryPeakKb_(0) 33 | , memoryLimitKb_(memoryLimitKb) 34 | , vmPeakValid_(false) 35 | , childPid_(-1) { 36 | TRACE(memoryLimitKb); 37 | 38 | // Possible memory problem here, we will return this references to *this. 39 | // User is responsible for ensuring that MemoryLimitListener last at least 40 | // as long as any reference to it's rules. 41 | using Arg = seccomp::filter::SyscallArg; 42 | for (const auto& syscall: {"mmap2", "mmap"}) { 43 | syscallRules_.emplace_back(seccomp::SeccompRule( 44 | syscall, 45 | seccomp::action::ActionTrace([this](tracer::Tracee& tracee) { 46 | TRACE(); 47 | if (!vmPeakValid_) { 48 | return tracer::TraceAction::CONTINUE; 49 | } 50 | return handleMemoryAllocation( 51 | tracee.getSyscallArgument(1) / 1024); 52 | }), 53 | Arg(0) == 0 && Arg(1) > MEMORY_LIMIT_MARGIN / 2)); 54 | } 55 | syscallRules_.emplace_back(seccomp::SeccompRule( 56 | "mremap", 57 | seccomp::action::ActionTrace([this](tracer::Tracee& tracee) { 58 | TRACE(); 59 | uint64_t old_size = tracee.getSyscallArgument(1); 60 | uint64_t new_size = tracee.getSyscallArgument(2); 61 | uint64_t flags = tracee.getSyscallArgument(3); 62 | if (!vmPeakValid_) { 63 | return tracer::TraceAction::CONTINUE; 64 | } 65 | bool doesntUnmap = 66 | (flags & MREMAP_DONTUNMAP) == MREMAP_DONTUNMAP; 67 | // Allow user to shrink its memory 68 | if (!doesntUnmap && old_size >= new_size) { 69 | return tracer::TraceAction::CONTINUE; 70 | } 71 | uint64_t newMemoryAllocated = new_size; 72 | // Do not count already allocated memory 73 | if (!doesntUnmap) 74 | newMemoryAllocated -= old_size; 75 | newMemoryAllocated /= 1024; 76 | return handleMemoryAllocation(newMemoryAllocated); 77 | }), 78 | Arg(2) > MEMORY_LIMIT_MARGIN / 2)); 79 | } 80 | tracer::TraceAction MemoryLimitListener::handleMemoryAllocation( 81 | uint64_t allocatedMemoryKb) { 82 | uint64_t memoryUsage = getMemoryUsageKb() + allocatedMemoryKb; 83 | memoryPeakKb_ = std::max(memoryPeakKb_, memoryUsage); 84 | outputBuilder_->setMemoryPeak(memoryPeakKb_); 85 | logger::debug( 86 | "Memory usage after allocation ", 87 | VAR(memoryUsage), 88 | ", ", 89 | VAR(memoryPeakKb_)); 90 | 91 | if (memoryLimitKb_ > 0 && memoryUsage > memoryLimitKb_) { 92 | outputBuilder_->setKillReason( 93 | printer::OutputBuilder::KillReason::MLE, 94 | "memory limit exceeded"); 95 | logger::debug( 96 | "Limit ", VAR(memoryLimitKb_), " exceeded, killing tracee"); 97 | return tracer::TraceAction::KILL; 98 | } 99 | return tracer::TraceAction::CONTINUE; 100 | } 101 | void MemoryLimitListener::onPostForkChild() { 102 | TRACE(); 103 | 104 | // If there is any memory limit, set it. 105 | if (memoryLimitKb_ > 0) { 106 | struct rlimit memoryLimit {}; 107 | 108 | memoryLimit.rlim_cur = memoryLimit.rlim_max = 109 | memoryLimitKb_ * 1024 + MEMORY_LIMIT_MARGIN; 110 | logger::debug("Seting address space limit ", VAR(memoryLimit.rlim_max)); 111 | withErrnoCheck( 112 | "setrlimit address space", setrlimit, RLIMIT_AS, &memoryLimit); 113 | 114 | memoryLimit.rlim_cur = memoryLimit.rlim_max = RLIM_INFINITY; 115 | logger::debug("Seting stack limit to infinity"); 116 | withErrnoCheck( 117 | "setrlimit stack", setrlimit, RLIMIT_STACK, &memoryLimit); 118 | } 119 | } 120 | 121 | void MemoryLimitListener::onPostForkParent(pid_t childPid) { 122 | TRACE(childPid); 123 | 124 | childPid_ = childPid; 125 | } 126 | 127 | tracer::TraceAction MemoryLimitListener::onPostExec( 128 | const tracer::TraceEvent& /* traceEvent */, 129 | tracer::Tracee& /* tracee */) { 130 | vmPeakValid_ = true; 131 | return tracer::TraceAction::CONTINUE; 132 | } 133 | 134 | executor::ExecuteAction MemoryLimitListener::onExecuteEvent( 135 | const executor::ExecuteEvent& /*executeEvent*/) { 136 | TRACE(); 137 | 138 | if (!vmPeakValid_) { 139 | return executor::ExecuteAction::CONTINUE; 140 | } 141 | 142 | memoryPeakKb_ = std::max(memoryPeakKb_, getMemoryPeakKb()); 143 | logger::debug("Read new memory peak ", VAR(memoryPeakKb_)); 144 | 145 | outputBuilder_->setMemoryPeak(memoryPeakKb_); 146 | if (memoryLimitKb_ > 0 && memoryPeakKb_ > memoryLimitKb_) { 147 | outputBuilder_->setKillReason( 148 | printer::OutputBuilder::KillReason::MLE, 149 | "memory limit exceeded"); 150 | logger::debug( 151 | "Limit ", VAR(memoryLimitKb_), " exceeded, killing tracee"); 152 | return executor::ExecuteAction::KILL; 153 | } 154 | 155 | return executor::ExecuteAction::CONTINUE; 156 | } 157 | 158 | uint64_t MemoryLimitListener::getMemoryPeakKb() { 159 | return procfs::readProcFS(childPid_, procfs::Field::VM_PEAK); 160 | } 161 | 162 | uint64_t MemoryLimitListener::getMemoryUsageKb() { 163 | return procfs::readProcFS(childPid_, procfs::Field::VM_SIZE); 164 | } 165 | 166 | const std::vector& MemoryLimitListener::getRules() const { 167 | return syscallRules_; 168 | } 169 | 170 | } // namespace limits 171 | } // namespace s2j 172 | -------------------------------------------------------------------------------- /src/limits/MemoryLimitListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "executor/ExecuteEventListener.h" 4 | #include "printer/OutputSource.h" 5 | #include "seccomp/policy/SyscallPolicy.h" 6 | #include "tracer/TraceEventListener.h" 7 | 8 | #include 9 | 10 | namespace s2j { 11 | namespace limits { 12 | 13 | class MemoryLimitListener 14 | : public executor::ExecuteEventListener 15 | , public tracer::TraceEventListener 16 | , public printer::OutputSource 17 | , public seccomp::policy::SyscallPolicy { 18 | public: 19 | MemoryLimitListener(uint64_t memoryLimitKb); 20 | 21 | void onPostForkChild() override; 22 | void onPostForkParent(pid_t childPid) override; 23 | tracer::TraceAction onPostExec( 24 | const tracer::TraceEvent& traceEvent, 25 | tracer::Tracee& tracee) override; 26 | executor::ExecuteAction onExecuteEvent( 27 | const executor::ExecuteEvent& executeEvent) override; 28 | 29 | const std::vector& getRules() const; 30 | 31 | private: 32 | static const uint64_t MEMORY_LIMIT_MARGIN; 33 | 34 | uint64_t getMemoryPeakKb(); 35 | uint64_t getMemoryUsageKb(); 36 | 37 | uint64_t memoryPeakKb_; 38 | uint64_t memoryLimitKb_; 39 | bool vmPeakValid_; 40 | pid_t childPid_; 41 | 42 | std::vector syscallRules_; 43 | tracer::TraceAction handleMemoryAllocation(uint64_t allocatedMemoryKb); 44 | }; 45 | 46 | } // namespace limits 47 | } // namespace s2j 48 | -------------------------------------------------------------------------------- /src/limits/OutputLimitListener.cc: -------------------------------------------------------------------------------- 1 | #include "OutputLimitListener.h" 2 | 3 | #include "common/WithErrnoCheck.h" 4 | #include "logger/Logger.h" 5 | #include "seccomp/SeccompRule.h" 6 | #include "seccomp/action/ActionAllow.h" 7 | #include "seccomp/action/ActionTrace.h" 8 | #include "seccomp/filter/LibSeccompFilter.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | namespace s2j { 19 | namespace limits { 20 | 21 | OutputLimitListener::OutputLimitListener(uint64_t outputLimitB) 22 | : outputLimitB_(outputLimitB), childPid_(-1) { 23 | TRACE(outputLimitB); 24 | } 25 | 26 | void OutputLimitListener::onPostForkChild() { 27 | TRACE(); 28 | 29 | // If there is any output limit, set it. 30 | if (outputLimitB_ > 0) { 31 | struct rlimit outputLimit { 32 | outputLimitB_, outputLimitB_ 33 | }; 34 | 35 | logger::debug("Seting limit ", VAR(outputLimit.rlim_max)); 36 | withErrnoCheck( 37 | "setrlimit file size", setrlimit, RLIMIT_FSIZE, &outputLimit); 38 | } 39 | } 40 | 41 | void OutputLimitListener::onPostForkParent(pid_t childPid) { 42 | TRACE(childPid); 43 | 44 | childPid_ = childPid; 45 | } 46 | 47 | executor::ExecuteAction OutputLimitListener::onExecuteEvent( 48 | const executor::ExecuteEvent& executeEvent) { 49 | TRACE(); 50 | 51 | if (outputLimitB_ > 0 && executeEvent.signal == SIGXFSZ) { 52 | logger::info("Tracee got SIGXFSZ, assuming output limit exceeded"); 53 | outputBuilder_->setKillReason( 54 | printer::OutputBuilder::KillReason::OLE, 55 | "output limit exceeded"); 56 | return executor::ExecuteAction::KILL; 57 | } 58 | 59 | return executor::ExecuteAction::CONTINUE; 60 | } 61 | 62 | } // namespace limits 63 | } // namespace s2j 64 | -------------------------------------------------------------------------------- /src/limits/OutputLimitListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "executor/ExecuteEventListener.h" 4 | #include "printer/OutputSource.h" 5 | #include "seccomp/policy/SyscallPolicy.h" 6 | 7 | #include 8 | 9 | namespace s2j { 10 | namespace limits { 11 | 12 | class OutputLimitListener 13 | : public executor::ExecuteEventListener 14 | , public printer::OutputSource { 15 | public: 16 | OutputLimitListener(uint64_t outputLimitB); 17 | 18 | void onPostForkChild() override; 19 | void onPostForkParent(pid_t childPid) override; 20 | executor::ExecuteAction onExecuteEvent( 21 | const executor::ExecuteEvent& executeEvent) override; 22 | 23 | private: 24 | uint64_t outputLimitB_; 25 | pid_t childPid_; 26 | }; 27 | 28 | } // namespace limits 29 | } // namespace s2j 30 | -------------------------------------------------------------------------------- /src/limits/ThreadsLimitListener.cc: -------------------------------------------------------------------------------- 1 | #include "ThreadsLimitListener.h" 2 | 3 | #include "common/WithErrnoCheck.h" 4 | #include "logger/Logger.h" 5 | #include "seccomp/SeccompRule.h" 6 | #include "seccomp/action/ActionAllow.h" 7 | #include "seccomp/action/ActionKill.h" 8 | #include "seccomp/filter/LibSeccompFilter.h" 9 | 10 | #include 11 | 12 | namespace s2j { 13 | namespace limits { 14 | 15 | ThreadsLimitListener::ThreadsLimitListener(int32_t threadsLimit) 16 | : threadsLimit_{threadsLimit} { 17 | TRACE(threadsLimit); 18 | 19 | syscallRules_.emplace_back( 20 | seccomp::SeccompRule("fork", seccomp::action::ActionKill{})); 21 | syscallRules_.emplace_back( 22 | seccomp::SeccompRule("vfork", seccomp::action::ActionKill{})); 23 | 24 | if (threadsLimit_ < 0) { 25 | // Disable threads support 26 | syscallRules_.emplace_back( 27 | seccomp::SeccompRule("clone", seccomp::action::ActionKill{})); 28 | } 29 | else { 30 | // Enable threads support 31 | using Arg = seccomp::filter::SyscallArg; 32 | syscallRules_.emplace_back(seccomp::SeccompRule( 33 | "clone", 34 | seccomp::action::ActionAllow{}, 35 | (Arg(2) & CLONE_VM) == CLONE_VM)); 36 | syscallRules_.emplace_back(seccomp::SeccompRule( 37 | "clone", 38 | seccomp::action::ActionKill(), 39 | (Arg(2) & CLONE_VM) == 0)); 40 | 41 | // And various thread related 42 | syscallRules_.emplace_back(seccomp::SeccompRule( 43 | // TODO: allow sleep up to time limit 44 | "nanosleep", 45 | seccomp::action::ActionAllow{})); 46 | } 47 | } 48 | 49 | std::tuple 50 | ThreadsLimitListener::onPostClone( 51 | const tracer::TraceEvent& traceEvent, 52 | tracer::Tracee& tracee, 53 | tracer::Tracee& traceeChild) { 54 | TRACE(tracee.getPid(), traceeChild.getPid()); 55 | if (threadsLimit_ < 0) { 56 | outputBuilder_->setKillReason( 57 | printer::OutputBuilder::KillReason::RV, 58 | "Threads are not allowed"); 59 | return {tracer::TraceAction::KILL, tracer::TraceAction::KILL}; 60 | } 61 | 62 | threadsPids_.insert(traceeChild.getPid()); 63 | logger::debug( 64 | "Thread ", 65 | traceeChild.getPid(), 66 | " started, new thread count ", 67 | threadsPids_.size()); 68 | if (threadsPids_.size() > static_cast(threadsLimit_)) { 69 | outputBuilder_->setKillReason( 70 | printer::OutputBuilder::KillReason::RV, 71 | "threads limit exceeded"); 72 | logger::info( 73 | "Threads limit ", threadsLimit_, " exceeded, killing tracee"); 74 | return {tracer::TraceAction::KILL, tracer::TraceAction::KILL}; 75 | } 76 | 77 | return {tracer::TraceAction::CONTINUE, tracer::TraceAction::CONTINUE}; 78 | } 79 | 80 | executor::ExecuteAction ThreadsLimitListener::onExecuteEvent( 81 | const executor::ExecuteEvent& executeEvent) { 82 | TRACE(executeEvent.pid); 83 | if (threadsLimit_ < 0) 84 | return executor::ExecuteAction::CONTINUE; 85 | 86 | if (executeEvent.exited || executeEvent.killed) { 87 | threadsPids_.erase(executeEvent.pid); 88 | logger::debug( 89 | "Thread ", 90 | executeEvent.pid, 91 | " exited, new threads count ", 92 | threadsPids_.size()); 93 | } 94 | return executor::ExecuteAction::CONTINUE; 95 | } 96 | 97 | } // namespace limits 98 | } // namespace s2j 99 | -------------------------------------------------------------------------------- /src/limits/ThreadsLimitListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "executor/ExecuteEventListener.h" 4 | #include "printer/OutputSource.h" 5 | #include "seccomp/policy/SyscallPolicy.h" 6 | #include "tracer/TraceEventListener.h" 7 | 8 | #include 9 | 10 | namespace s2j { 11 | namespace limits { 12 | 13 | class ThreadsLimitListener 14 | : public executor::ExecuteEventListener 15 | , public tracer::TraceEventListener 16 | , public printer::OutputSource 17 | , public seccomp::policy::SyscallPolicy { 18 | public: 19 | ThreadsLimitListener(int32_t threadsLimit); 20 | 21 | std::tuple onPostClone( 22 | const tracer::TraceEvent& traceEvent, 23 | tracer::Tracee& tracee, 24 | tracer::Tracee& traceeChild) override; 25 | 26 | executor::ExecuteAction onExecuteEvent( 27 | const executor::ExecuteEvent& executeEvent) override; 28 | 29 | const std::vector& getRules() const override { 30 | return syscallRules_; 31 | } 32 | 33 | private: 34 | std::unordered_set threadsPids_; 35 | std::vector syscallRules_; 36 | const int32_t threadsLimit_; 37 | }; 38 | 39 | } // namespace limits 40 | } // namespace s2j 41 | -------------------------------------------------------------------------------- /src/limits/TimeLimitListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "executor/ExecuteEventListener.h" 4 | #include "printer/OutputSource.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace s2j { 10 | namespace limits { 11 | 12 | class TimeLimitListener 13 | : public executor::ExecuteEventListener 14 | , public printer::OutputSource { 15 | public: 16 | TimeLimitListener( 17 | uint64_t rTimelimitUs, 18 | uint64_t uTimelimitUs, 19 | uint64_t sTimelimitUs, 20 | uint64_t usTimelimitUs); 21 | ~TimeLimitListener(); 22 | 23 | void onPostForkParent(pid_t childPid) override; 24 | executor::ExecuteAction onSigalrmSignal() override; 25 | void onPostExecute() override; 26 | 27 | private: 28 | struct ProcessTimeUsage { 29 | uint64_t uTimeUs; // user time in [us] 30 | uint64_t sTimeUs; // system time in [us] 31 | }; 32 | 33 | struct TimeUsage { 34 | uint64_t realTimeUs; 35 | ProcessTimeUsage processTimeUs; 36 | }; 37 | 38 | static const uint64_t TIMER_TICKING_INTERVAL_US; 39 | static const long CLOCK_TICKS_PER_SECOND; 40 | 41 | executor::ExecuteAction verifyTimeUsage(std::unique_ptr); 42 | uint64_t getRealTimeUsage() const; 43 | ProcessTimeUsage getProcessTimeUsage() const; 44 | std::unique_ptr getTimeUsage() const; 45 | 46 | uint64_t rTimelimitUs_; // real time limit in [us] 47 | uint64_t uTimelimitUs_; // user time limit in [us] 48 | uint64_t sTimelimitUs_; // system time limit in [us] 49 | uint64_t usTimelimitUs_; // user+system time limit in [us] 50 | pid_t childPid_{}; 51 | 52 | std::chrono::steady_clock::time_point startRealTime_; 53 | bool isTimerCreated_; 54 | timer_t timerId_{}; 55 | }; 56 | 57 | } // namespace limits 58 | } // namespace s2j 59 | -------------------------------------------------------------------------------- /src/logger/FDLogger.cc: -------------------------------------------------------------------------------- 1 | #include "FDLogger.h" 2 | 3 | namespace s2j { 4 | namespace logger { 5 | 6 | FDLogger::FDLogger(int fd, bool close) : fd_(fd), close_(close) { 7 | if (fd_ < 0) { 8 | throw SystemException("Invalid logger initialization"); 9 | } 10 | } 11 | 12 | FDLogger::~FDLogger() { 13 | if (close_ && fd_ >= 0) { 14 | close(fd_); 15 | fd_ = -1; 16 | } 17 | } 18 | 19 | bool FDLogger::isLoggerFD(int fd) const noexcept { 20 | return fd == fd_; 21 | } 22 | 23 | void FDLogger::write(const std::string& string) noexcept { 24 | for (size_t written = 0; written < string.size();) { 25 | ssize_t res = 26 | ::write(fd_, string.c_str() + written, string.size() - written); 27 | if (res < 0) { 28 | if (errno != EAGAIN && errno != EINTR) { 29 | break; 30 | } 31 | } 32 | else { 33 | written += res; 34 | } 35 | } 36 | } 37 | 38 | } // namespace logger 39 | } // namespace s2j 40 | -------------------------------------------------------------------------------- /src/logger/FDLogger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Logger.h" 4 | 5 | #include "common/FD.h" 6 | 7 | namespace s2j { 8 | namespace logger { 9 | 10 | class FDLogger : public Logger { 11 | public: 12 | FDLogger(int fd, bool close = false); 13 | ~FDLogger(); 14 | 15 | bool isLoggerFD(int fd) const noexcept override; 16 | 17 | protected: 18 | void write(const std::string& string) noexcept; 19 | 20 | private: 21 | int fd_; 22 | bool close_; 23 | }; 24 | 25 | } // namespace logger 26 | } // namespace s2j 27 | -------------------------------------------------------------------------------- /src/logger/FileLogger.cc: -------------------------------------------------------------------------------- 1 | #include "FileLogger.h" 2 | 3 | #include "common/Exception.h" 4 | 5 | #include 6 | 7 | namespace s2j { 8 | namespace logger { 9 | 10 | FileLogger::FileLogger(const std::string& fileName) 11 | : FDLogger( 12 | open(fileName.c_str(), 13 | O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 14 | S_IRUSR | S_IWUSR | S_IRGRP), 15 | true) {} 16 | 17 | } // namespace logger 18 | } // namespace s2j 19 | -------------------------------------------------------------------------------- /src/logger/FileLogger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FDLogger.h" 4 | 5 | #include 6 | 7 | namespace s2j { 8 | namespace logger { 9 | 10 | class FileLogger : public FDLogger { 11 | public: 12 | FileLogger(const std::string& fileName = "/dev/null"); 13 | }; 14 | 15 | } // namespace logger 16 | } // namespace s2j 17 | -------------------------------------------------------------------------------- /src/logger/Logger.cc: -------------------------------------------------------------------------------- 1 | #include "Logger.h" 2 | 3 | #include "VoidLogger.h" 4 | 5 | namespace s2j { 6 | namespace logger { 7 | 8 | std::shared_ptr Logger::logger_; 9 | 10 | void Logger::setLogger(std::shared_ptr logger) noexcept { 11 | if (logger != nullptr) { 12 | logger_ = std::move(logger); 13 | } 14 | } 15 | 16 | std::shared_ptr Logger::getLogger() noexcept { 17 | if (logger_ == nullptr) { 18 | logger_ = std::make_shared(); 19 | } 20 | return logger_; 21 | } 22 | 23 | } // namespace logger 24 | } // namespace s2j 25 | -------------------------------------------------------------------------------- /src/logger/Logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #pragma GCC system_header 3 | 4 | #include "executor/ExecuteEventListener.h" 5 | #include "tracer/TraceEventListener.h" 6 | 7 | #include "common/Exception.h" 8 | #include "common/Preprocessor.h" 9 | #include "common/TypeList.h" 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #define VAR(v) #v, "=", v 21 | #define TRACE(...) \ 22 | s2j::logger::Logger::Trace trace_( \ 23 | __PRETTY_FUNCTION__ MAP(_TRACE_PARAM, __VA_ARGS__)); 24 | #define _TRACE_PARAM(p) , ", ", VAR(p) 25 | 26 | namespace s2j { 27 | namespace logger { 28 | 29 | #ifdef NTRACE 30 | const constexpr bool enableTrace = false; 31 | #else 32 | const constexpr bool enableTrace = true; 33 | #endif 34 | 35 | template 36 | void trace(Args&&... args) noexcept; 37 | 38 | template 39 | void debug(Args&&... args) noexcept; 40 | 41 | template 42 | void error(Args&&... args) noexcept; 43 | 44 | inline bool isLoggerFD(int fd) noexcept; 45 | 46 | class Logger { 47 | friend class LogSource; 48 | 49 | public: 50 | class Trace { 51 | public: 52 | template 53 | Trace(const char* functionName, Args&&... args) 54 | : functionName_(functionName) { 55 | trace(functionName_, args...); 56 | } 57 | 58 | ~Trace() { 59 | try { 60 | trace(functionName_, " -"); 61 | } 62 | catch (...) { 63 | } 64 | } 65 | 66 | private: 67 | const char* const functionName_; 68 | }; 69 | 70 | virtual ~Logger() = default; 71 | 72 | template 73 | void log(const std::string& level, Args&&... args) noexcept; 74 | 75 | virtual bool isLoggerFD(int fd) const noexcept = 0; 76 | 77 | /** 78 | * Singleton pattern for logger. 79 | */ 80 | static void setLogger(std::shared_ptr logger) noexcept; 81 | static std::shared_ptr getLogger() noexcept; 82 | 83 | protected: 84 | template 85 | void log( 86 | std::ostream& stream, 87 | const std::string& delimeter, 88 | Arg&& arg, 89 | Args&&... args) noexcept; 90 | 91 | void log(std::ostream& stream, const std::string& delimeter) noexcept {} 92 | 93 | virtual void write(const std::string& string) noexcept = 0; 94 | 95 | private: 96 | static std::shared_ptr logger_; 97 | }; 98 | 99 | template 100 | void trace(Args&&... args) noexcept { 101 | if (enableTrace) { 102 | Logger::getLogger()->log("TRACE", args...); 103 | } 104 | } 105 | 106 | template 107 | void debug(Args&&... args) noexcept { 108 | Logger::getLogger()->log("DEBUG", args...); 109 | } 110 | 111 | template 112 | void info(Args&&... args) noexcept { 113 | Logger::getLogger()->log("INFO", args...); 114 | } 115 | 116 | template 117 | void warn(Args&&... args) noexcept { 118 | Logger::getLogger()->log("WARN", args...); 119 | } 120 | 121 | template 122 | void error(Args&&... args) noexcept { 123 | Logger::getLogger()->log("ERROR", args...); 124 | } 125 | 126 | inline bool isLoggerFD(int fd) noexcept { 127 | return Logger::getLogger()->isLoggerFD(fd); 128 | } 129 | 130 | template 131 | void Logger::log(const std::string& level, Args&&... args) noexcept { 132 | std::stringstream stream; 133 | 134 | struct timeval tv; 135 | gettimeofday(&tv, nullptr); 136 | 137 | char strTimeBuff[64]; 138 | if (std::strftime( 139 | strTimeBuff, 140 | sizeof(strTimeBuff), 141 | "%F %T", 142 | std::localtime(&tv.tv_sec)) == 0) 143 | ::strcpy(strTimeBuff, "0000-00-00 00:00:00.000000"); 144 | 145 | stream << strTimeBuff << "." << std::setw(6) << std::setfill('0') 146 | << std::right << tv.tv_usec << "\t" << level << "\t"; 147 | log(stream, "", args...); 148 | stream << std::endl; 149 | 150 | write(stream.str()); 151 | } 152 | 153 | template 154 | void Logger::log( 155 | std::ostream& stream, 156 | const std::string& delimeter, 157 | Arg&& arg, 158 | Args&&... args) noexcept { 159 | stream << arg << delimeter; 160 | log(stream, delimeter, args...); 161 | } 162 | 163 | } // namespace logger 164 | } // namespace s2j 165 | -------------------------------------------------------------------------------- /src/logger/LoggerListener.cc: -------------------------------------------------------------------------------- 1 | #include "LoggerListener.h" 2 | 3 | #include "Logger.h" 4 | 5 | namespace s2j { 6 | namespace logger { 7 | 8 | void LoggerListener::onPreFork() { 9 | logger::debug("Execution stage onPreFork"); 10 | } 11 | 12 | void LoggerListener::onPostForkChild() { 13 | logger::debug("Execution stage onPostForkChild"); 14 | } 15 | 16 | void LoggerListener::onPostForkParent(pid_t childPid) { 17 | logger::debug("Execution stage onPostForkParent, ", VAR(childPid)); 18 | } 19 | 20 | executor::ExecuteAction LoggerListener::onExecuteEvent( 21 | const executor::ExecuteEvent& executeEvent) { 22 | logger::debug( 23 | "Execution stage onExecuteEvent, ", 24 | "pid=", 25 | executeEvent.pid, 26 | ", " 27 | "exitStatus=", 28 | executeEvent.exitStatus, 29 | ", " 30 | "signal=", 31 | executeEvent.signal, 32 | ", " 33 | "exited=", 34 | executeEvent.exited, 35 | ", " 36 | "killed=", 37 | executeEvent.killed, 38 | ", " 39 | "stopped=", 40 | executeEvent.stopped, 41 | ", " 42 | "trapped=", 43 | executeEvent.trapped); 44 | return executor::ExecuteAction::CONTINUE; 45 | } 46 | 47 | void LoggerListener::onPostExecute() { 48 | logger::debug("Execution stage onPostExecute"); 49 | } 50 | 51 | tracer::TraceAction LoggerListener::onPostExec( 52 | const tracer::TraceEvent& /* traceEvent */, 53 | tracer::Tracee& /* tracee */) { 54 | logger::debug("Execution stage onPostExec"); 55 | return tracer::TraceAction::CONTINUE; 56 | } 57 | 58 | std::tuple 59 | LoggerListener::onPostClone( 60 | const tracer::TraceEvent& /* traceEvent */, 61 | tracer::Tracee& tracee, 62 | tracer::Tracee& traceeChild) { 63 | logger::debug( 64 | "Execution stage onPostClone, traceePid=", 65 | tracee.getPid(), 66 | ", traceeChildPid=", 67 | traceeChild.getPid()); 68 | return {tracer::TraceAction::CONTINUE, tracer::TraceAction::CONTINUE}; 69 | } 70 | 71 | tracer::TraceAction LoggerListener::onTraceEvent( 72 | const tracer::TraceEvent& /*traceEvent*/, 73 | tracer::Tracee& tracee) { 74 | logger::debug("Execution stage onTraceEvent, isAlive=", tracee.isAlive()); 75 | return tracer::TraceAction::CONTINUE; 76 | } 77 | 78 | } // namespace logger 79 | } // namespace s2j 80 | -------------------------------------------------------------------------------- /src/logger/LoggerListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "executor/ExecuteEventListener.h" 4 | #include "tracer/TraceEventListener.h" 5 | 6 | #include 7 | 8 | namespace s2j { 9 | namespace logger { 10 | 11 | class LoggerListener 12 | : public executor::ExecuteEventListener 13 | , public tracer::TraceEventListener { 14 | public: 15 | void onPreFork() override; 16 | void onPostForkChild() override; 17 | void onPostForkParent(pid_t childPid) override; 18 | executor::ExecuteAction onExecuteEvent( 19 | const executor::ExecuteEvent& executeEvent) override; 20 | void onPostExecute() override; 21 | 22 | tracer::TraceAction onPostExec( 23 | const tracer::TraceEvent& traceEvent, 24 | tracer::Tracee& tracee) override; 25 | std::tuple onPostClone( 26 | const tracer::TraceEvent& traceEvent, 27 | tracer::Tracee& tracee, 28 | tracer::Tracee& traceeChild) override; 29 | tracer::TraceAction onTraceEvent( 30 | const tracer::TraceEvent& traceEvent, 31 | tracer::Tracee& tracee) override; 32 | }; 33 | 34 | } // namespace logger 35 | } // namespace s2j 36 | -------------------------------------------------------------------------------- /src/logger/VoidLogger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Logger.h" 4 | 5 | namespace s2j { 6 | namespace logger { 7 | 8 | class VoidLogger : public Logger { 9 | public: 10 | bool isLoggerFD(int /* fd */) const noexcept override { 11 | return false; 12 | } 13 | 14 | protected: 15 | void write(const std::string&) noexcept override {} 16 | }; 17 | 18 | } // namespace logger 19 | } // namespace s2j 20 | -------------------------------------------------------------------------------- /src/ns/IPCNamespaceListener.cc: -------------------------------------------------------------------------------- 1 | #include "IPCNamespaceListener.h" 2 | 3 | #include "common/Exception.h" 4 | #include "common/WithErrnoCheck.h" 5 | 6 | #include 7 | 8 | namespace s2j { 9 | namespace ns { 10 | 11 | const Feature IPCNamespaceListener::feature = Feature::IPC_NAMESPACE; 12 | 13 | void IPCNamespaceListener::onPostForkChild() { 14 | TRACE(); 15 | 16 | withErrnoCheck("unshare newipc", unshare, CLONE_NEWIPC); 17 | } 18 | 19 | } // namespace ns 20 | } // namespace s2j 21 | -------------------------------------------------------------------------------- /src/ns/IPCNamespaceListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/Feature.h" 4 | #include "executor/ExecuteEventListener.h" 5 | #include "printer/OutputSource.h" 6 | 7 | namespace s2j { 8 | namespace ns { 9 | 10 | class IPCNamespaceListener : public executor::ExecuteEventListener { 11 | public: 12 | void onPostForkChild() override; 13 | 14 | const static Feature feature; 15 | }; 16 | 17 | } // namespace ns 18 | } // namespace s2j 19 | -------------------------------------------------------------------------------- /src/ns/MountEventListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace s2j { 6 | namespace ns { 7 | 8 | class MountEventListener { 9 | public: 10 | virtual void onProgramNameChange(const std::string& newProgramName){}; 11 | }; 12 | 13 | } // namespace ns 14 | } // namespace s2j 15 | -------------------------------------------------------------------------------- /src/ns/MountNamespaceListener.cc: -------------------------------------------------------------------------------- 1 | #include "MountNamespaceListener.h" 2 | 3 | #include "common/Exception.h" 4 | #include "common/FD.h" 5 | #include "common/Utils.h" 6 | #include "common/WithErrnoCheck.h" 7 | #include "logger/Logger.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace { 19 | 20 | void pivot_root(const char* new_root, const char* put_old) { 21 | s2j::withErrnoCheck( 22 | "pivot_root", syscall, SYS_pivot_root, new_root, put_old); 23 | } 24 | 25 | } // namespace 26 | 27 | namespace s2j { 28 | namespace ns { 29 | 30 | const std::string MountNamespaceListener::newExecutablePath = "/exe"; 31 | 32 | const Feature MountNamespaceListener::feature = Feature::MOUNT_NAMESPACE; 33 | 34 | MountNamespaceListener::MountNamespaceListener( 35 | const Settings& settings, 36 | std::string executablePath, 37 | const bool mountProc) 38 | : executablePath_(std::move(executablePath)) 39 | , bindMounts_(settings.bindMounts) 40 | , mountProc_(mountProc) 41 | , bindExecutable_(settings.bindExecutable) { 42 | if (bindExecutable_) { 43 | bindMounts_.emplace_back(BindMount( 44 | executablePath_, newExecutablePath, BindMount::Mode::RO)); 45 | } 46 | 47 | if (bindMounts_.empty() || bindMounts_.front().targetPath != "/") { 48 | throw Exception("Invalid configration, first bind mount must be root " 49 | "bind mount if namespace listener is used"); 50 | } 51 | newRoot_ = std::move(bindMounts_.front()); 52 | bindMounts_.erase(bindMounts_.begin()); 53 | } 54 | 55 | void MountNamespaceListener::onPostForkChild() { 56 | TRACE(); 57 | 58 | withErrnoCheck("unshare mount namespace", unshare, CLONE_NEWNS); 59 | 60 | // make-private on everything 61 | withErrnoCheck( 62 | "mount make-private", 63 | mount, 64 | "none", 65 | "/", 66 | nullptr, 67 | MS_PRIVATE | MS_REC, 68 | nullptr); 69 | 70 | const std::string& newRootPath = newRoot_.sourcePath; 71 | // bind-mount newRoot 72 | withErrnoCheck( 73 | "mount bind " + newRootPath, 74 | mount, 75 | newRootPath.c_str(), 76 | newRootPath.c_str(), 77 | "", 78 | MS_BIND, 79 | nullptr); 80 | for (auto& bindMount: bindMounts_) { 81 | bindMount.mount(newRootPath); 82 | } 83 | 84 | // cd to newRoot 85 | withErrnoCheck("chdir " + newRootPath, chdir, newRootPath.c_str()); 86 | 87 | // move to new root, and put old root on top 88 | pivot_root(".", "."); 89 | 90 | // chroot because pivot_root manpage says so 91 | withErrnoCheck("chroot", chroot, "."); 92 | 93 | if (bindExecutable_) { 94 | for (const auto& listener: 95 | EventProvider::eventListeners_) { 96 | listener->onProgramNameChange(newExecutablePath); 97 | } 98 | } 99 | 100 | if (mountProc_) { 101 | // mount new /proc 102 | // TODO: add a flag to disable this 103 | if (mkdir("proc", 0755) < 0) { 104 | if (errno != EEXIST) { 105 | throw SystemException("mkdir proc"); 106 | } 107 | } 108 | withErrnoCheck( 109 | "mount proc", 110 | mount, 111 | "proc", 112 | "proc", 113 | "proc", 114 | MS_NOSUID | MS_NODEV | MS_NOEXEC, 115 | nullptr); 116 | } 117 | 118 | // now detach the old root 119 | // NOTE: this needs to be done AFTER new proc is already mounted, unless we 120 | // don't want proc at all 121 | withErrnoCheck("umount detach old root", umount2, "/", MNT_DETACH); 122 | withErrnoCheck( 123 | "remount root rdolny", 124 | mount, 125 | "none", 126 | "/", 127 | nullptr, 128 | MS_REMOUNT | MS_BIND | newRoot_.flags(), 129 | nullptr); 130 | } 131 | 132 | void MountNamespaceListener::onPostExecute() { 133 | TRACE(); 134 | } 135 | 136 | uint32_t MountNamespaceListener::BindMount::flags() const { 137 | uint32_t flags = MS_NOSUID; 138 | if (mode == Mode::RO) { 139 | flags |= MS_RDONLY; 140 | } 141 | if (!dev) { 142 | flags |= MS_NODEV; 143 | } 144 | return flags; 145 | } 146 | 147 | void MountNamespaceListener::BindMount::mount(const std::string& root) { 148 | TRACE(root); 149 | 150 | // NOTE: On bind mount, any flags other than MS_BIND and MS_REC are ignored 151 | // by the kernel 152 | withErrnoCheck( 153 | "bind mount " + sourcePath + " -> " + targetPath, 154 | ::mount, 155 | sourcePath.c_str(), 156 | (root + "/" + targetPath).c_str(), 157 | "", 158 | MS_BIND, 159 | nullptr); 160 | // remount to apply flags 161 | withErrnoCheck( 162 | "bind remount " + targetPath, 163 | ::mount, 164 | "none", 165 | (root + "/" + targetPath).c_str(), 166 | nullptr, 167 | MS_REMOUNT | MS_BIND | flags(), 168 | nullptr); 169 | } 170 | 171 | void MountNamespaceListener::BindMount::umount(const std::string& root) { 172 | TRACE(root); 173 | 174 | withErrnoCheck( 175 | "ummount bind mount" + sourcePath, 176 | umount2, 177 | (root + "/" + targetPath).c_str(), 178 | MNT_DETACH); 179 | } 180 | 181 | } // namespace ns 182 | } // namespace s2j 183 | -------------------------------------------------------------------------------- /src/ns/MountNamespaceListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "MountEventListener.h" 4 | 5 | #include "common/EventProvider.h" 6 | #include "common/Feature.h" 7 | #include "executor/ExecuteEventListener.h" 8 | #include "printer/OutputSource.h" 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | namespace s2j { 16 | namespace ns { 17 | 18 | /** 19 | * Makes the child run in a separate PID namespace. 20 | * Requires CAP_SYS_ADMIN. 21 | * 22 | * Note: it'd be way more useful if the child 23 | * didn't have access to parent's /proc 24 | */ 25 | class MountNamespaceListener 26 | : public executor::ExecuteEventListener 27 | , public EventProvider { 28 | public: 29 | struct BindMount { 30 | enum class Mode { RO, RW }; 31 | 32 | BindMount() {} 33 | 34 | BindMount( 35 | const std::string& sourcePath, 36 | const std::string& targetPath, 37 | Mode mode) 38 | : sourcePath(sourcePath), targetPath(targetPath), mode(mode) {} 39 | 40 | uint32_t flags() const; 41 | void mount(const std::string& root); 42 | void umount(const std::string& root); 43 | 44 | std::string sourcePath, targetPath; 45 | Mode mode; 46 | bool dev = false; 47 | }; 48 | 49 | struct Settings { 50 | std::vector bindMounts; 51 | bool bindExecutable; 52 | }; 53 | 54 | MountNamespaceListener( 55 | const Settings& settings, 56 | std::string executablePath, 57 | bool mountProc); 58 | 59 | void onPostForkChild() override; 60 | void onPostExecute() override; 61 | 62 | static const std::string newExecutablePath; 63 | static const Feature feature; 64 | 65 | private: 66 | BindMount newRoot_; 67 | std::string executablePath_; 68 | std::vector bindMounts_; 69 | bool mountProc_; 70 | bool bindExecutable_; 71 | }; 72 | 73 | } // namespace ns 74 | } // namespace s2j 75 | -------------------------------------------------------------------------------- /src/ns/NetNamespaceListener.cc: -------------------------------------------------------------------------------- 1 | #include "NetNamespaceListener.h" 2 | 3 | #include "common/Exception.h" 4 | #include "common/WithErrnoCheck.h" 5 | 6 | #include 7 | 8 | namespace s2j { 9 | namespace ns { 10 | 11 | const Feature NetNamespaceListener::feature = Feature::NET_NAMESPACE; 12 | 13 | void NetNamespaceListener::onPostForkChild() { 14 | TRACE(); 15 | 16 | withErrnoCheck("unshare newnet", unshare, CLONE_NEWNET); 17 | } 18 | 19 | } // namespace ns 20 | } // namespace s2j 21 | -------------------------------------------------------------------------------- /src/ns/NetNamespaceListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/Feature.h" 4 | #include "executor/ExecuteEventListener.h" 5 | #include "printer/OutputSource.h" 6 | 7 | namespace s2j { 8 | namespace ns { 9 | 10 | class NetNamespaceListener : public executor::ExecuteEventListener { 11 | public: 12 | void onPostForkChild() override; 13 | 14 | const static Feature feature; 15 | }; 16 | 17 | } // namespace ns 18 | } // namespace s2j 19 | -------------------------------------------------------------------------------- /src/ns/PIDNamespaceListener.cc: -------------------------------------------------------------------------------- 1 | #include "PIDNamespaceListener.h" 2 | 3 | #include "common/Exception.h" 4 | #include "common/WithErrnoCheck.h" 5 | 6 | #include 7 | 8 | namespace s2j { 9 | namespace ns { 10 | 11 | const Feature PIDNamespaceListener::feature = Feature::PID_NAMESPACE; 12 | 13 | void PIDNamespaceListener::onPreFork() { 14 | TRACE(); 15 | 16 | withErrnoCheck("unshare newpid", unshare, CLONE_NEWPID); 17 | } 18 | 19 | } // namespace ns 20 | } // namespace s2j 21 | -------------------------------------------------------------------------------- /src/ns/PIDNamespaceListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/Feature.h" 4 | #include "executor/ExecuteEventListener.h" 5 | #include "printer/OutputSource.h" 6 | 7 | namespace s2j { 8 | namespace ns { 9 | 10 | /** 11 | * Makes the child run in a separate PID namespace. 12 | * Requires CAP_SYS_ADMIN. 13 | */ 14 | class PIDNamespaceListener : public executor::ExecuteEventListener { 15 | public: 16 | void onPreFork() override; 17 | 18 | const static Feature feature; 19 | }; 20 | 21 | } // namespace ns 22 | } // namespace s2j 23 | -------------------------------------------------------------------------------- /src/ns/UTSNamespaceListener.cc: -------------------------------------------------------------------------------- 1 | #include "UTSNamespaceListener.h" 2 | 3 | #include "common/Exception.h" 4 | #include "common/WithErrnoCheck.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace s2j { 10 | namespace ns { 11 | 12 | const Feature UTSNamespaceListener::feature = Feature::UTS_NAMESPACE; 13 | 14 | void UTSNamespaceListener::onPostForkChild() { 15 | TRACE(); 16 | 17 | withErrnoCheck("unshare newuts", unshare, CLONE_NEWUTS); 18 | 19 | std::string hostname = "sio2jail"; 20 | withErrnoCheck( 21 | "set hostname", sethostname, hostname.c_str(), hostname.length()); 22 | withErrnoCheck( 23 | "set domainname", 24 | setdomainname, 25 | hostname.c_str(), 26 | hostname.length()); 27 | } 28 | 29 | } // namespace ns 30 | } // namespace s2j 31 | -------------------------------------------------------------------------------- /src/ns/UTSNamespaceListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/Feature.h" 4 | #include "executor/ExecuteEventListener.h" 5 | #include "printer/OutputSource.h" 6 | 7 | namespace s2j { 8 | namespace ns { 9 | 10 | class UTSNamespaceListener : public executor::ExecuteEventListener { 11 | public: 12 | void onPostForkChild() override; 13 | 14 | const static Feature feature; 15 | }; 16 | 17 | } // namespace ns 18 | } // namespace s2j 19 | -------------------------------------------------------------------------------- /src/ns/UserNamespaceListener.cc: -------------------------------------------------------------------------------- 1 | #include "UserNamespaceListener.h" 2 | 3 | #include "common/Exception.h" 4 | #include "common/FD.h" 5 | #include "common/WithErrnoCheck.h" 6 | #include "logger/Logger.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace s2j { 15 | namespace ns { 16 | 17 | const Feature UserNamespaceListener::feature = Feature::USER_NAMESPACE; 18 | 19 | UserNamespaceListener::UserNamespaceListener() 20 | : UserNamespaceListener(-1, -1) {} 21 | 22 | UserNamespaceListener::UserNamespaceListener( 23 | uid_t rootOutsideUid, 24 | gid_t rootOutsideGid) 25 | : UserNamespaceListener(rootOutsideUid, rootOutsideGid, -1, -1) {} 26 | 27 | UserNamespaceListener::UserNamespaceListener( 28 | uid_t rootOutsideUid, 29 | gid_t rootOutsideGid, 30 | uid_t childOutsideUid, 31 | gid_t childOutsideGid) 32 | : rootOutsideUid_(rootOutsideUid) 33 | , rootOutsideGid_(rootOutsideGid) 34 | , childOutsideUid_(childOutsideUid) 35 | , childOutsideGid_(childOutsideGid) {} 36 | 37 | void UserNamespaceListener::onPreFork() { 38 | TRACE(); 39 | 40 | uid_t uid = rootOutsideUid_; 41 | gid_t gid = rootOutsideGid_; 42 | 43 | // watch out, they're unsigned ;) 44 | if (uid == static_cast(-1)) { 45 | uid = getuid(); 46 | } 47 | if (gid == static_cast(-1)) { 48 | gid = getgid(); 49 | } 50 | 51 | withErrnoCheck("unshare newuser", unshare, CLONE_NEWUSER); 52 | 53 | writeUidGidMap("uid_map", uid, childOutsideUid_); 54 | writeSetGroups(); 55 | writeUidGidMap("gid_map", gid, childOutsideGid_); 56 | } 57 | 58 | void UserNamespaceListener::writeSetGroups() { 59 | TRACE(); 60 | 61 | FD::open("/proc/self/setgroups", O_WRONLY | O_CLOEXEC) << "deny"; 62 | } 63 | 64 | void UserNamespaceListener::writeUidGidMap( 65 | const std::string& file, 66 | uid_t rootUid, 67 | uid_t childUid) { 68 | TRACE(file, rootUid, childUid); 69 | 70 | std::stringstream map; 71 | map << "0 " << rootUid << " 1\n"; 72 | // watch out, this is an unsigned -1 73 | if (childUid != static_cast(-1)) { 74 | map << "1 " << childUid << " 1\n"; 75 | } 76 | FD::open("/proc/self/" + file, O_WRONLY | O_CLOEXEC) << map.str(); 77 | } 78 | 79 | } // namespace ns 80 | } // namespace s2j 81 | -------------------------------------------------------------------------------- /src/ns/UserNamespaceListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/Feature.h" 4 | #include "executor/ExecuteEventListener.h" 5 | #include "printer/OutputSource.h" 6 | 7 | #include 8 | 9 | namespace s2j { 10 | namespace ns { 11 | 12 | class UserNamespaceListener : public executor::ExecuteEventListener { 13 | public: 14 | UserNamespaceListener(); 15 | UserNamespaceListener(uid_t rootOutsideUid, gid_t rootOutsideGid); 16 | UserNamespaceListener( 17 | uid_t rootOutsideUid, 18 | gid_t rootOutsideGid, 19 | uid_t childOutsideUid, 20 | gid_t childOutsideGid); 21 | 22 | void onPreFork() override; 23 | 24 | const static Feature feature; 25 | 26 | private: 27 | void writeSetGroups(); 28 | void writeUidGidMap(const std::string& file, uid_t rootUid, uid_t childUid); 29 | 30 | const uid_t rootOutsideUid_; 31 | const gid_t rootOutsideGid_; 32 | const uid_t childOutsideUid_; 33 | const gid_t childOutsideGid_; 34 | }; 35 | 36 | } // namespace ns 37 | } // namespace s2j 38 | -------------------------------------------------------------------------------- /src/perf/PerfListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/Feature.h" 4 | #include "executor/ExecuteEventListener.h" 5 | #include "printer/OutputSource.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace s2j { 11 | namespace perf { 12 | 13 | class PerfListener 14 | : public executor::ExecuteEventListener 15 | , public printer::OutputSource { 16 | public: 17 | PerfListener(uint64_t instructionCountLimit, uint64_t samplingFactor); 18 | ~PerfListener(); 19 | 20 | void onPreFork() override; 21 | void onPostForkParent(pid_t childPid) override; 22 | void onPostForkChild() override; 23 | void onPostExecute() override; 24 | executor::ExecuteAction onSigioSignal() override; 25 | 26 | const static Feature feature; 27 | 28 | private: 29 | uint64_t getInstructionsUsed(); 30 | 31 | const uint64_t instructionCountLimit_; 32 | const uint64_t samplingFactor_; 33 | std::vector perfFds_; 34 | pid_t childPid_{}; 35 | 36 | // Barrier used for synchronization 37 | pthread_barrier_t* barrier_{}; 38 | }; 39 | 40 | } // namespace perf 41 | } // namespace s2j 42 | -------------------------------------------------------------------------------- /src/printer/AugmentedOIOutputBuilder.cc: -------------------------------------------------------------------------------- 1 | #include "AugmentedOIOutputBuilder.h" 2 | 3 | #include 4 | 5 | namespace s2j { 6 | namespace printer { 7 | 8 | const std::string AugmentedOIOutputBuilder::FORMAT_NAME = "oiaug"; 9 | 10 | std::string AugmentedOIOutputBuilder::dump() const { 11 | KillReason reason = killReason_; 12 | if (reason == KillReason::NONE) { 13 | if (killSignal_ > 0 || exitStatus_ > 0) { 14 | reason = KillReason::RE; 15 | } 16 | } 17 | 18 | std::stringstream ss; 19 | ss << killReasonName(reason) << " " << exitStatus_ << " " 20 | << milliSecondsElapsed_ << " " << 0ULL << " " << memoryPeakKb_ << " " 21 | << syscallsCounter_ << std::endl; 22 | dumpStatus(ss); 23 | ss << std::endl; 24 | return ss.str(); 25 | } 26 | 27 | } // namespace printer 28 | } // namespace s2j 29 | -------------------------------------------------------------------------------- /src/printer/AugmentedOIOutputBuilder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "OIModelOutputBuilder.h" 4 | 5 | namespace s2j { 6 | namespace printer { 7 | 8 | class AugmentedOIOutputBuilder : public OIModelOutputBuilder { 9 | public: 10 | std::string dump() const override; 11 | 12 | const static std::string FORMAT_NAME; 13 | }; 14 | 15 | } // namespace printer 16 | } // namespace s2j 17 | -------------------------------------------------------------------------------- /src/printer/HumanReadableOIOutputBuilder.cc: -------------------------------------------------------------------------------- 1 | #include "HumanReadableOIOutputBuilder.h" 2 | 3 | #include 4 | 5 | namespace s2j { 6 | namespace printer { 7 | 8 | const std::string HumanReadableOIOutputBuilder::FORMAT_NAME = "human"; 9 | 10 | std::string HumanReadableOIOutputBuilder::dump() const { 11 | // This is inspired by the oiejq script 12 | std::stringstream ss; 13 | ss << std::endl << "-------------------------" << std::endl << "Result: "; 14 | dumpStatus(ss); 15 | ss << std::endl 16 | << "Time used: " << static_cast(milliSecondsElapsed_) / 1000 17 | << "s" << std::endl 18 | << "Memory used: " << memoryPeakKb_ / 1024 << "MiB" << std::endl; 19 | return ss.str(); 20 | } 21 | 22 | } // namespace printer 23 | } // namespace s2j 24 | -------------------------------------------------------------------------------- /src/printer/HumanReadableOIOutputBuilder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "OIModelOutputBuilder.h" 4 | 5 | namespace s2j { 6 | namespace printer { 7 | 8 | class HumanReadableOIOutputBuilder : public OIModelOutputBuilder { 9 | public: 10 | std::string dump() const override; 11 | 12 | const static std::string FORMAT_NAME; 13 | }; 14 | 15 | } // namespace printer 16 | } // namespace s2j 17 | -------------------------------------------------------------------------------- /src/printer/OIModelOutputBuilder.cc: -------------------------------------------------------------------------------- 1 | #include "OIModelOutputBuilder.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace s2j { 7 | namespace printer { 8 | 9 | OIModelOutputBuilder::OIModelOutputBuilder() 10 | : milliSecondsElapsed_(0) 11 | , realMilliSecondsElapsed_(0) 12 | , memoryPeakKb_(0) 13 | , syscallsCounter_(0) 14 | , exitStatus_(0) 15 | , killSignal_(0) {} 16 | 17 | OutputBuilder& OIModelOutputBuilder::setCyclesUsed(uint64_t cyclesUsed) { 18 | milliSecondsElapsed_ = cyclesUsed * 1'000 / CYCLES_PER_SECOND; 19 | return *this; 20 | } 21 | 22 | OutputBuilder& OIModelOutputBuilder::setRealTimeMicroseconds(uint64_t time) { 23 | realMilliSecondsElapsed_ = time / 1000; 24 | return *this; 25 | } 26 | 27 | OutputBuilder& OIModelOutputBuilder::setUserTimeMicroseconds(uint64_t time) { 28 | userMilliSecondsElapsed_ = time / 1000; 29 | return *this; 30 | } 31 | 32 | OutputBuilder& OIModelOutputBuilder::setSysTimeMicroseconds(uint64_t time) { 33 | sysMilliSecondsElapsed_ = time / 1000; 34 | return *this; 35 | } 36 | 37 | OutputBuilder& OIModelOutputBuilder::setMemoryPeak(uint64_t memoryPeakKb) { 38 | memoryPeakKb_ = memoryPeakKb; 39 | return *this; 40 | } 41 | 42 | OutputBuilder& OIModelOutputBuilder::setExitStatus(uint32_t exitStatus) { 43 | if (exitStatus_ == 0) { 44 | exitStatus_ = exitStatus; 45 | } 46 | return *this; 47 | } 48 | 49 | OutputBuilder& OIModelOutputBuilder::setKillSignal(uint32_t killSignal) { 50 | if (killSignal_ == 0) { 51 | killSignal_ = killSignal; 52 | setExitStatus(128 + killSignal_); 53 | } 54 | return *this; 55 | } 56 | 57 | OutputBuilder& OIModelOutputBuilder::setKillReason( 58 | KillReason reason, 59 | const std::string& comment) { 60 | // Remember only first kill reason 61 | if (killReason_ == KillReason::NONE) { 62 | killReason_ = reason; 63 | killReasonComment_ = comment; 64 | } 65 | 66 | return *this; 67 | } 68 | 69 | void OIModelOutputBuilder::dumpStatus(std::ostream& ss) const { 70 | if (killReason_ != KillReason::NONE) { 71 | ss << killReasonComment_; 72 | } 73 | else if (killSignal_ > 0) { 74 | ss << "process exited due to signal " << killSignal_; 75 | } 76 | else if (exitStatus_ > 0) { 77 | ss << "runtime error " << exitStatus_; 78 | } 79 | else { 80 | ss << "ok"; 81 | } 82 | } 83 | 84 | } // namespace printer 85 | } // namespace s2j 86 | -------------------------------------------------------------------------------- /src/printer/OIModelOutputBuilder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "OutputBuilder.h" 4 | 5 | #include 6 | 7 | namespace s2j { 8 | namespace printer { 9 | 10 | class OIModelOutputBuilder : public OutputBuilder { 11 | public: 12 | OIModelOutputBuilder(); 13 | 14 | OutputBuilder& setCyclesUsed(uint64_t cyclesUsed) override; 15 | OutputBuilder& setRealTimeMicroseconds(uint64_t time) override; 16 | OutputBuilder& setUserTimeMicroseconds(uint64_t time) override; 17 | OutputBuilder& setSysTimeMicroseconds(uint64_t time) override; 18 | OutputBuilder& setMemoryPeak(uint64_t memoryPeakKb) override; 19 | OutputBuilder& setExitStatus(uint32_t exitStatus) override; 20 | OutputBuilder& setKillSignal(uint32_t killSignal) override; 21 | OutputBuilder& setKillReason(KillReason reason, const std::string& comment) 22 | override; 23 | 24 | protected: 25 | static const uint64_t CYCLES_PER_SECOND = 2'000'000'000; 26 | 27 | uint64_t milliSecondsElapsed_; 28 | uint64_t realMilliSecondsElapsed_; 29 | uint64_t userMilliSecondsElapsed_; 30 | uint64_t sysMilliSecondsElapsed_; 31 | uint64_t memoryPeakKb_; 32 | uint64_t syscallsCounter_; 33 | uint32_t exitStatus_; 34 | uint32_t killSignal_; 35 | 36 | KillReason killReason_ = KillReason::NONE; 37 | std::string killReasonComment_; 38 | 39 | void dumpStatus(std::ostream& ss) const; 40 | }; 41 | 42 | } // namespace printer 43 | } // namespace s2j 44 | -------------------------------------------------------------------------------- /src/printer/OITimeToolOutputBuilder.cc: -------------------------------------------------------------------------------- 1 | #include "OITimeToolOutputBuilder.h" 2 | #include "common/Exception.h" 3 | 4 | #include 5 | 6 | namespace s2j { 7 | namespace printer { 8 | 9 | const std::string OITimeToolOutputBuilder::FORMAT_NAME = "oitt"; 10 | 11 | std::string OITimeToolOutputBuilder::dump() const { 12 | // mimic orginal oititmetools' output 13 | std::stringstream ss; 14 | ss << "__RESULT__ " << encodeStatusCode() << " " << milliSecondsElapsed_ 15 | << " " << 0ULL << " " << memoryPeakKb_ << " " << syscallsCounter_ 16 | << std::endl; 17 | dumpStatus(ss); 18 | ss << std::endl; 19 | return ss.str(); 20 | } 21 | 22 | int OITimeToolOutputBuilder::encodeStatusCode() const { 23 | static const int CODE_SIG_BASE = 0; 24 | static const int CODE_RE_BASE = 200; 25 | 26 | if (killReason_ == KillReason::NONE) { 27 | // NOTE: order of ifs is important, as nonzero killSignal also sets 28 | // exitStatus_ 29 | if (killSignal_ > 0) { 30 | return CODE_SIG_BASE + killSignal_; 31 | } 32 | if (exitStatus_ > 0) { 33 | return CODE_RE_BASE + exitStatus_; 34 | } 35 | } 36 | 37 | switch (killReason_) { 38 | case KillReason::NONE: 39 | return 0; 40 | case KillReason::RE: 41 | return 100; 42 | case KillReason::RV: 43 | return 121; 44 | case KillReason::TLE: 45 | return 125; 46 | case KillReason::MLE: 47 | return 124; 48 | case KillReason::OLE: 49 | return 120; 50 | }; 51 | __builtin_unreachable(); 52 | } 53 | 54 | } // namespace printer 55 | } // namespace s2j 56 | -------------------------------------------------------------------------------- /src/printer/OITimeToolOutputBuilder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "OIModelOutputBuilder.h" 4 | 5 | namespace s2j { 6 | namespace printer { 7 | 8 | class OITimeToolOutputBuilder : public OIModelOutputBuilder { 9 | public: 10 | std::string dump() const override; 11 | 12 | const static std::string FORMAT_NAME; 13 | 14 | private: 15 | int encodeStatusCode() const; 16 | }; 17 | 18 | } // namespace printer 19 | } // namespace s2j 20 | -------------------------------------------------------------------------------- /src/printer/OutputBuilder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace s2j { 7 | namespace printer { 8 | 9 | class OutputBuilder { 10 | public: 11 | enum class KillReason { NONE, RE, RV, TLE, MLE, OLE }; 12 | 13 | static std::string killReasonName(KillReason reason) { 14 | switch (reason) { 15 | case KillReason::NONE: 16 | return "OK"; 17 | case KillReason::RE: 18 | return "RE"; 19 | case KillReason::RV: 20 | return "RV"; 21 | case KillReason::TLE: 22 | return "TLE"; 23 | case KillReason::MLE: 24 | return "MLE"; 25 | case KillReason::OLE: 26 | return "OLE"; 27 | } 28 | __builtin_unreachable(); 29 | } 30 | 31 | virtual ~OutputBuilder() = default; 32 | 33 | virtual OutputBuilder& setCyclesUsed(uint64_t cyclesUsed) { 34 | return *this; 35 | } 36 | virtual OutputBuilder& setRealTimeMicroseconds(uint64_t time) { 37 | return *this; 38 | } 39 | virtual OutputBuilder& setUserTimeMicroseconds(uint64_t time) { 40 | return *this; 41 | } 42 | virtual OutputBuilder& setSysTimeMicroseconds(uint64_t time) { 43 | return *this; 44 | } 45 | virtual OutputBuilder& setMemoryPeak(uint64_t memoryPeakKb) { 46 | return *this; 47 | } 48 | virtual OutputBuilder& setExitStatus(uint32_t exitStatus) { 49 | return *this; 50 | } 51 | virtual OutputBuilder& setKillSignal(uint32_t killSignal) { 52 | return *this; 53 | } 54 | virtual OutputBuilder& setKillReason( 55 | KillReason reason, 56 | const std::string& comment) { 57 | return *this; 58 | } 59 | 60 | virtual std::string dump() const { 61 | return ""; 62 | } 63 | }; 64 | 65 | } // namespace printer 66 | } // namespace s2j 67 | -------------------------------------------------------------------------------- /src/printer/OutputSource.cc: -------------------------------------------------------------------------------- 1 | #include "OutputSource.h" 2 | 3 | #include 4 | 5 | namespace s2j { 6 | namespace printer { 7 | 8 | void OutputSource::setOutputBuilder( 9 | std::shared_ptr outputBuilder) { 10 | outputBuilder_ = std::move(outputBuilder); 11 | } 12 | 13 | } // namespace printer 14 | } // namespace s2j 15 | -------------------------------------------------------------------------------- /src/printer/OutputSource.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "OutputBuilder.h" 4 | 5 | #include 6 | 7 | namespace s2j { 8 | namespace printer { 9 | 10 | class OutputSource { 11 | public: 12 | void setOutputBuilder(std::shared_ptr outputBuilder); 13 | 14 | protected: 15 | std::shared_ptr outputBuilder_; 16 | }; 17 | 18 | } // namespace printer 19 | } // namespace s2j 20 | -------------------------------------------------------------------------------- /src/printer/RealTimeOIOutputBuilder.cc: -------------------------------------------------------------------------------- 1 | #include "RealTimeOIOutputBuilder.h" 2 | 3 | #include 4 | 5 | namespace s2j { 6 | namespace printer { 7 | 8 | const std::string RealTimeOIOutputBuilder::FORMAT_NAME = "oireal"; 9 | 10 | std::string RealTimeOIOutputBuilder::dump() const { 11 | KillReason reason = killReason_; 12 | if (reason == KillReason::NONE) { 13 | if (killSignal_ > 0 || exitStatus_ > 0) { 14 | reason = KillReason::RE; 15 | } 16 | } 17 | 18 | std::stringstream ss; 19 | ss << killReasonName(reason) << " " << exitStatus_ << " " 20 | << realMilliSecondsElapsed_ << " " << 0ULL << " " << memoryPeakKb_ << " " 21 | << syscallsCounter_ << std::endl; 22 | dumpStatus(ss); 23 | ss << std::endl; 24 | return ss.str(); 25 | } 26 | 27 | } // namespace printer 28 | } // namespace s2j 29 | -------------------------------------------------------------------------------- /src/printer/RealTimeOIOutputBuilder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "OIModelOutputBuilder.h" 4 | 5 | namespace s2j { 6 | namespace printer { 7 | 8 | class RealTimeOIOutputBuilder : public OIModelOutputBuilder { 9 | public: 10 | std::string dump() const override; 11 | 12 | const static std::string FORMAT_NAME; 13 | }; 14 | 15 | } // namespace printer 16 | } // namespace s2j 17 | -------------------------------------------------------------------------------- /src/printer/UserTimeOIOutputBuilder.cc: -------------------------------------------------------------------------------- 1 | #include "UserTimeOIOutputBuilder.h" 2 | #include "common/Exception.h" 3 | 4 | #include 5 | 6 | namespace s2j { 7 | namespace printer { 8 | 9 | const std::string UserTimeOIOutputBuilder::FORMAT_NAME = "oiuser"; 10 | 11 | std::string UserTimeOIOutputBuilder::dump() const { 12 | KillReason reason = killReason_; 13 | if (reason == KillReason::NONE) { 14 | if (killSignal_ > 0 || exitStatus_ > 0) { 15 | reason = KillReason::RE; 16 | } 17 | } 18 | 19 | std::stringstream ss; 20 | ss << killReasonName(reason) << " " << exitStatus_ << " " 21 | << userMilliSecondsElapsed_ << " " << 0ULL << " " << memoryPeakKb_ << " " 22 | << syscallsCounter_ << std::endl; 23 | dumpStatus(ss); 24 | ss << std::endl; 25 | return ss.str(); 26 | } 27 | 28 | } // namespace printer 29 | } // namespace s2j 30 | -------------------------------------------------------------------------------- /src/printer/UserTimeOIOutputBuilder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "OIModelOutputBuilder.h" 4 | 5 | namespace s2j { 6 | namespace printer { 7 | 8 | class UserTimeOIOutputBuilder : public OIModelOutputBuilder { 9 | public: 10 | std::string dump() const override; 11 | 12 | const static std::string FORMAT_NAME; 13 | }; 14 | 15 | } // namespace printer 16 | } // namespace s2j 17 | -------------------------------------------------------------------------------- /src/priv/PrivListener.cc: -------------------------------------------------------------------------------- 1 | #include "PrivListener.h" 2 | 3 | #include "common/Exception.h" 4 | #include "common/Utils.h" 5 | #include "common/WithErrnoCheck.h" 6 | #include "logger/Logger.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace s2j { 16 | namespace priv { 17 | 18 | const Feature PrivListener::feature = Feature::CAPABILITY_DROP; 19 | 20 | PrivListener::PrivListener() 21 | : secureBits_(withErrnoCheck( 22 | "prct get secure bits", 23 | prctl, 24 | PR_GET_SECUREBITS)) {} 25 | 26 | void PrivListener::onPostForkParent(pid_t /*childPid*/) { 27 | TRACE(); 28 | 29 | dropBSet(); 30 | noNewPrivs(); 31 | dropCaps(); 32 | } 33 | 34 | void PrivListener::onPostForkChild() { 35 | TRACE(); 36 | 37 | dropBSet(); 38 | noNewPrivs(); 39 | dropCaps(); 40 | } 41 | 42 | void PrivListener::dropBSet() { 43 | TRACE(); 44 | 45 | for (int i = 0; i <= CAP_LAST_CAP; ++i) { 46 | try { 47 | withErrnoCheck( 48 | "prctl drop cap " + std::to_string(i), 49 | prctl, 50 | PR_CAPBSET_DROP, 51 | i, 52 | 0, 53 | 0, 54 | 0); 55 | } 56 | catch (s2j::SystemException& ex) { 57 | if (ex.getErrno() != EINVAL) { 58 | throw ex; 59 | } 60 | } 61 | } 62 | } 63 | 64 | void PrivListener::dropCaps() { 65 | TRACE(); 66 | 67 | /* If we're uid=0, we MUST set secbit_noroot or fail with an error. 68 | * IOW, the following must NEVER happen: 69 | * uid == 0 && SECBIT_NOROOT == 0 && we're doing execve() 70 | * otherwise, there's no point dropping capabilities. 71 | */ 72 | addSecureBits(SECBIT_NOROOT | SECBIT_NOROOT_LOCKED); 73 | 74 | __user_cap_header_struct header = { 75 | _LINUX_CAPABILITY_VERSION_3, // version 76 | 0 // pid 77 | }; 78 | 79 | #ifdef PR_CAP_AMBIENT_CLEAR_ALL 80 | if (checkKernelVersion(4, 3)) { 81 | // clear ambient set, just in case 82 | withErrnoCheck( 83 | "clear ambient caps", 84 | prctl, 85 | PR_CAP_AMBIENT, 86 | PR_CAP_AMBIENT_CLEAR_ALL, 87 | 0, 88 | 0, 89 | 0); 90 | } 91 | else { 92 | logger::warn("kernel older than 4.3, not clearing ambinet caps"); 93 | } 94 | #endif 95 | 96 | // version 3 - 64-bit capabilities 97 | __user_cap_data_struct caps[2] = { 98 | { 99 | 0, // effective 100 | 0, // permitted 101 | 0 // inheritable 102 | }, 103 | { 104 | 0, // effective 105 | 0, // permitted 106 | 0 // inheritable 107 | }, 108 | }; 109 | withErrnoCheck("capset", capset, &header, caps); 110 | } 111 | 112 | void PrivListener::noNewPrivs() { 113 | TRACE(); 114 | 115 | addSecureBits(SECBIT_NOROOT | SECBIT_NOROOT_LOCKED); 116 | #ifdef SECBIT_NO_CAP_AMBIENT_RAISE // Present since linux 4.3 117 | // Apparently, some distros have CAP_AMBINET_RAISE in headers even on 118 | // pre-4.3 kernels. But only in headers. So we need a runtime check. 119 | if (checkKernelVersion(4, 3)) { 120 | addSecureBits( 121 | SECBIT_NO_CAP_AMBIENT_RAISE | 122 | SECBIT_NO_CAP_AMBIENT_RAISE_LOCKED); 123 | } 124 | else { 125 | logger::warn( 126 | "kernel is older than 4.3, not setting NO_CAP_AMBIENT_RAISE"); 127 | } 128 | #endif // ifdef SECBIT_NO_CAP_AMBIENT_RAISE 129 | withErrnoCheck( 130 | "prctl no new privs", prctl, PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 131 | } 132 | 133 | // This function must NEVER succeed if the bits in question are not set 134 | void PrivListener::addSecureBits(unsigned long bits) { 135 | TRACE(bits); 136 | 137 | try { 138 | if ((secureBits_ & bits) != bits) { 139 | secureBits_ |= bits; 140 | withErrnoCheck( 141 | "prctl set securebits", 142 | prctl, 143 | PR_SET_SECUREBITS, 144 | secureBits_, 145 | 0, 146 | 0, 147 | 0); 148 | } 149 | } 150 | catch (const SystemException& ex) { 151 | if (ex.getErrno() != EPERM) { 152 | throw; 153 | } 154 | logger::debug("Set securebits failed with EPERM"); 155 | } 156 | } 157 | 158 | } // namespace priv 159 | } // namespace s2j 160 | -------------------------------------------------------------------------------- /src/priv/PrivListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/Feature.h" 4 | #include "executor/ExecuteEventListener.h" 5 | #include "printer/OutputSource.h" 6 | 7 | namespace s2j { 8 | namespace priv { 9 | 10 | class PrivListener : public executor::ExecuteEventListener { 11 | public: 12 | PrivListener(); 13 | 14 | void onPostForkParent(pid_t childPid) override; 15 | void onPostForkChild() override; 16 | 17 | const static Feature feature; 18 | 19 | private: 20 | void dropBSet(); 21 | void dropCaps(); 22 | void noNewPrivs(); 23 | void addSecureBits(unsigned long bits); 24 | 25 | unsigned long secureBits_; 26 | }; 27 | 28 | } // namespace priv 29 | } // namespace s2j 30 | -------------------------------------------------------------------------------- /src/s2japp/Application.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ApplicationSettings.h" 4 | 5 | #include "logger/Logger.h" 6 | #include "printer/OutputBuilder.h" 7 | 8 | namespace s2j { 9 | namespace app { 10 | 11 | class Application { 12 | public: 13 | enum ExitCode : int { 14 | OK = 0, 15 | PARSE_ERROR = 1, 16 | FATAL_ERROR = 2, 17 | }; 18 | 19 | Application(int argc, const char* argv[]); 20 | Application(ApplicationSettings settings); 21 | 22 | ExitCode main(); 23 | 24 | private: 25 | ExitCode handleHelp(); 26 | ExitCode handleVersion(); 27 | ExitCode handleRun(); 28 | 29 | const ApplicationSettings settings_; 30 | std::shared_ptr logger_; 31 | 32 | void initializeLogger(); 33 | 34 | template 35 | std::shared_ptr createListener(Args... args); 36 | 37 | template 38 | void forEachListener(Operation operation, Listeners... listeners); 39 | }; 40 | 41 | } // namespace app 42 | } // namespace s2j 43 | -------------------------------------------------------------------------------- /src/s2japp/ApplicationArguments.cc: -------------------------------------------------------------------------------- 1 | #include "ApplicationArguments.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | /** 11 | * This is tiny bit repetitive. Possibly could be replaced by one "fun" template 12 | */ 13 | 14 | namespace s2j { 15 | namespace app { 16 | namespace args { 17 | 18 | const std::array MemoryArgument::sizes = {"b", "k", "m", "g"}; 19 | 20 | MemoryArgument& MemoryArgument::operator=(const std::string& str) { 21 | std::string istr = str; 22 | std::transform(str.begin(), str.end(), istr.begin(), ::tolower); 23 | std::istringstream iss(istr); 24 | std::string unit; 25 | 26 | if (!(iss >> value_)) { 27 | throw TCLAP::ArgParseException(str + " is not a valid memory limit"); 28 | } 29 | if (!(iss >> unit)) { 30 | unit = "k"; 31 | } 32 | 33 | // std::array::size is constexpr 34 | for (const auto& size: sizes) { 35 | if (size == unit) { 36 | return *this; 37 | } 38 | value_ *= 1024; 39 | } 40 | throw TCLAP::ArgParseException(unit + " is not a valid memory unit"); 41 | } 42 | 43 | const std::array TimeArgument::sizes = 44 | {"u", "ms", "s", "m", "h", "d"}; 45 | const std::array TimeArgument::multipliers = 46 | {1000, 1000, 60, 60, 60, 24}; 47 | 48 | TimeArgument& TimeArgument::operator=(const std::string& str) { 49 | std::string istr = str; 50 | std::transform(str.begin(), str.end(), istr.begin(), ::tolower); 51 | std::istringstream iss(istr); 52 | std::string unit; 53 | 54 | if (!(iss >> value_)) { 55 | throw TCLAP::ArgParseException(str + " is not a valid time limit"); 56 | } 57 | if (!(iss >> unit)) { 58 | unit = "u"; 59 | } 60 | 61 | // std::array::size is constexpr 62 | for (size_t i = 0; i < sizes.size(); ++i) { 63 | if (sizes[i] == unit) { 64 | return *this; 65 | } 66 | value_ *= multipliers[i]; 67 | } 68 | throw TCLAP::ArgParseException(unit + " is not a valid time unit"); 69 | } 70 | 71 | const std::array AmountArgument::sizes = {"", "k", "m", "g"}; 72 | 73 | AmountArgument& AmountArgument::operator=(const std::string& str) { 74 | std::string istr = str; 75 | std::transform(str.begin(), str.end(), istr.begin(), ::tolower); 76 | std::istringstream iss(istr); 77 | std::string unit; 78 | 79 | if (!(iss >> value_)) { 80 | throw TCLAP::ArgParseException( 81 | str + " is not a valid amount specifier"); 82 | } 83 | if (!(iss >> unit)) { 84 | unit = ""; 85 | } 86 | 87 | // std::array::size is constexpr 88 | for (const auto& size: sizes) { 89 | if (size == unit) { 90 | return *this; 91 | } 92 | value_ *= 1000; 93 | } 94 | throw TCLAP::ArgParseException(unit + " is not a valid amount unit"); 95 | } 96 | 97 | } // namespace args 98 | } // namespace app 99 | } // namespace s2j 100 | -------------------------------------------------------------------------------- /src/s2japp/ApplicationArguments.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "common/Utils.h" 10 | 11 | namespace s2j { 12 | namespace app { 13 | namespace args { 14 | 15 | /** 16 | * A simple wrapper for memory sizes read from command line. 17 | */ 18 | class MemoryArgument { 19 | static const std::array sizes; 20 | 21 | size_t value_; // Size in bytes 22 | 23 | public: 24 | MemoryArgument() : value_(0) {} 25 | 26 | MemoryArgument(size_t value) : value_(value) {} 27 | 28 | MemoryArgument& operator=(const std::string& str); 29 | 30 | size_t getValue() const { 31 | return value_; 32 | } 33 | 34 | operator size_t() const { 35 | return getValue(); 36 | } 37 | }; 38 | 39 | 40 | /** 41 | * A simple wrapper for times read from command line. 42 | */ 43 | class TimeArgument { 44 | static const std::array sizes; 45 | static const std::array multipliers; 46 | 47 | size_t value_; // Size in microseconds 48 | 49 | public: 50 | TimeArgument() : value_(0) {} 51 | 52 | TimeArgument(size_t value) : value_(value) {} 53 | 54 | TimeArgument& operator=(const std::string& str); 55 | 56 | size_t getValue() const { 57 | return value_; 58 | } 59 | 60 | operator size_t() const { 61 | return getValue(); 62 | } 63 | }; 64 | 65 | 66 | /** 67 | * A simple wrapper for large numbers from command line. 68 | */ 69 | class AmountArgument { 70 | static const std::array sizes; 71 | 72 | size_t value_; // Size in microseconds 73 | 74 | public: 75 | AmountArgument() : value_(0) {} 76 | 77 | AmountArgument(size_t value) : value_(value) {} 78 | 79 | AmountArgument& operator=(const std::string& str); 80 | 81 | size_t getValue() const { 82 | return value_; 83 | } 84 | 85 | operator size_t() const { 86 | return getValue(); 87 | } 88 | }; 89 | 90 | 91 | /** 92 | * A simple wrapper that chooses implementation by a name 93 | */ 94 | template 95 | class ImplementationNameArgument 96 | : public TCLAP::Constraint> { 97 | // should be const, but sadly TCLAP doens't allow this 98 | FactoryMap factories_; 99 | std::string description_, implementationName_; 100 | 101 | public: 102 | template< 103 | typename DescriptionType, 104 | typename DefaultNameType, 105 | typename FactoryMapType> 106 | ImplementationNameArgument( 107 | DescriptionType&& description, 108 | DefaultNameType& defaultName, 109 | FactoryMapType&& factories) 110 | : factories_(std::forward(factories)) 111 | , description_(std::forward(description)) 112 | , implementationName_(std::forward(defaultName)) {} 113 | 114 | ImplementationNameArgument(const ImplementationNameArgument&) = default; 115 | ImplementationNameArgument(ImplementationNameArgument&&) = default; 116 | ImplementationNameArgument& operator=(const ImplementationNameArgument&) = 117 | default; 118 | ImplementationNameArgument& operator=(ImplementationNameArgument&&) = 119 | default; 120 | 121 | ImplementationNameArgument& operator=(const std::string& name) { 122 | if (factories_.find(name) == factories_.end()) { 123 | throw TCLAP::ArgParseException( 124 | name + " is not a valid name for " + description_ + ", " + 125 | description()); 126 | } 127 | implementationName_ = name; 128 | return *this; 129 | } 130 | 131 | bool check(const ImplementationNameArgument& value) 132 | const override { 133 | return factories_.find(value.implementationName_) != factories_.end(); 134 | } 135 | 136 | std::string description() const override { 137 | std::stringstream ss; 138 | ss << "value should be one of: "; 139 | for (auto it = factories_.begin(); it != factories_.end(); ++it) { 140 | if (it != factories_.begin()) 141 | ss << ", "; 142 | ss << it->first; 143 | } 144 | return ss.str(); 145 | } 146 | 147 | std::string shortID() const override { 148 | std::stringstream ss; 149 | for (auto it = factories_.begin(); it != factories_.end(); ++it) { 150 | if (it != factories_.begin()) 151 | ss << "|"; 152 | ss << it->first; 153 | } 154 | return ss.str(); 155 | } 156 | 157 | Factory getFactory() const { 158 | return factories_.at(implementationName_); 159 | } 160 | }; 161 | 162 | 163 | } // namespace args 164 | } // namespace app 165 | } // namespace s2j 166 | namespace TCLAP { 167 | template<> 168 | struct ArgTraits<::s2j::app::args::MemoryArgument> { 169 | typedef StringLike ValueCategory; 170 | }; 171 | template<> 172 | struct ArgTraits<::s2j::app::args::TimeArgument> { 173 | typedef StringLike ValueCategory; 174 | }; 175 | template<> 176 | struct ArgTraits<::s2j::app::args::AmountArgument> { 177 | typedef StringLike ValueCategory; 178 | }; 179 | template 180 | struct ArgTraits<::s2j::app::args::ImplementationNameArgument> { 181 | typedef StringLike ValueCategory; 182 | }; 183 | } // namespace TCLAP 184 | -------------------------------------------------------------------------------- /src/s2japp/ApplicationException.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/Exception.h" 4 | 5 | namespace s2j { 6 | namespace app { 7 | 8 | class InvalidConfigurationException : public Exception { 9 | public: 10 | InvalidConfigurationException(const std::string& msg) 11 | : Exception("Invalid configration: " + msg) {} 12 | }; 13 | 14 | } // namespace app 15 | } // namespace s2j 16 | -------------------------------------------------------------------------------- /src/s2japp/ApplicationSettings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/Feature.h" 4 | #include "ns/MountNamespaceListener.h" 5 | #include "printer/OutputBuilder.h" 6 | #include "seccomp/policy/SyscallPolicy.h" 7 | 8 | #include "common/Utils.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace s2j { 17 | namespace app { 18 | 19 | struct ApplicationSettings : public ns::MountNamespaceListener::Settings { 20 | enum class Action { PRINT_HELP, PRINT_VERSION, RUN }; 21 | 22 | ApplicationSettings(); 23 | ApplicationSettings(int argc, const char* argv[]); 24 | 25 | static const std::string VERSION; 26 | static const std::string DESCRIPTION; 27 | static const FactoryMap OUTPUT_FORMATS; 28 | static const std::string DEFAULT_OUTPUT_FORMAT; 29 | static const FactoryMap 30 | SYSCALL_POLICIES; 31 | static const std::string DEFAULT_SYSCALL_POLICY; 32 | static const std::map> 33 | FEATURE_BY_NAME; 34 | 35 | Action action; 36 | std::string loggerPath; 37 | 38 | uint64_t memoryLimitKb{}; 39 | uint64_t outputLimitB{}; 40 | uint64_t instructionCountLimit{}; 41 | // [us] - microseconds, 10^(-6) s 42 | uint64_t rTimelimitUs{}; 43 | uint64_t uTimelimitUs{}; 44 | uint64_t sTimelimitUs{}; 45 | uint64_t usTimelimitUs{}; 46 | 47 | int resultsFD{}; 48 | int threadsLimit{}; 49 | uint32_t perfOversamplingFactor{}; 50 | 51 | std::string parsingError; 52 | std::string helpMessage; 53 | std::string versionMessage; 54 | 55 | std::string programName; 56 | std::vector programArgv; 57 | std::string programWorkingDir; 58 | 59 | Factory outputBuilderFactory; 60 | Factory syscallPolicyFactory; 61 | std::set features; 62 | 63 | bool suppressStderr{}; 64 | 65 | private: 66 | static const std::vector FLAGS_ON, FLAGS_OFF; 67 | 68 | void addBindMount(const std::string& bindMountLine); 69 | }; 70 | 71 | } // namespace app 72 | } // namespace s2j 73 | -------------------------------------------------------------------------------- /src/seccomp/SeccompContext.cc: -------------------------------------------------------------------------------- 1 | #include "SeccompContext.h" 2 | #include "SeccompException.h" 3 | 4 | #include "common/FD.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace s2j { 10 | namespace seccomp { 11 | 12 | const std::map 13 | SeccompContext::SECCOMP_FILTER_ARCHITECTURES( 14 | {{tracer::Arch::X86, SCMP_ARCH_X86}, 15 | {tracer::Arch::X86_64, SCMP_ARCH_X86_64}}); 16 | 17 | const uint32_t SeccompContext::SECCOMP_TRACE_MSG_NUM_SHIFT = 3; 18 | 19 | SeccompContext::Builder::Builder() { 20 | for (const auto arch: SECCOMP_FILTER_ARCHITECTURES) { 21 | scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_TRACE(0)); 22 | if (ctx == nullptr) { 23 | throw Exception("Can't create a seccomp context"); 24 | } 25 | if (seccomp_arch_remove(ctx, SCMP_ARCH_NATIVE) < 0) { 26 | throw Exception("Can't remove native architecture"); 27 | } 28 | if (seccomp_arch_add(ctx, arch.second) < 0) { 29 | throw Exception("Can't add architecture to seccomp context"); 30 | } 31 | ctx_.emplace(arch.first, ctx); 32 | } 33 | } 34 | 35 | SeccompContext::Builder::~Builder() { 36 | std::for_each(ctx_.begin(), ctx_.end(), [](auto ctx) { 37 | seccomp_release(ctx.second); 38 | }); 39 | ctx_.clear(); 40 | } 41 | 42 | SeccompContext SeccompContext::Builder::build() && { 43 | TRACE(); 44 | 45 | return SeccompContext(std::move(*this)); 46 | } 47 | 48 | void SeccompContext::Builder::addRule( 49 | const SeccompRule& rule, 50 | uint32_t actionGroupId) { 51 | TRACE(rule.syscall, actionGroupId); 52 | 53 | auto filter = rule.filter->createLibSeccompFilter(); 54 | for (auto& arch: SECCOMP_FILTER_ARCHITECTURES) { 55 | int32_t ruleId = (actionGroupId << SECCOMP_TRACE_MSG_NUM_SHIFT) | 56 | static_cast(arch.first); 57 | 58 | rule.action->setRuleId(ruleId); 59 | auto action = rule.action->createLibSeccompAction(); 60 | if (!rule.filter->isPureLibSeccompFilter()) { 61 | /** 62 | * When filter is too complex to be expressed by libseccomp filters, 63 | * always trace when rule is matched. Later filter will be checked 64 | * by tracer. 65 | */ 66 | action = SCMP_ACT_TRACE(ruleId); 67 | } 68 | 69 | int res; 70 | 71 | /** 72 | * XXX 73 | * Libseccomp seems to optimize cases when there is an action without 74 | * any filter conditions. In these cases other rules are discarded. As 75 | * we rely on order and masking of rules always include at least one 76 | * filter condition. 77 | */ 78 | if (!filter.empty()) { 79 | res = seccomp_rule_add_array( 80 | ctx_[arch.first], 81 | action, 82 | rule.syscall, 83 | filter.size(), 84 | &filter[0]); 85 | } 86 | else { 87 | res = seccomp_rule_add( 88 | ctx_[arch.first], 89 | action, 90 | rule.syscall, 91 | 1, 92 | SCMP_CMP(0, SCMP_CMP_GE, 0)); 93 | if (res > 0) { 94 | res = seccomp_rule_add( 95 | ctx_[arch.first], 96 | action, 97 | rule.syscall, 98 | 1, 99 | SCMP_CMP(0, SCMP_CMP_LT, 0)); 100 | } 101 | } 102 | if (res < 0) { 103 | throw SystemException("Can't add rule to seccomp filter", -res); 104 | } 105 | } 106 | } 107 | 108 | SeccompContext::SeccompContext(Builder&& builder) { 109 | if (builder.ctx_.empty()) { 110 | ctx_ = seccomp_init(SCMP_ACT_KILL); 111 | if (ctx_ == nullptr) { 112 | throw Exception("Can't create a seccomp context"); 113 | } 114 | } 115 | else { 116 | ctx_ = builder.ctx_.begin()->second; 117 | builder.ctx_.erase(builder.ctx_.begin()); 118 | 119 | for (auto it = builder.ctx_.begin(); it != builder.ctx_.end(); 120 | it = builder.ctx_.erase(it)) { 121 | if (seccomp_merge(ctx_, it->second) < 0) { 122 | throw Exception("Can't merge libseccomp filters"); 123 | } 124 | } 125 | } 126 | } 127 | 128 | SeccompContext::~SeccompContext() { 129 | if (ctx_ != nullptr) { 130 | seccomp_release(ctx_); 131 | ctx_ = nullptr; 132 | } 133 | } 134 | 135 | void SeccompContext::loadFilter() { 136 | TRACE(); 137 | 138 | if (seccomp_load(ctx_) < 0) { 139 | throw SeccompException("Filter load failed"); 140 | } 141 | } 142 | 143 | std::string SeccompContext::exportFilter() const { 144 | FD fd(withErrnoCheck("memfd_create", syscall, __NR_memfd_create, "", 0)); 145 | withErrnoCheck("truncate memfd_created file", ftruncate, fd, 1024 * 1024); 146 | 147 | // Export filter... 148 | if (seccomp_export_pfc(ctx_, fd) < 0) { 149 | throw Exception("Can't export libseccomp filter"); 150 | } 151 | 152 | // ... and read it. 153 | withErrnoCheck("lseek on memfd file", lseek, fd, 0, SEEK_SET); 154 | std::string pseudoCode; 155 | while (true) { 156 | char buff[32 * 4096]; 157 | ssize_t bytesRead = 158 | withErrnoCheck("read", read, fd, buff, sizeof(buff) - 1); 159 | if (bytesRead <= 0) { 160 | break; 161 | } 162 | 163 | // Filter out NULL bytes 164 | for (ssize_t index = 0; index < bytesRead; ++index) { 165 | if (buff[index] != '\0') { 166 | pseudoCode += buff[index]; 167 | } 168 | } 169 | } 170 | 171 | return pseudoCode; 172 | } 173 | 174 | } // namespace seccomp 175 | } // namespace s2j 176 | -------------------------------------------------------------------------------- /src/seccomp/SeccompContext.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SeccompRule.h" 4 | 5 | #include "tracer/Tracee.h" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace s2j { 14 | namespace seccomp { 15 | 16 | class SeccompContext { 17 | public: 18 | class Builder { 19 | public: 20 | Builder(); 21 | ~Builder(); 22 | 23 | /** 24 | * Adds new rule to filter, it won't be active until @loadFilter is 25 | * called. 26 | */ 27 | void addRule(const SeccompRule& rule, uint32_t actionGroupId); 28 | 29 | /** 30 | * Consumes builder and creates seccomp context. 31 | */ 32 | SeccompContext build() &&; 33 | 34 | private: 35 | friend class SeccompContext; 36 | 37 | /** 38 | * Mantain two separate contexts, one for x86 and one for i386 39 | * architecture. In laodFilter merge them. This allows to distuinguish 40 | * syscalls architectures 41 | */ 42 | std::map ctx_; 43 | }; 44 | 45 | SeccompContext(Builder&& builder); 46 | ~SeccompContext(); 47 | 48 | /** 49 | * Loads filter into kernel, from this point it will be active. 50 | */ 51 | void loadFilter(); 52 | 53 | /** 54 | * Exports filter in human-readable pseudocode. 55 | */ 56 | std::string exportFilter() const; 57 | 58 | /** 59 | * List of all supported architectures 60 | */ 61 | static const std::map SECCOMP_FILTER_ARCHITECTURES; 62 | 63 | /** 64 | * Shift for trace values, smallest power of two not lesser than 65 | * size of SECCOMP_FILTER_ARCHITECTURES. 66 | */ 67 | static const uint32_t SECCOMP_TRACE_MSG_NUM_SHIFT; 68 | 69 | 70 | private: 71 | /** 72 | * Libseccomp's context of created filter. 73 | */ 74 | scmp_filter_ctx ctx_; 75 | }; 76 | 77 | } // namespace seccomp 78 | } // namespace s2j 79 | -------------------------------------------------------------------------------- /src/seccomp/SeccompException.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/Exception.h" 4 | 5 | namespace s2j { 6 | namespace seccomp { 7 | 8 | class SeccompException : public Exception { 9 | public: 10 | SeccompException(const std::string& msg) : Exception(msg) {} 11 | }; 12 | 13 | class SeccompFilterAlreadyCompiledException : public SeccompException { 14 | public: 15 | SeccompFilterAlreadyCompiledException() 16 | : SeccompException("Can't add new seccomp rule, seccomp filter " 17 | "already compiled") {} 18 | }; 19 | 20 | class UnknownSyscallNameException : public SeccompException { 21 | public: 22 | UnknownSyscallNameException(const std::string& syscallName) 23 | : SeccompException("Unknown syscall \"" + syscallName + "\"") {} 24 | }; 25 | 26 | } // namespace seccomp 27 | } // namespace s2j 28 | -------------------------------------------------------------------------------- /src/seccomp/SeccompListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SeccompContext.h" 4 | #include "SeccompException.h" 5 | #include "SeccompRule.h" 6 | #include "action/SeccompAction.h" 7 | #include "filter/SyscallFilter.h" 8 | #include "policy/DefaultPolicy.h" 9 | 10 | #include "common/Assert.h" 11 | #include "common/Feature.h" 12 | #include "executor/ExecuteEventListener.h" 13 | #include "printer/OutputSource.h" 14 | #include "tracer/TraceEventListener.h" 15 | #include "tracer/Tracee.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace s2j { 23 | namespace seccomp { 24 | 25 | class SeccompListener 26 | : public virtual executor::ExecuteEventListener 27 | , public virtual tracer::TraceEventListener 28 | , public printer::OutputSource { 29 | public: 30 | using syscall_t = int; 31 | 32 | SeccompListener(); 33 | SeccompListener(std::shared_ptr basePolicy); 34 | 35 | /* Create seccomp context and build syscall filter. */ 36 | void onPreFork() override; 37 | 38 | /* Load syscall filter into kernel. */ 39 | void onPostForkChild() override; 40 | 41 | /* Inform any rules about event that have occured. */ 42 | tracer::TraceAction onTraceEvent( 43 | const tracer::TraceEvent& traceEvent, 44 | tracer::Tracee& tracee) override; 45 | 46 | /* Adds new rule to seccomp syscall filter. */ 47 | template 48 | void addRule(SeccompRule&& rule) { 49 | if (context_ != nullptr) 50 | throw SeccompFilterAlreadyCompiledException(); 51 | rules_[rule.syscall].emplace_back(std::forward(rule)); 52 | } 53 | 54 | /* Emebds new policy */ 55 | void addPolicy(const policy::SyscallPolicy& policy); 56 | 57 | const static Feature feature; 58 | 59 | private: 60 | static std::string resolveSyscallNumber( 61 | uint32_t syscallNumber, 62 | const tracer::Arch& arch); 63 | 64 | const static uint32_t TRACE_EVENT_ID_BASE; 65 | 66 | std::unique_ptr context_; 67 | 68 | std::shared_ptr basePolicy_; 69 | 70 | std::map> rules_; 71 | std::map rulesById_; 72 | 73 | tracer::Arch lastSyscallArch_; 74 | }; 75 | 76 | } // namespace seccomp 77 | } // namespace s2j 78 | -------------------------------------------------------------------------------- /src/seccomp/SeccompRule.cc: -------------------------------------------------------------------------------- 1 | #include "SeccompRule.h" 2 | #include "SeccompException.h" 3 | 4 | #include 5 | 6 | namespace s2j { 7 | namespace seccomp { 8 | 9 | SeccompRule::SeccompRule( 10 | uint32_t syscallNumber, 11 | std::shared_ptr action, 12 | std::shared_ptr filter) 13 | : syscall(syscallNumber) 14 | , action(std::move(action)) 15 | , filter(std::move(filter)) {} 16 | 17 | SeccompRule::SeccompRule( 18 | const std::string& syscallName, 19 | std::shared_ptr action, 20 | std::shared_ptr filter) 21 | : SeccompRule( 22 | resolveSyscallName(syscallName), 23 | std::move(action), 24 | std::move(filter)) {} 25 | 26 | SeccompRule::SeccompRule( 27 | uint32_t syscallNumber, 28 | std::shared_ptr action) 29 | : SeccompRule( 30 | syscallNumber, 31 | std::move(action), 32 | std::make_shared()) {} 33 | 34 | SeccompRule::SeccompRule( 35 | const std::string& syscallName, 36 | std::shared_ptr action) 37 | : SeccompRule(resolveSyscallName(syscallName), std::move(action)) {} 38 | 39 | uint32_t SeccompRule::resolveSyscallName(const std::string& name) { 40 | auto syscall = seccomp_syscall_resolve_name(name.c_str()); 41 | if (syscall == __NR_SCMP_ERROR) { 42 | throw UnknownSyscallNameException(name); 43 | } 44 | return syscall; 45 | } 46 | 47 | } // namespace seccomp 48 | } // namespace s2j 49 | -------------------------------------------------------------------------------- /src/seccomp/SeccompRule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "action/SeccompAction.h" 4 | #include "filter/LibSeccompFilter.h" 5 | #include "filter/SyscallFilter.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace s2j { 12 | namespace seccomp { 13 | 14 | class SeccompRule { 15 | public: 16 | SeccompRule(const SeccompRule&) = default; 17 | SeccompRule(SeccompRule&&) = default; 18 | 19 | SeccompRule( 20 | uint32_t syscallNumber, 21 | std::shared_ptr action, 22 | std::shared_ptr filter); 23 | SeccompRule( 24 | const std::string& syscallName, 25 | std::shared_ptr action, 26 | std::shared_ptr filter); 27 | SeccompRule( 28 | uint32_t syscallNumber, 29 | std::shared_ptr action); 30 | SeccompRule( 31 | const std::string& syscallName, 32 | std::shared_ptr action); 33 | 34 | template 35 | SeccompRule( 36 | const std::string syscallName, 37 | SeccompAction&& action, 38 | SyscallFilter&& filter, 39 | typename std::enable_if::value>::type* = nullptr, 42 | typename std::enable_if::value>::type* = nullptr) 45 | : syscall(resolveSyscallName(syscallName)) 46 | , action(std::make_shared( 47 | std::forward(action))) 48 | , filter(std::make_shared( 49 | std::forward(filter))) {} 50 | 51 | template 52 | SeccompRule( 53 | const std::string syscallName, 54 | SeccompAction&& action, 55 | typename std::enable_if::value>::type* = nullptr) 58 | : syscall(resolveSyscallName(syscallName)) 59 | , action(std::make_shared( 60 | std::forward(action))) 61 | , filter(std::make_shared()) {} 62 | 63 | SeccompRule& operator=(const SeccompRule&) = default; 64 | SeccompRule& operator=(SeccompRule&&) = default; 65 | 66 | uint32_t syscall; 67 | std::shared_ptr action; 68 | std::shared_ptr filter; 69 | 70 | private: 71 | static uint32_t resolveSyscallName(const std::string& name); 72 | }; 73 | 74 | } // namespace seccomp 75 | } // namespace s2j 76 | -------------------------------------------------------------------------------- /src/seccomp/action/ActionAllow.cc: -------------------------------------------------------------------------------- 1 | #include "ActionAllow.h" 2 | 3 | #include 4 | 5 | namespace s2j { 6 | namespace seccomp { 7 | namespace action { 8 | 9 | SeccompAction::Type ActionAllow::getType() const { 10 | return SeccompAction::Type::ALLOW; 11 | } 12 | 13 | tracer::TraceAction ActionAllow::execute(tracer::Tracee& /*tracee*/) { 14 | return tracer::TraceAction::CONTINUE; 15 | } 16 | 17 | uint32_t ActionAllow::createLibSeccompAction() const { 18 | return SCMP_ACT_ALLOW; 19 | } 20 | 21 | } // namespace action 22 | } // namespace seccomp 23 | } // namespace s2j 24 | -------------------------------------------------------------------------------- /src/seccomp/action/ActionAllow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SeccompAction.h" 4 | 5 | #include 6 | 7 | namespace s2j { 8 | namespace seccomp { 9 | namespace action { 10 | 11 | class ActionAllow : public SeccompAction { 12 | public: 13 | Type getType() const override; 14 | 15 | tracer::TraceAction execute(tracer::Tracee& tracee) override; 16 | 17 | protected: 18 | virtual uint32_t createLibSeccompAction() const override; 19 | }; 20 | 21 | } // namespace action 22 | } // namespace seccomp 23 | } // namespace s2j 24 | -------------------------------------------------------------------------------- /src/seccomp/action/ActionErrno.cc: -------------------------------------------------------------------------------- 1 | #include "ActionErrno.h" 2 | 3 | #include "common/Exception.h" 4 | 5 | #include 6 | 7 | namespace s2j { 8 | namespace seccomp { 9 | namespace action { 10 | 11 | ActionErrno::ActionErrno(uint64_t errnoNumber) : errnoNumber_(errnoNumber) {} 12 | 13 | SeccompAction::Type ActionErrno::getType() const { 14 | return SeccompAction::Type::ERRNO; 15 | } 16 | 17 | tracer::TraceAction ActionErrno::execute(tracer::Tracee& tracee) { 18 | tracee.cancelSyscall(-errnoNumber_); 19 | return tracer::TraceAction::CONTINUE; 20 | } 21 | 22 | uint32_t ActionErrno::createLibSeccompAction() const { 23 | return SCMP_ACT_ERRNO(errnoNumber_); 24 | } 25 | 26 | } // namespace action 27 | } // namespace seccomp 28 | } // namespace s2j 29 | -------------------------------------------------------------------------------- /src/seccomp/action/ActionErrno.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SeccompAction.h" 4 | 5 | #include 6 | 7 | namespace s2j { 8 | namespace seccomp { 9 | namespace action { 10 | 11 | class ActionErrno : public SeccompAction { 12 | public: 13 | ActionErrno(uint64_t errnoNumber); 14 | 15 | Type getType() const override; 16 | 17 | tracer::TraceAction execute(tracer::Tracee& tracee) override; 18 | 19 | protected: 20 | virtual uint32_t createLibSeccompAction() const override; 21 | 22 | uint64_t errnoNumber_; 23 | }; 24 | 25 | } // namespace action 26 | } // namespace seccomp 27 | } // namespace s2j 28 | -------------------------------------------------------------------------------- /src/seccomp/action/ActionKill.cc: -------------------------------------------------------------------------------- 1 | #include "ActionKill.h" 2 | 3 | #include 4 | 5 | namespace s2j { 6 | namespace seccomp { 7 | namespace action { 8 | 9 | SeccompAction::Type ActionKill::getType() const { 10 | return SeccompAction::Type::KILL; 11 | } 12 | 13 | tracer::TraceAction ActionKill::execute(tracer::Tracee& /*tracee*/) { 14 | /** 15 | * Do not kill explicitly, just return TraceAction::KILL. Child will be 16 | * sacrificed by Executor. 17 | */ 18 | return tracer::TraceAction::KILL; 19 | } 20 | 21 | uint32_t ActionKill::createLibSeccompAction() const { 22 | // Trace here so that executor will display nice output. Perfomance doesn't 23 | // matter here since we are gpoing to finish anyway. 24 | return SCMP_ACT_TRACE(getRuleId()); 25 | } 26 | 27 | } // namespace action 28 | } // namespace seccomp 29 | } // namespace s2j 30 | -------------------------------------------------------------------------------- /src/seccomp/action/ActionKill.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SeccompAction.h" 4 | 5 | #include 6 | 7 | namespace s2j { 8 | namespace seccomp { 9 | namespace action { 10 | 11 | class ActionKill : public SeccompAction { 12 | public: 13 | Type getType() const override; 14 | 15 | tracer::TraceAction execute(tracer::Tracee& tracee) override; 16 | 17 | protected: 18 | virtual uint32_t createLibSeccompAction() const override; 19 | }; 20 | 21 | } // namespace action 22 | } // namespace seccomp 23 | } // namespace s2j 24 | -------------------------------------------------------------------------------- /src/seccomp/action/ActionTrace.cc: -------------------------------------------------------------------------------- 1 | #include "ActionTrace.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace s2j { 7 | namespace seccomp { 8 | namespace action { 9 | 10 | SeccompAction::Type ActionTrace::getType() const { 11 | return SeccompAction::Type::TRACE; 12 | } 13 | 14 | tracer::TraceAction ActionTrace::execute(tracer::Tracee& tracee) { 15 | return handler_(tracee); 16 | } 17 | 18 | uint32_t ActionTrace::createLibSeccompAction() const { 19 | return SCMP_ACT_TRACE(getRuleId()); 20 | } 21 | 22 | } // namespace action 23 | } // namespace seccomp 24 | } // namespace s2j 25 | -------------------------------------------------------------------------------- /src/seccomp/action/ActionTrace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SeccompAction.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace s2j { 10 | namespace seccomp { 11 | namespace action { 12 | 13 | class ActionTrace : public SeccompAction { 14 | public: 15 | template 16 | ActionTrace(Handler&& handler) : handler_(handler) {} 17 | 18 | ActionTrace() 19 | : ActionTrace([](auto& tracee) { 20 | return tracer::TraceAction::CONTINUE; 21 | }) {} 22 | 23 | Type getType() const override; 24 | 25 | virtual tracer::TraceAction execute(tracer::Tracee& tracee) override; 26 | 27 | protected: 28 | virtual uint32_t createLibSeccompAction() const override; 29 | 30 | private: 31 | std::function handler_; 32 | }; 33 | 34 | } // namespace action 35 | } // namespace seccomp 36 | } // namespace s2j 37 | -------------------------------------------------------------------------------- /src/seccomp/action/SeccompAction.cc: -------------------------------------------------------------------------------- 1 | #include "SeccompAction.h" 2 | 3 | #include 4 | 5 | namespace s2j { 6 | namespace seccomp { 7 | namespace action { 8 | 9 | SeccompAction::SeccompAction() : ruleId_(0) {} 10 | 11 | void SeccompAction::setRuleId(uint32_t groupId) { 12 | ruleId_ = groupId; 13 | } 14 | 15 | uint32_t SeccompAction::getRuleId() const { 16 | return ruleId_; 17 | } 18 | 19 | } // namespace action 20 | } // namespace seccomp 21 | 22 | std::string to_string(const seccomp::action::SeccompAction::Type type) { 23 | switch (type) { 24 | case seccomp::action::SeccompAction::Type::ALLOW: 25 | return "Allow"; 26 | case seccomp::action::SeccompAction::Type::TRACE: 27 | return "Trace"; 28 | case seccomp::action::SeccompAction::Type::ERRNO: 29 | return "Errno"; 30 | case seccomp::action::SeccompAction::Type::KILL: 31 | return "Kill"; 32 | default: 33 | return "Unknown"; 34 | } 35 | } 36 | 37 | } // namespace s2j 38 | -------------------------------------------------------------------------------- /src/seccomp/action/SeccompAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tracer/TraceAction.h" 4 | #include "tracer/Tracee.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace s2j { 10 | namespace seccomp { 11 | 12 | class SeccompContext; 13 | 14 | namespace action { 15 | 16 | class SeccompAction { 17 | public: 18 | enum class Type { ALLOW = 0, TRACE = 1, ERRNO = 2, KILL = 3 }; 19 | 20 | SeccompAction(); 21 | virtual ~SeccompAction() = default; 22 | 23 | virtual Type getType() const = 0; 24 | 25 | virtual void setRuleId(uint32_t groupId); 26 | virtual uint32_t getRuleId() const; 27 | 28 | virtual tracer::TraceAction execute(tracer::Tracee& tracee) = 0; 29 | 30 | protected: 31 | friend class s2j::seccomp::SeccompContext; 32 | 33 | virtual uint32_t createLibSeccompAction() const = 0; 34 | 35 | uint32_t ruleId_; 36 | }; 37 | 38 | } // namespace action 39 | } // namespace seccomp 40 | 41 | std::string to_string(const seccomp::action::SeccompAction::Type type); 42 | 43 | } // namespace s2j 44 | -------------------------------------------------------------------------------- /src/seccomp/filter/LibSeccompFilter.cc: -------------------------------------------------------------------------------- 1 | #include "LibSeccompFilter.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace s2j { 8 | namespace seccomp { 9 | namespace filter { 10 | 11 | bool LibSeccompFilter::isPureLibSeccompFilter() const { 12 | return true; 13 | } 14 | 15 | bool LibSeccompFilter::match( 16 | const tracer::TraceEvent& event, 17 | tracer::Tracee& tracee) const { 18 | return std::all_of( 19 | filterConditions_.begin(), 20 | filterConditions_.end(), 21 | [&](auto& filter) { return filter(event, tracee); }); 22 | } 23 | 24 | const std::vector& 25 | LibSeccompFilter::createLibSeccompFilter() const { 26 | return libSeccompFilterConditions_; 27 | } 28 | 29 | LibSeccompFilter LibSeccompFilter::operator&&( 30 | const LibSeccompFilter& filter) const { 31 | LibSeccompFilter newFilter; 32 | 33 | newFilter.filterConditions_ = filterConditions_; 34 | std::copy( 35 | filter.filterConditions_.begin(), 36 | filter.filterConditions_.end(), 37 | std::back_inserter(newFilter.filterConditions_)); 38 | 39 | newFilter.libSeccompFilterConditions_ = libSeccompFilterConditions_; 40 | std::copy( 41 | filter.libSeccompFilterConditions_.begin(), 42 | filter.libSeccompFilterConditions_.end(), 43 | std::back_inserter(newFilter.libSeccompFilterConditions_)); 44 | 45 | return newFilter; 46 | } 47 | 48 | SyscallArg::SyscallArg(uint8_t argumentIndex) : argumentIndex_(argumentIndex) {} 49 | 50 | LibSeccompFilter SyscallArg::operator==(const uint64_t data) const { 51 | return LibSeccompFilter( 52 | [index = this->argumentIndex_, data]( 53 | const tracer::TraceEvent& /* event */, 54 | tracer::Tracee& tracee) -> bool { 55 | return tracee.getSyscallArgument(index) == data; 56 | }, 57 | SCMP_CMP(argumentIndex_, SCMP_CMP_EQ, data)); 58 | } 59 | 60 | LibSeccompFilter SyscallArg::operator!=(const uint64_t data) const { 61 | return LibSeccompFilter( 62 | [index = this->argumentIndex_, data]( 63 | const tracer::TraceEvent& /* event */, 64 | tracer::Tracee& tracee) -> bool { 65 | return tracee.getSyscallArgument(index) != data; 66 | }, 67 | SCMP_CMP(argumentIndex_, SCMP_CMP_NE, data)); 68 | } 69 | 70 | LibSeccompFilter SyscallArg::operator<=(const uint64_t data) const { 71 | return LibSeccompFilter( 72 | [index = this->argumentIndex_, data]( 73 | const tracer::TraceEvent& /* event */, 74 | tracer::Tracee& tracee) -> bool { 75 | return tracee.getSyscallArgument(index) <= data; 76 | }, 77 | SCMP_CMP(argumentIndex_, SCMP_CMP_LE, data)); 78 | } 79 | 80 | LibSeccompFilter SyscallArg::operator>=(const uint64_t data) const { 81 | return LibSeccompFilter( 82 | [index = this->argumentIndex_, data]( 83 | const tracer::TraceEvent& /* event */, 84 | tracer::Tracee& tracee) -> bool { 85 | return tracee.getSyscallArgument(index) >= data; 86 | }, 87 | SCMP_CMP(argumentIndex_, SCMP_CMP_GE, data)); 88 | } 89 | 90 | LibSeccompFilter SyscallArg::operator<(const uint64_t data) const { 91 | return LibSeccompFilter( 92 | [index = this->argumentIndex_, data]( 93 | const tracer::TraceEvent& /* event */, 94 | tracer::Tracee& tracee) -> bool { 95 | return tracee.getSyscallArgument(index) < data; 96 | }, 97 | SCMP_CMP(argumentIndex_, SCMP_CMP_LT, data)); 98 | } 99 | 100 | LibSeccompFilter SyscallArg::operator>(const uint64_t data) const { 101 | return LibSeccompFilter( 102 | [index = this->argumentIndex_, data]( 103 | const tracer::TraceEvent& /* event */, 104 | tracer::Tracee& tracee) -> bool { 105 | return tracee.getSyscallArgument(index) > data; 106 | }, 107 | SCMP_CMP(argumentIndex_, SCMP_CMP_GT, data)); 108 | } 109 | 110 | MaskedSyscallArg::MaskedSyscallArg(uint8_t argumentIndex, uint64_t mask) 111 | : argumentIndex_(argumentIndex), mask_(mask) {} 112 | 113 | MaskedSyscallArg SyscallArg::operator&(const uint64_t mask) const { 114 | return MaskedSyscallArg{argumentIndex_, mask}; 115 | } 116 | 117 | LibSeccompFilter MaskedSyscallArg::operator==(const uint64_t data) const { 118 | return LibSeccompFilter( 119 | [index = this->argumentIndex_, mask = this->mask_, data]( 120 | const tracer::TraceEvent& /* event */, 121 | tracer::Tracee& tracee) -> bool { 122 | return (tracee.getSyscallArgument(index) & mask) == data; 123 | }, 124 | SCMP_CMP( 125 | argumentIndex_, 126 | SCMP_CMP_MASKED_EQ, 127 | static_cast(mask_), 128 | static_cast(data))); 129 | } 130 | 131 | } // namespace filter 132 | } // namespace seccomp 133 | } // namespace s2j 134 | -------------------------------------------------------------------------------- /src/seccomp/filter/LibSeccompFilter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SyscallFilter.h" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace s2j { 11 | namespace seccomp { 12 | namespace filter { 13 | 14 | class SyscallArg; 15 | class MaskedSyscallArg; 16 | 17 | /** 18 | * Class that represents libseccomp syscall filter. 19 | */ 20 | class LibSeccompFilter : public SyscallFilter { 21 | public: 22 | LibSeccompFilter() = default; 23 | 24 | bool isPureLibSeccompFilter() const override; 25 | 26 | bool match(const tracer::TraceEvent& event, tracer::Tracee& tracee) 27 | const override; 28 | 29 | /* Filters can be joined tohegher. */ 30 | LibSeccompFilter operator&&(const LibSeccompFilter& filter) const; 31 | 32 | protected: 33 | const std::vector& createLibSeccompFilter() 34 | const override; 35 | 36 | private: 37 | friend class SyscallArg; 38 | friend class MaskedSyscallArg; 39 | 40 | template 41 | LibSeccompFilter( 42 | SeccompFilterFunction filterCondition, 43 | struct scmp_arg_cmp libSeccompFilterCondition) 44 | : filterConditions_({std::function< 45 | bool(const tracer::TraceEvent&, tracer::Tracee&)>( 46 | filterCondition)}) 47 | , libSeccompFilterConditions_({libSeccompFilterCondition}) {} 48 | 49 | std::vector> 50 | filterConditions_; 51 | std::vector libSeccompFilterConditions_; 52 | }; 53 | 54 | /** 55 | * Helpers used for LibSeccmpFilter creation. 56 | */ 57 | class SyscallArg { 58 | public: 59 | SyscallArg(uint8_t argumentIndex); 60 | 61 | /** 62 | * Syscall arguments can be compared to produce filter. 63 | */ 64 | LibSeccompFilter operator==(const uint64_t data) const; 65 | LibSeccompFilter operator!=(const uint64_t data) const; 66 | LibSeccompFilter operator<=(const uint64_t data) const; 67 | LibSeccompFilter operator>=(const uint64_t data) const; 68 | LibSeccompFilter operator<(const uint64_t data) const; 69 | LibSeccompFilter operator>(const uint64_t data) const; 70 | 71 | MaskedSyscallArg operator&(const uint64_t mask) const; 72 | 73 | private: 74 | uint8_t argumentIndex_; 75 | }; 76 | 77 | class MaskedSyscallArg { 78 | public: 79 | MaskedSyscallArg(uint8_t argumentIndex, uint64_t mask); 80 | 81 | /** 82 | * Masked syscall argument can be compared as well. 83 | */ 84 | LibSeccompFilter operator==(const uint64_t data) const; 85 | 86 | private: 87 | uint8_t argumentIndex_; 88 | uint64_t mask_; 89 | }; 90 | 91 | } // namespace filter 92 | } // namespace seccomp 93 | } // namespace s2j 94 | -------------------------------------------------------------------------------- /src/seccomp/filter/SyscallFilter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tracer/TraceEvent.h" 4 | #include "tracer/Tracee.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace s2j { 11 | namespace seccomp { 12 | 13 | class SeccompContext; 14 | 15 | namespace filter { 16 | 17 | class SyscallFilter { 18 | public: 19 | virtual ~SyscallFilter() = default; 20 | 21 | /** 22 | * Wheather filter can be expressed with plain seccomp filters. 23 | * */ 24 | virtual bool isPureLibSeccompFilter() const = 0; 25 | 26 | /** 27 | * Checks wheather conditions in this filter match given event. 28 | */ 29 | virtual bool match(const tracer::TraceEvent& event, tracer::Tracee& tracee) 30 | const = 0; 31 | 32 | protected: 33 | friend class s2j::seccomp::SeccompContext; 34 | 35 | virtual const std::vector& createLibSeccompFilter() 36 | const = 0; 37 | }; 38 | 39 | } // namespace filter 40 | } // namespace seccomp 41 | } // namespace s2j 42 | -------------------------------------------------------------------------------- /src/seccomp/policy/DefaultPolicy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SyscallPolicy.h" 4 | #include "seccomp/SeccompRule.h" 5 | 6 | #include 7 | 8 | namespace s2j { 9 | namespace seccomp { 10 | namespace policy { 11 | 12 | /** 13 | * Class that represents generic, tweakable (TODO) syscall 14 | * policy used in sio2jail 15 | */ 16 | class DefaultPolicy : public BaseSyscallPolicy { 17 | public: 18 | DefaultPolicy(); 19 | 20 | const std::vector& getRules() const override; 21 | 22 | private: 23 | /** 24 | * Process should be capable of controlling it's exection. 25 | */ 26 | void addExecutionControlRules(bool allowFork = false); 27 | 28 | /** 29 | * Adds rules for read-only access to filesystem. 30 | */ 31 | void addFileSystemAccessRules(bool readOnly = true); 32 | 33 | /** 34 | * Adds rules for input and output 35 | */ 36 | void addInputOutputRules(); 37 | 38 | /** 39 | * Adds rules for memory management 40 | */ 41 | void addMemoryManagementRules(); 42 | 43 | /** 44 | * Adds rules that control gaining informations about system. 45 | */ 46 | void addSystemInformationRules(); 47 | 48 | /** 49 | * Allow many syscalls. 50 | */ 51 | void allowSyscalls(std::initializer_list syscalls); 52 | 53 | /** 54 | * If signal is valid for tracee to send. 55 | */ 56 | static inline bool isSignalValid(int signal) { 57 | return signal != SIGSTOP && signal != SIGKILL && signal != SIGVTALRM && 58 | signal != SIGKILL; 59 | } 60 | 61 | std::vector rules_; 62 | }; 63 | 64 | } // namespace policy 65 | } // namespace seccomp 66 | } // namespace s2j 67 | -------------------------------------------------------------------------------- /src/seccomp/policy/PermissivePolicy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "seccomp/action/ActionAllow.h" 4 | 5 | namespace s2j { 6 | namespace seccomp { 7 | namespace policy { 8 | 9 | class PermissivePolicy final : public BaseSyscallPolicy { 10 | public: 11 | PermissivePolicy() 12 | : BaseSyscallPolicy(std::make_shared()) {} 13 | 14 | const std::vector& getRules() const override { 15 | static const std::vector emptyRules_; 16 | return emptyRules_; 17 | } 18 | }; 19 | 20 | } // namespace policy 21 | } // namespace seccomp 22 | } // namespace s2j 23 | -------------------------------------------------------------------------------- /src/seccomp/policy/SyscallPolicy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "seccomp/SeccompRule.h" 4 | #include "seccomp/action/SeccompAction.h" 5 | 6 | namespace s2j { 7 | namespace seccomp { 8 | namespace policy { 9 | 10 | class SyscallPolicy { 11 | public: 12 | virtual const std::vector& getRules() const = 0; 13 | }; 14 | 15 | class BaseSyscallPolicy : public SyscallPolicy { 16 | public: 17 | BaseSyscallPolicy(std::shared_ptr defaultAction) 18 | : defaultAction_(defaultAction) {} 19 | 20 | std::shared_ptr getDefaultAction() const { 21 | return defaultAction_; 22 | } 23 | 24 | private: 25 | std::shared_ptr defaultAction_; 26 | }; 27 | 28 | } // namespace policy 29 | } // namespace seccomp 30 | } // namespace s2j 31 | -------------------------------------------------------------------------------- /src/sio2jail.cc: -------------------------------------------------------------------------------- 1 | #include "common/Exception.h" 2 | #include "s2japp/Application.h" 3 | 4 | #include 5 | 6 | int main(int argc, const char* argv[]) { 7 | try { 8 | return s2j::app::Application(argc, argv).main(); 9 | } 10 | catch (const std::exception& ex) { 11 | std::cerr << "Exception occurred: " << ex.what() << std::endl; 12 | return s2j::app::Application::ExitCode::FATAL_ERROR; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/tracer/ProcessInfo.cc: -------------------------------------------------------------------------------- 1 | #include "ProcessInfo.h" 2 | 3 | #include "common/Assert.h" 4 | 5 | namespace s2j { 6 | namespace tracer { 7 | 8 | ProcessInfo::ProcessInfo(pid_t pid, std::shared_ptr parent) 9 | : pid_{pid}, parent_{parent} { 10 | if (parent == nullptr) 11 | wholeTreeInfo_ = 12 | std::make_shared>>(); 13 | else 14 | wholeTreeInfo_ = parent->wholeTreeInfo_; 15 | } 16 | 17 | std::shared_ptr ProcessInfo::makeProcessInfo( 18 | pid_t pid, 19 | std::shared_ptr parent) { 20 | auto process = std::make_shared(pid, parent); 21 | 22 | auto selfInsert = process->wholeTreeInfo_->emplace(pid, process); 23 | assert(selfInsert.second, "pid was already present in process tree info"); 24 | 25 | return process; 26 | } 27 | 28 | ProcessInfo::~ProcessInfo() { 29 | wholeTreeInfo_->erase(pid_); 30 | } 31 | 32 | std::shared_ptr ProcessInfo::addChild(pid_t pid) { 33 | auto childInsert = children_.emplace( 34 | pid, ProcessInfo::makeProcessInfo(pid, shared_from_this())); 35 | assert(childInsert.second, "pid was already present in children_"); 36 | return childInsert.first->second; 37 | } 38 | 39 | void ProcessInfo::delChild(pid_t pid) { 40 | auto child = children_.find(pid); 41 | assert(child != children_.end()); 42 | children_.erase(child); 43 | } 44 | 45 | } // namespace tracer 46 | } // namespace s2j 47 | -------------------------------------------------------------------------------- /src/tracer/ProcessInfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace s2j { 9 | namespace tracer { 10 | 11 | class ProcessInfo : public std::enable_shared_from_this { 12 | public: 13 | // Should not be called directly, use makeProcessInfo instead 14 | ProcessInfo(pid_t pid, std::shared_ptr parent); 15 | static std::shared_ptr makeProcessInfo( 16 | pid_t pid, 17 | std::shared_ptr parent); 18 | 19 | ~ProcessInfo(); 20 | 21 | std::shared_ptr addChild(pid_t pid); 22 | void delChild(pid_t pid); 23 | 24 | pid_t getPid() const { 25 | return pid_; 26 | } 27 | 28 | std::shared_ptr getParent() const { 29 | return parent_.lock(); 30 | } 31 | 32 | std::shared_ptr getChild(pid_t pid) const { 33 | auto child = children_.find(pid); 34 | if (child == children_.end()) { 35 | return nullptr; 36 | } 37 | return child->second; 38 | } 39 | 40 | std::shared_ptr getProcess(pid_t pid) const { 41 | auto process = wholeTreeInfo_->find(pid); 42 | if (process == wholeTreeInfo_->end()) { 43 | return nullptr; 44 | } 45 | auto processInfo = process->second.lock(); 46 | if (processInfo == nullptr) { 47 | wholeTreeInfo_->erase(process); 48 | return nullptr; 49 | } 50 | return processInfo; 51 | } 52 | 53 | private: 54 | const pid_t pid_; 55 | const std::weak_ptr parent_; 56 | std::shared_ptr>> wholeTreeInfo_; 57 | std::map> children_; 58 | }; 59 | 60 | } // namespace tracer 61 | } // namespace s2j 62 | -------------------------------------------------------------------------------- /src/tracer/TraceAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace s2j { 4 | namespace tracer { 5 | 6 | enum class TraceAction { CONTINUE = 0, KILL = 1 }; 7 | 8 | } 9 | } // namespace s2j 10 | -------------------------------------------------------------------------------- /src/tracer/TraceEvent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Tracee.h" 4 | 5 | #include "executor/ExecuteEvent.h" 6 | 7 | namespace s2j { 8 | namespace tracer { 9 | 10 | struct TraceEvent { 11 | // TODO: use reference here to avoid copying 12 | executor::ExecuteEvent executeEvent; 13 | }; 14 | 15 | } // namespace tracer 16 | } // namespace s2j 17 | -------------------------------------------------------------------------------- /src/tracer/TraceEventListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TraceAction.h" 4 | #include "TraceEvent.h" 5 | 6 | #include "executor/ExecuteEventListener.h" 7 | 8 | #include 9 | 10 | namespace s2j { 11 | namespace tracer { 12 | 13 | class TraceEventListener { 14 | public: 15 | virtual ~TraceEventListener() = default; 16 | 17 | /** 18 | * Triggers in parent after child's initial execve. 19 | */ 20 | virtual TraceAction onPostExec( 21 | const tracer::TraceEvent& traceEvent, 22 | tracer::Tracee& tracee) { 23 | return TraceAction::CONTINUE; 24 | } 25 | 26 | /** 27 | * Triggers in parent after each clone. Returns pair tracee action, 28 | * tracee child action. 29 | */ 30 | virtual std::tuple onPostClone( 31 | const tracer::TraceEvent& traceEvent, 32 | tracer::Tracee& tracee, 33 | tracer::Tracee& traceeChild) { 34 | return {TraceAction::CONTINUE, TraceAction::CONTINUE}; 35 | } 36 | 37 | /** 38 | * Triggers in parent after each event 39 | */ 40 | virtual TraceAction onTraceEvent( 41 | const tracer::TraceEvent& traceEvent, 42 | tracer::Tracee& tracee) { 43 | return TraceAction::CONTINUE; 44 | } 45 | }; 46 | 47 | } // namespace tracer 48 | } // namespace s2j 49 | -------------------------------------------------------------------------------- /src/tracer/TraceExecutor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ProcessInfo.h" 4 | #include "TraceAction.h" 5 | #include "TraceEventListener.h" 6 | 7 | #include "common/EventProvider.h" 8 | #include "common/Feature.h" 9 | #include "executor/ExecuteEvent.h" 10 | #include "executor/ExecuteEventListener.h" 11 | #include "printer/OutputSource.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | namespace s2j { 21 | namespace tracer { 22 | 23 | class TraceExecutor 24 | : public virtual executor::ExecuteEventListener 25 | , public printer::OutputSource 26 | , public EventProvider { 27 | public: 28 | void onPostForkChild() override; 29 | void onPostForkParent(pid_t childPid) override; 30 | executor::ExecuteAction onExecuteEvent( 31 | const executor::ExecuteEvent& executeEvent) override; 32 | 33 | const static Feature feature; 34 | 35 | private: 36 | TraceAction onEventExec(const TraceEvent& executeEvent, Tracee& tracee); 37 | 38 | TraceAction onEventClone(const TraceEvent& executeEvent, Tracee& tracee); 39 | 40 | /* Returns action and injectedSignal */ 41 | std::tuple handleTraceeSignal( 42 | const TraceEvent& event, 43 | Tracee& tracee); 44 | 45 | void continueTracee( 46 | TraceAction action, 47 | int injectedSignal, 48 | const TraceEvent& event, 49 | Tracee& tracee); 50 | 51 | static const uint64_t PTRACE_OPTIONS; 52 | 53 | std::shared_ptr rootTraceeInfo_; 54 | bool hasExecved_{false}; 55 | }; 56 | 57 | } // namespace tracer 58 | } // namespace s2j 59 | -------------------------------------------------------------------------------- /src/tracer/Tracee.cc: -------------------------------------------------------------------------------- 1 | #include "Tracee.h" 2 | 3 | #include "common/Assert.h" 4 | #include "common/Exception.h" 5 | #include "common/WithErrnoCheck.h" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace s2j { 13 | namespace tracer { 14 | 15 | Tracee::Tracee(std::shared_ptr traceeInfo) 16 | : traceeInfo_{std::move(traceeInfo)}, syscallArch_{Arch::UNKNOWN} { 17 | assert(traceeInfo_ != nullptr); 18 | 19 | if (isAlive()) { 20 | try { 21 | withErrnoCheck( 22 | "ptrace getregs", 23 | ptrace, 24 | PTRACE_GETREGS, 25 | getPid(), 26 | nullptr, 27 | ®s_); 28 | } 29 | catch (const SystemException& e) { 30 | if (e.getErrno() != ESRCH) { 31 | throw; 32 | } 33 | } 34 | } 35 | } 36 | 37 | bool Tracee::isAlive() { 38 | return kill(getPid(), 0) == 0; 39 | } 40 | 41 | int64_t Tracee::getEventMsg() { 42 | int64_t code; 43 | withErrnoCheck( 44 | "ptrace geteventmsg", 45 | ptrace, 46 | PTRACE_GETEVENTMSG, 47 | getPid(), 48 | nullptr, 49 | &code); 50 | return code; 51 | } 52 | 53 | void Tracee::setSyscallArch(Arch arch) { 54 | syscallArch_ = arch; 55 | } 56 | 57 | Arch Tracee::getSyscallArch() const { 58 | return syscallArch_; 59 | } 60 | 61 | reg_t Tracee::getSyscallNumber() { 62 | if (syscallArch_ == Arch::UNKNOWN) { 63 | throw Exception("Can't get syscall number, unknown syscall arch"); 64 | } 65 | #if defined(__x86_64__) 66 | return regs_.orig_rax; 67 | #elif defined(__i386__) 68 | return regs_.orig_eax; 69 | #else 70 | #error "arch not supported" 71 | #endif 72 | } 73 | 74 | reg_t Tracee::getSyscallArgument(uint8_t argumentNumber) { 75 | #if defined(__x86_64__) 76 | if (syscallArch_ == Arch::X86) { 77 | switch (argumentNumber) { 78 | case 0: 79 | return static_cast(regs_.rbx); 80 | 81 | case 1: 82 | return static_cast(regs_.rcx); 83 | 84 | case 2: 85 | return static_cast(regs_.rdx); 86 | 87 | case 3: 88 | return static_cast(regs_.rsi); 89 | 90 | case 4: 91 | return static_cast(regs_.rdi); 92 | 93 | case 5: 94 | return static_cast(regs_.rbp); 95 | } 96 | } 97 | else if (syscallArch_ == Arch::X86_64) { 98 | switch (argumentNumber) { 99 | case 0: 100 | return regs_.rdi; 101 | 102 | case 1: 103 | return regs_.rsi; 104 | 105 | case 2: 106 | return regs_.rdx; 107 | 108 | case 3: 109 | return regs_.r10; 110 | 111 | case 4: 112 | return regs_.r8; 113 | 114 | case 5: 115 | return regs_.r9; 116 | } 117 | } 118 | #elif defined(__i386__) 119 | if (syscallArch_ == Arch::X86) { 120 | switch (argumentNumber) { 121 | case 0: 122 | return static_cast(regs_.ebx); 123 | 124 | case 1: 125 | return static_cast(regs_.ecx); 126 | 127 | case 2: 128 | return static_cast(regs_.edx); 129 | 130 | case 3: 131 | return static_cast(regs_.esi); 132 | 133 | case 4: 134 | return static_cast(regs_.edi); 135 | 136 | case 5: 137 | return static_cast(regs_.ebp); 138 | } 139 | } 140 | else if (syscallArch_ == Arch::X86_64) { 141 | throw s2j::AssertionException( 142 | "Tracing 64bit program from 32bit not implemented"); 143 | } 144 | #else 145 | #error "arch not supported" 146 | #endif 147 | else { 148 | throw Exception("Can't get syscall argument, unknown syscall arch"); 149 | } 150 | throw Exception( 151 | "No such syscall argument number " + 152 | std::to_string(argumentNumber)); 153 | } 154 | 155 | std::string Tracee::getMemoryString( 156 | uint64_t /*address*/, 157 | size_t /*sizeLimit*/) { 158 | NOT_IMPLEMENTED(); 159 | // This code is broken in may ways. Implement using process_vm_readv 160 | // syscall. 161 | /* 162 | for (size_t ptr = address; str.size() < sizeLimit; ptr += sizeof(int)) { 163 | int word = withErrnoCheck( 164 | "ptrace peektext", 165 | ptrace, 166 | PTRACE_PEEKTEXT, 167 | traceePid_, 168 | ptr, 169 | nullptr); 170 | for (size_t i = 0; i < sizeof(word); ++i) { 171 | char byte = (word >> (8 * i)); 172 | if (byte == '\0') 173 | return str; 174 | str += byte; 175 | } 176 | } 177 | */ 178 | } 179 | 180 | void Tracee::cancelSyscall(reg_t returnValue) { 181 | #if defined(__x86_64__) 182 | regs_.orig_rax = -1; 183 | regs_.rax = returnValue; 184 | #elif defined(__i386__) 185 | regs_.orig_eax = -1; 186 | regs_.eax = returnValue; 187 | #else 188 | #error "arch not supported" 189 | #endif 190 | withErrnoCheck( 191 | "ptrace setregs", 192 | ptrace, 193 | PTRACE_SETREGS, 194 | getPid(), 195 | nullptr, 196 | ®s_); 197 | } 198 | 199 | } // namespace tracer 200 | 201 | std::string to_string(const tracer::Arch arch) { 202 | switch (arch) { 203 | case tracer::Arch::X86: 204 | return "x86"; 205 | case tracer::Arch::X86_64: 206 | return "x86_64"; 207 | default: 208 | return "UNKNOWN"; 209 | } 210 | } 211 | 212 | } // namespace s2j 213 | -------------------------------------------------------------------------------- /src/tracer/Tracee.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ProcessInfo.h" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #if defined(__x86_64__) 12 | using reg_t = uint64_t; 13 | #elif defined(__i386__) 14 | using reg_t = uint32_t; 15 | #else 16 | #error "arch not supported" 17 | #endif 18 | 19 | namespace s2j { 20 | namespace tracer { 21 | 22 | enum Arch : uint8_t { UNKNOWN = 0, X86 = 1, X86_64 = 2 }; 23 | 24 | class Tracee { 25 | public: 26 | Tracee(std::shared_ptr traceeInfo); 27 | 28 | pid_t getPid() const { 29 | return traceeInfo_->getPid(); 30 | } 31 | 32 | std::shared_ptr getInfo() { 33 | return traceeInfo_; 34 | } 35 | 36 | /** 37 | * Checks wheather underlying process is still alive. 38 | */ 39 | bool isAlive(); 40 | 41 | /** 42 | * Returns trace event message. 43 | */ 44 | int64_t getEventMsg(); 45 | 46 | /** 47 | * Syscall related functions, will work only with seccomp listener. 48 | */ 49 | void setSyscallArch(Arch arch); 50 | Arch getSyscallArch() const; 51 | reg_t getSyscallNumber(); 52 | reg_t getSyscallArgument(uint8_t argumentNumber); 53 | 54 | void cancelSyscall(reg_t returnValue); 55 | 56 | std::string getMemoryString(uint64_t address, size_t sizeLimit = 512); 57 | 58 | // TODO: simple wrapper around ptrace syscall that simulates RW access to 59 | // traced process. getRegister getMemory getMemoryString 60 | // ... 61 | 62 | private: 63 | std::shared_ptr traceeInfo_; 64 | user_regs_struct regs_{}; 65 | 66 | Arch syscallArch_; 67 | }; 68 | 69 | } // namespace tracer 70 | 71 | std::string to_string(const tracer::Arch); 72 | 73 | } // namespace s2j 74 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ADD_SUBDIRECTORY(src) 2 | 3 | ADD_TEST( 4 | NAME 5 | python 6 | COMMAND 7 | bash -c "SIO2JAIL_BUILD_PATH='${CMAKE_BINARY_DIR}' python3 -m unittest discover -s '${CMAKE_SOURCE_DIR}/test/testsuits/' -v" 8 | WORKING_DIRECTORY 9 | "${CMAKE_SOURCE_DIR}" 10 | ) 11 | 12 | ADD_CUSTOM_TARGET(check 13 | COMMAND 14 | "${CMAKE_CTEST_COMMAND}" --verbose 15 | DEPENDS 16 | sio2jail test-binaries boxes) 17 | -------------------------------------------------------------------------------- /test/src/1-sec-prog-th.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void do1secloop(int divisor) { 7 | int i = 2; 8 | int j = i; 9 | for (; i < 500000000 / divisor; ++i) 10 | j += i; 11 | 12 | std::this_thread::sleep_for(std::chrono::milliseconds{100}); 13 | std::cout << (j - 1711656321 == 1 ? 1 : 0) << std::endl; 14 | } 15 | 16 | std::thread deepThread(int i, int threadsCount) { 17 | return std::thread{[i, threadsCount]() { 18 | if (i > 1) { 19 | auto th = deepThread(i - 1, threadsCount); 20 | do1secloop(threadsCount); 21 | th.join(); 22 | } 23 | else { 24 | do1secloop(threadsCount); 25 | } 26 | }}; 27 | } 28 | 29 | void deepTest(int threadsCount) { 30 | deepThread(threadsCount, threadsCount).join(); 31 | } 32 | 33 | void flatTest(int threadsCount) { 34 | std::vector ths; 35 | for (int i = 0; i < threadsCount; ++i) { 36 | ths.emplace_back( 37 | std::thread{[threadsCount]() { do1secloop(threadsCount); }}); 38 | } 39 | 40 | for (auto& th: ths) { 41 | th.join(); 42 | } 43 | } 44 | 45 | int main(int argc, const char* argv[]) { 46 | if (argc != 3) 47 | return 1; 48 | 49 | int threadsCount = std::stoi(argv[2]); 50 | 51 | if (std::strcmp(argv[1], "deep") == 0) { 52 | deepTest(threadsCount); 53 | } 54 | else if (std::strcmp(argv[1], "flat") == 0) { 55 | flatTest(threadsCount); 56 | } 57 | else { 58 | return EXIT_FAILURE; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/src/1-sec-prog.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int i = 2; 5 | int j = i; 6 | for(;i<500000000;++i) 7 | j += i; 8 | 9 | printf("%d\n", j-1711656321); 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /test/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | SET(CMAKE_C_FLAGS "-std=gnu99 -lm -static -O2") 2 | SET(CMAKE_C_FLAGS_DEBUG "") 3 | SET(CMAKE_C_FLAGS_RELEASE "") 4 | SET(CMAKE_CXX_FLAGS "-std=c++17 -lm -static -O2") 5 | SET(CMAKE_CXX_FLAGS_DEBUG "") 6 | SET(CMAKE_CXX_FLAGS_RELEASE "") 7 | 8 | # Time tests 9 | ADD_EXECUTABLE(1-sec-prog 1-sec-prog.c) 10 | ADD_EXECUTABLE(infinite-loop infinite-loop.c) 11 | SET_TARGET_PROPERTIES(1-sec-prog 12 | infinite-loop 13 | PROPERTIES COMPILE_FLAGS "-m32" 14 | LINK_FLAGS "-m32") 15 | 16 | ADD_EXECUTABLE(1-sec-prog-th 1-sec-prog-th.cc) 17 | SET_TARGET_PROPERTIES(1-sec-prog-th 18 | PROPERTIES COMPILE_FLAGS "-pthread -Wl,--whole-archive -lpthread -Wl,--no-whole-archive" 19 | LINK_FLAGS "-pthread -Wl,--whole-archive -lpthread -Wl,--no-whole-archive") 20 | 21 | # Memory limit tests 22 | ADD_EXECUTABLE(leak-tiny_32 leak-tiny.c) 23 | ADD_EXECUTABLE(leak-huge_32 leak-huge.c) 24 | ADD_EXECUTABLE(leak-dive_32 leak-dive.c) 25 | SET_TARGET_PROPERTIES(leak-tiny_32 26 | leak-huge_32 27 | leak-dive_32 28 | PROPERTIES COMPILE_FLAGS "-m32" 29 | LINK_FLAGS "-m32") 30 | ADD_EXECUTABLE(leak-tiny_64 leak-tiny.c) 31 | ADD_EXECUTABLE(leak-huge_64 leak-huge.c) 32 | ADD_EXECUTABLE(leak-dive_64 leak-dive.c) 33 | SET_TARGET_PROPERTIES(leak-tiny_64 34 | leak-huge_64 35 | leak-dive_64 PROPERTIES COMPILE_FLAGS "-m64" 36 | LINK_FLAGS "-m64") 37 | # Summing programs 38 | ADD_EXECUTABLE(sum_c sum_c.c) 39 | ADD_EXECUTABLE(sum_cxx sum_cxx.cc) 40 | SET_TARGET_PROPERTIES(sum_c 41 | sum_cxx PROPERTIES COMPILE_FLAGS "-m32" 42 | LINK_FLAGS "-m32") 43 | 44 | # Other 45 | ADD_EXECUTABLE(stderr-write stderr-write.c) 46 | 47 | ADD_CUSTOM_TARGET(test-binaries 48 | DEPENDS 49 | 1-sec-prog infinite-loop 1-sec-prog-th 50 | leak-tiny_32 leak-huge_32 leak-dive_32 51 | leak-tiny_64 leak-huge_64 leak-dive_64 52 | sum_c sum_cxx stderr-write) 53 | -------------------------------------------------------------------------------- /test/src/infinite-loop.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int i = 2; 5 | int j = i; 6 | for(;;++i) 7 | j += i; 8 | 9 | printf("%d\n", j-1711656321); 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /test/src/leak-dive.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int foo(long unsigned int* used) { 5 | if (*used >= 2ULL * 1024 * 1024 * 1024) 6 | return 0; 7 | 8 | *used += sizeof(int) * 1024; 9 | int tmp[1024]; 10 | 11 | return tmp[foo(used) % 1024] + tmp[1023]; 12 | } 13 | 14 | int main() { 15 | long unsigned int used = 0; 16 | int res = foo(&used); 17 | 18 | printf("FAIL used %lumb\n", used / 1024 / 1024, res); 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /test/src/leak-huge.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | int res = 0; 7 | 8 | long unsigned int used = 0; 9 | do { 10 | used += 128 * 1024 * 1024; 11 | 12 | int* tmp = malloc(128 * 1024 * 1024); 13 | if (tmp == NULL) 14 | break; 15 | 16 | res += tmp[128 * 1024 * 1024 / sizeof(int) - 1]; 17 | } while (used < 2ULL * 1024 * 1024 * 1024); 18 | 19 | printf("FAIL used %lumb\n", used / 1024 / 1024, res); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /test/src/leak-tiny.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | int res = 0; 7 | 8 | long unsigned int used = 0; 9 | do { 10 | used += 1024; 11 | 12 | int* tmp = malloc(1024); 13 | if (tmp == NULL) 14 | break; 15 | 16 | res += tmp[1024 / sizeof(int) - 1]; 17 | } while (used < 2ULL * 1024 * 1024 * 1024); 18 | 19 | printf("FAIL used %lumb\n", used / 1024 / 1024, res); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /test/src/stderr-write.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | write(2, "stderr", 6); 5 | write(1, "stdout", 6); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /test/src/sum_c.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int a, b; 5 | scanf("%d %d", &a, &b); 6 | printf("%d\n", a + b); 7 | } 8 | -------------------------------------------------------------------------------- /test/src/sum_cxx.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int a, b; 5 | std::cin >> a >> b; 6 | std::cout << a + b; 7 | } 8 | -------------------------------------------------------------------------------- /test/src/sum_python2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | 3 | print sum(map(int, raw_input().split())) 4 | -------------------------------------------------------------------------------- /test/src/sum_python3.9.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3.9 2 | 3 | import numpy 4 | 5 | print(numpy.sum(list(map(int, input().split())))) 6 | -------------------------------------------------------------------------------- /test/src/sum_python3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3.5 2 | 3 | print(sum(map(int, input().split()))) 4 | -------------------------------------------------------------------------------- /test/src/zero: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sio2project/sio2jail/deed83eb14d9f5c6e2defa4a5d3ac65c111fc574/test/src/zero -------------------------------------------------------------------------------- /test/testsuits/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sio2project/sio2jail/deed83eb14d9f5c6e2defa4a5d3ac65c111fc574/test/testsuits/__init__.py -------------------------------------------------------------------------------- /test/testsuits/base/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sio2project/sio2jail/deed83eb14d9f5c6e2defa4a5d3ac65c111fc574/test/testsuits/base/__init__.py -------------------------------------------------------------------------------- /test/testsuits/base/paths.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | BIN_PATH = os.environ.get( 4 | "SIO2JAIL_BUILD_PATH", os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../build/")) 5 | 6 | SOURCE_PATH = os.path.join( 7 | os.path.dirname(os.path.abspath(__file__)), "../../../") 8 | 9 | TEST_BIN_PATH = os.path.join( 10 | BIN_PATH, './test/src/') 11 | 12 | SIO2JAIL_BIN_PATH = os.path.join( 13 | BIN_PATH, './src/sio2jail') 14 | 15 | BOXES_PATH = os.path.join( 16 | BIN_PATH, './boxes/') 17 | -------------------------------------------------------------------------------- /test/testsuits/base/supervisor.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | from .paths import * 5 | 6 | 7 | class Supervisor(object): 8 | SUPERVISOR_BIN = None 9 | 10 | class Result(object): 11 | def __init__(self): 12 | self.supervisor_return_code = None 13 | self.return_code = None 14 | self.message = None 15 | self.memory = None 16 | self.time = None 17 | 18 | def run(self, program, stdin=None, extra_options=None): 19 | if extra_options is None: 20 | extra_options = [] 21 | 22 | if isinstance(program, (list, tuple)): 23 | options = extra_options + list(program) 24 | else: 25 | options = extra_options + [program] 26 | options = list(map(str, options)) 27 | 28 | if stdin is not None: 29 | stdin = stdin.encode('utf-8') 30 | 31 | print("running:\n{}\n".format( 32 | " ".join([self.SUPERVISOR_BIN] + options))) 33 | process = subprocess.Popen([self.SUPERVISOR_BIN] + options, 34 | stdin=subprocess.PIPE, 35 | stdout=subprocess.PIPE, 36 | stderr=subprocess.PIPE) 37 | (stdout, stderr) = process.communicate(stdin) 38 | stdout = stdout.decode('utf-8') 39 | stderr = stderr.decode('utf-8') 40 | 41 | if process.poll() is None: 42 | process.kill() 43 | process.poll() 44 | print("result: {}\n\nstdout:\n{}\nstderr:\n{}\n".format( 45 | process.poll(), stdout.strip(), stderr.strip())) 46 | 47 | result = self.Result() 48 | self.parse_results(result, stdout, stderr) 49 | result.stdout = list(map(lambda s: s.strip(), stdout.split('\n'))) 50 | result.stderr = list(map(lambda s: s.strip(), stderr.split('\n')[:-3])) 51 | result.supervisor_return_code = process.returncode 52 | return result 53 | 54 | def parse_results(self, result, stdout, stderr): 55 | raise NotImplementedError() 56 | 57 | 58 | class SIO2Jail(Supervisor): 59 | SUPERVISOR_BIN = SIO2JAIL_BIN_PATH 60 | MINIMAL_BOX_PATH = os.path.join(BOXES_PATH, './minimal/') 61 | 62 | def run(self, program, box=None, memory=None, stdin=None, extra_options=None): 63 | if extra_options is None: 64 | extra_options = [] 65 | if box is None: 66 | box = self.MINIMAL_BOX_PATH 67 | else: 68 | box = os.path.join(BOXES_PATH, box) 69 | 70 | extra_options = ['-b', box + ':/:ro'] + extra_options 71 | 72 | if memory is not None: 73 | extra_options.extend(['-m', str(memory)]) 74 | 75 | return super(SIO2Jail, self).run(program, stdin, extra_options) 76 | 77 | def parse_results(self, result, stdout, stderr): 78 | lines = [s.strip() for s in stderr.split('\n') if len(s.strip()) > 0] 79 | 80 | if len(lines) < 2: 81 | result.message = lines 82 | result.return_code = None 83 | result.memory = None 84 | result.time = None 85 | 86 | else: 87 | result.message = lines[-1] 88 | result.return_code = int(lines[-2].split()[1]) 89 | result.memory = int(lines[-2].split()[4]) 90 | result.time = int(lines[-2].split()[2]) / 1000.0 91 | -------------------------------------------------------------------------------- /test/testsuits/test_executor.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from base.supervisor import SIO2Jail 5 | from base.paths import * 6 | 7 | 8 | class TestExecutor(unittest.TestCase): 9 | STDERR_PROGRAM_PATH = os.path.join(TEST_BIN_PATH, 'stderr-write') 10 | LOOP_PROGRAM_PATH = os.path.join(TEST_BIN_PATH, 'infinite-loop') 11 | 12 | def setUp(self): 13 | self.sio2jail = SIO2Jail() 14 | 15 | def test_stderr_write(self): 16 | result = self.sio2jail.run(self.STDERR_PROGRAM_PATH) 17 | self.assertEqual(result.stdout, ['stdout']) 18 | self.assertFalse(result.stderr) 19 | 20 | def test_infinite_loop(self): 21 | options = ["--instruction-count-limit", "1g"] 22 | result = self.sio2jail.run( 23 | self.LOOP_PROGRAM_PATH, extra_options=options) 24 | self.assertAlmostEqual(result.time, 0.5) 25 | -------------------------------------------------------------------------------- /test/testsuits/test_langs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from base.supervisor import SIO2Jail 5 | from base.paths import * 6 | 7 | 8 | class TestLanguages(unittest.TestCase): 9 | C_PROGRAM_PATH = os.path.join(TEST_BIN_PATH, 'sum_c') 10 | CXX_PROGRAM_PATH = os.path.join(TEST_BIN_PATH, 'sum_cxx') 11 | PYTHON2_PROGRAM_PATH = os.path.join( 12 | SOURCE_PATH, './test/src/sum_python2.py') 13 | PYTHON3_PROGRAM_PATH = os.path.join( 14 | SOURCE_PATH, './test/src/sum_python3.py') 15 | 16 | PYTHON3_9_PROGRAM_PATH = os.path.join( 17 | SOURCE_PATH, './test/src/sum_python3.9.py') 18 | 19 | URANDOM_MOCKED_FILE = os.path.join(SOURCE_PATH, './test/src/zero') 20 | 21 | def setUp(self): 22 | self.sio2jail = SIO2Jail() 23 | 24 | def perform(self, *args, **kwargs): 25 | kwargs.update({'stdin': '18 24'}) 26 | result = self.sio2jail.run(*args, **kwargs) 27 | self.assertEqual(result.return_code, 0) 28 | self.assertEqual(result.supervisor_return_code, 0) 29 | self.assertGreater(len(result.stdout), 0) 30 | self.assertEqual(result.stdout[0], '42') 31 | 32 | def test_c(self): 33 | self.perform(self.C_PROGRAM_PATH) 34 | 35 | def test_cxx(self): 36 | self.perform(self.CXX_PROGRAM_PATH) 37 | 38 | def test_python2(self): 39 | self.perform(self.PYTHON2_PROGRAM_PATH, box='python2') 40 | 41 | def test_python3_5(self): 42 | self.perform(self.PYTHON3_PROGRAM_PATH, box='python3', 43 | extra_options=['-b', self.URANDOM_MOCKED_FILE + ':/dev/urandom:ro,dev']) 44 | 45 | def test_python3_9(self): 46 | self.perform(self.PYTHON3_9_PROGRAM_PATH, box='python3_9', 47 | extra_options=['--memory-limit', "100M"]) 48 | -------------------------------------------------------------------------------- /test/testsuits/test_memory_limit.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from base.supervisor import SIO2Jail 5 | from base.paths import * 6 | 7 | 8 | class MemoryLimitExceededMeta(type): 9 | def __new__(mcs, name, bases, members): 10 | def create_mle_test(program): 11 | def test(self): 12 | self._run_memory_test(program, 16 * self.MB, expect_mle=True) 13 | self._run_memory_test(program, self.GB, expect_mle=True) 14 | return test 15 | 16 | test_cases = [ 17 | leak_type + '_' + arch 18 | for leak_type in ['tiny', 'huge', 'dive'] 19 | for arch in ['32', '64']] 20 | 21 | for test_case in test_cases: 22 | members['test_memory_leak_' + test_case] = \ 23 | create_mle_test('leak-' + test_case) 24 | 25 | return type.__new__(mcs, name, bases, members) 26 | 27 | 28 | class TestMemoryLimit(unittest.TestCase): 29 | __metaclass__ = MemoryLimitExceededMeta 30 | 31 | MB = 1 * 1024 32 | GB = 1 * 1024 * MB 33 | 34 | def setUp(self): 35 | self.sio2jail = SIO2Jail() 36 | 37 | def _run_memory_test(self, program, memory_limit, memory_delta=1024, 38 | expected_memory=None, expect_mle=False): 39 | program = os.path.join(TEST_BIN_PATH, program) 40 | result = self.sio2jail.run(program, memory=memory_limit) 41 | 42 | self.assertEqual(result.supervisor_return_code, 0) 43 | if expect_mle: 44 | self.assertGreater(result.memory, memory_limit) 45 | self.assertEqual('memory limit exceeded', result.message) 46 | else: 47 | self.assertAlmostEqual(result.memory, expected_memory, 48 | delta=memory_delta) 49 | self.assertEqual('ok', result.message) 50 | 51 | def test_memory_result(self): 52 | self._run_memory_test('1-sec-prog', None, 1024, False) 53 | -------------------------------------------------------------------------------- /test/testsuits/test_threads_limit.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from base.supervisor import SIO2Jail 5 | from base.paths import * 6 | 7 | 8 | class TestThreadsLimit(unittest.TestCase): 9 | SEC_PROGRAM_TH_PATH = os.path.join(TEST_BIN_PATH, '1-sec-prog-th') 10 | 11 | def setUp(self): 12 | self.sio2jail = SIO2Jail() 13 | 14 | @unittest.expectedFailure 15 | def test_threads_limit_exceeded_flat_race_condition(self): 16 | result = self.sio2jail.run( 17 | [self.SEC_PROGRAM_TH_PATH, 'flat', 16], 18 | memory='1G', 19 | extra_options=['-t', 15]) 20 | self.assertEqual(result.message, 'threads limit exceeded') 21 | 22 | @unittest.expectedFailure 23 | def test_threads_limit_exceeded_deep_race_condition(self): 24 | result = self.sio2jail.run( 25 | [self.SEC_PROGRAM_TH_PATH, 'deep', 16], 26 | memory='1G', 27 | extra_options=['-t', 15]) 28 | self.assertEqual(result.message, 'threads limit exceeded') 29 | -------------------------------------------------------------------------------- /test/testsuits/test_times.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from base.supervisor import SIO2Jail 5 | from base.paths import * 6 | 7 | 8 | class TestReportedTimes(unittest.TestCase): 9 | SEC_PROGRAM_PATH = os.path.join(TEST_BIN_PATH, '1-sec-prog') 10 | SEC_PROGRAM_TH_PATH = os.path.join(TEST_BIN_PATH, '1-sec-prog-th') 11 | 12 | def setUp(self): 13 | self.sio2jail = SIO2Jail() 14 | 15 | def test_1_sec_program(self): 16 | result = self.sio2jail.run(self.SEC_PROGRAM_PATH) 17 | self.assertAlmostEqual(result.time, 1.0) 18 | 19 | def test_1_sec_program_threads_1(self): 20 | result = self.sio2jail.run( 21 | [self.SEC_PROGRAM_TH_PATH, 'flat', 1], 22 | memory='1G', 23 | extra_options=['-t', 1]) 24 | self.assertAlmostEqual(result.time, 1.0) 25 | 26 | @unittest.expectedFailure 27 | def test_1_sec_program_threads_15_flat(self): 28 | result = self.sio2jail.run( 29 | [self.SEC_PROGRAM_TH_PATH, 'flat', 15], 30 | memory='1G', 31 | extra_options=['-t', 15]) 32 | self.assertAlmostEqual(result.time, 1.0) 33 | 34 | @unittest.expectedFailure 35 | def test_1_sec_program_threads_15_deep(self): 36 | result = self.sio2jail.run( 37 | [self.SEC_PROGRAM_TH_PATH, 'deep', 15], 38 | memory='1G', 39 | extra_options=['-t', 15]) 40 | self.assertAlmostEqual(result.time, 1.0) 41 | --------------------------------------------------------------------------------