├── README.rst ├── setup.py ├── uchroot ├── README.rst ├── doc │ ├── CMakeLists.txt │ ├── todo.rst │ ├── modules.rst │ ├── index.rst │ ├── conf.py │ ├── changelog.rst │ ├── README.rst │ └── multistrap_example.rst ├── CMakeLists.txt ├── demo │ ├── base_files.patch │ ├── multistrap.patch │ ├── trusty_arm64.conf │ ├── trusty_arm64.py │ └── sources-arm64.list ├── pypi │ └── setup.py ├── dump_constants.py ├── __main__.py └── __init__.py ├── .cmake-format.py ├── .gitignore ├── .pep8 ├── cmake ├── ctest_helpers.cmake ├── doctools.cmake ├── codestyle.cmake └── pkgconfig.cmake ├── .flake8 ├── Makefile ├── CMakeLists.txt ├── doc ├── conf.py └── sphinx-static │ └── css │ └── cheshire_theme.css ├── pylintrc └── LICENSE /README.rst: -------------------------------------------------------------------------------- 1 | uchroot/doc/README.rst -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | uchroot/pypi/setup.py -------------------------------------------------------------------------------- /uchroot/README.rst: -------------------------------------------------------------------------------- 1 | doc/README.rst -------------------------------------------------------------------------------- /.cmake-format.py: -------------------------------------------------------------------------------- 1 | additional_commands = { 2 | "pkg_find": { 3 | "kwargs": { 4 | "PKG": "*" 5 | } 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /uchroot/doc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | sphinx( 2 | uchroot 3 | conf.py 4 | changelog.rst 5 | index.rst 6 | todo.rst 7 | README.rst) 8 | -------------------------------------------------------------------------------- /uchroot/doc/todo.rst: -------------------------------------------------------------------------------- 1 | ==== 2 | TODO 3 | ==== 4 | 5 | 1. make a Configuration class and member function hooks setup_fs() and 6 | teardown_fs() called upon entering the mount namespace. 7 | -------------------------------------------------------------------------------- /uchroot/doc/modules.rst: -------------------------------------------------------------------------------- 1 | uchroot package 2 | ===================== 3 | 4 | Module contents 5 | --------------- 6 | 7 | .. automodule:: uchroot 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | -------------------------------------------------------------------------------- /uchroot/doc/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: README.rst 2 | 3 | .. toctree:: 4 | :maxdepth: 2 5 | 6 | changelog 7 | todo 8 | modules 9 | 10 | 11 | 12 | Indices and tables 13 | ================== 14 | 15 | * :ref:`genindex` 16 | * :ref:`modindex` 17 | * :ref:`search` 18 | -------------------------------------------------------------------------------- /uchroot/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(uchroot_py_files # 2 | __init__.py __main__.py dump_constants.py) 3 | 4 | format_and_lint(uchroot # 5 | ${uchroot_py_files} 6 | CMakeLists.txt 7 | doc/CMakeLists.txt) 8 | 9 | add_subdirectory(doc) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.o 3 | *.so 4 | 5 | # unified repo tracking 6 | .gitu 7 | 8 | # qt creator 9 | *.qtc.* 10 | 11 | # cmake builds 12 | .build 13 | 14 | # python packaging 15 | *.egg-info/ 16 | dist/ 17 | 18 | # pyces build 19 | .pyces-out 20 | 21 | # jupyter autosaves 22 | .ipynb_checkpoints 23 | 24 | # bazel poop 25 | bazel-* 26 | 27 | # vscode poop 28 | .vscode/ipch 29 | 30 | # node package manager 31 | node_modules 32 | -------------------------------------------------------------------------------- /.pep8: -------------------------------------------------------------------------------- 1 | [pep8] 2 | indent-size=2 3 | max-line-length=80 4 | 5 | # E309: Don't add an empty line after a class declaration 6 | # E125: continuation line with same indent as next logical line 7 | # E129: visually indented line with same indent as next logical line 8 | # 9 | # NOTE(josh): E125 and E129 have false positives due to line-width=2. They 10 | # match cases for which this would only be true if line-width=4 11 | ignore=E309,E125,E129 12 | 13 | -------------------------------------------------------------------------------- /cmake/ctest_helpers.cmake: -------------------------------------------------------------------------------- 1 | # Add a target test. which depends on the program of the same name, 2 | # allows us to use the build system to run a test and build all it's 3 | # dependencies 4 | function(add_test_target test_name) 5 | add_custom_target(test.${test_name} 6 | COMMAND ${test_name} 7 | DEPENDS ${test_name} 8 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) 9 | endfunction() 10 | -------------------------------------------------------------------------------- /uchroot/demo/base_files.patch: -------------------------------------------------------------------------------- 1 | --- var/lib/dpkg/info/base-files.postinst 2016-09-07 11:48:24.997337549 -0700 2 | +++ var/lib/dpkg/info/base-files.postinst 2016-09-07 11:50:09.641334627 -0700 3 | @@ -23,6 +23,13 @@ 4 | 5 | migrate_directory() { 6 | if [ ! -L $1 ]; then 7 | + if [ ! -z "`ls -A $1/`" ]; then 8 | + for x in $1/* $1/.[!.]* $1/..?*; do 9 | + if [ -e "$x" ]; then 10 | + mv -- "$x" $2/ 11 | + fi 12 | + done 13 | + fi 14 | rmdir $1 15 | ln -s $2 $1 16 | fi 17 | -------------------------------------------------------------------------------- /uchroot/demo/multistrap.patch: -------------------------------------------------------------------------------- 1 | --- /usr/sbin/multistrap 2014-02-21 23:27:42.000000000 -0800 2 | +++ multistrap 2017-02-16 10:22:26.675399614 -0800 3 | @@ -986,7 +986,7 @@ 4 | } 5 | # reinstall set 6 | foreach my $reinst (sort @reinstall) { 7 | - system ("$str $env chroot $dir apt-get --reinstall -y $forceyes install $reinst"); 8 | + system ("$str $env chroot $dir apt-get --reinstall -y install $reinst"); 9 | } 10 | &run_native_hooks_end(sort @{$hooks{'N'}}) if (defined $hooks{'N'}); 11 | return $retval; 12 | -------------------------------------------------------------------------------- /uchroot/doc/conf.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import os 3 | 4 | this_file = os.path.realpath(__file__) 5 | this_dir = os.path.dirname(this_file) 6 | _ = os.path.dirname(this_dir) 7 | root_dir = os.path.dirname(_) 8 | 9 | with open(os.path.join(root_dir, "doc/conf.py")) as infile: 10 | exec(infile.read()) 11 | 12 | project = "uchroot" 13 | module = importlib.import_module(project) 14 | 15 | docname = project + u'doc' 16 | title = project + ' Documentation' 17 | version = module.VERSION 18 | release = module.VERSION 19 | 20 | 21 | html_static_path = [os.path.join(root_dir, "doc/sphinx-static")] -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # E111: indentation is not a multiple of four 3 | # E118: indentation is not a multiple of four (comment) 4 | # W503: line break before binary operator 5 | # W504: line break after binary operator 6 | 7 | # NOTE(josh): the following were added to pass checks for current state. 8 | # Remove them as you clean things up. 9 | # C901: xxx is too complex 10 | # E121: continuation line under-indented for hanging indent 11 | # E125: continuation line with same indent as next logical line 12 | # E126: continuation line over-indented for hanging indent 13 | # E129: visually indented line with same indent as next logical line 14 | # E501: line too long 15 | # E722: do not use bare 'except' 16 | # F401: xxx imported but unused 17 | # F403: from xxx import * used; unable to detect undefined names 18 | # F821: undefined name 'unicode' 19 | # F841: local variable '_' is assigned but never used 20 | ignore = C901,E111,E114,E121,E125,E126,E129,E501,E722,F401,F403,F821,F841,W503,W504 21 | 22 | exclude = .git,__pycache__ 23 | max-complexity = 10 24 | -------------------------------------------------------------------------------- /uchroot/pypi/setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | from setuptools import setup 3 | 4 | GITHUB_URL = 'https://github.com/cheshirekow/uchroot' 5 | 6 | VERSION = None 7 | with io.open('uchroot/__init__.py', encoding='utf-8') as infile: 8 | for line in infile: 9 | line = line.strip() 10 | if line.startswith('VERSION ='): 11 | VERSION = line.split('=', 1)[1].strip().strip("'") 12 | 13 | assert VERSION is not None 14 | 15 | with io.open('README.rst', encoding='utf8') as infile: 16 | long_description = infile.read() 17 | 18 | setup( 19 | name='uchroot', 20 | packages=['uchroot'], 21 | version=VERSION, 22 | description="chroot without root", 23 | long_description=long_description, 24 | author='Josh Bialkowski', 25 | author_email='josh.bialkowski@gmail.com', 26 | url=GITHUB_URL, 27 | download_url='{}/archive/{}.tar.gz'.format(GITHUB_URL, VERSION), 28 | keywords=['chroot', 'linux'], 29 | classifiers=[], 30 | entry_points={ 31 | 'console_scripts': ['uchroot=uchroot.__main__:main'], 32 | } 33 | ) 34 | -------------------------------------------------------------------------------- /cmake/doctools.cmake: -------------------------------------------------------------------------------- 1 | # porcelain for documentation generation tools 2 | 3 | # Run sphinx 4 | # 5 | # usage: 6 | # ~~~ 7 | # sphinx( 8 | # changlog.rst 9 | # index.rst 10 | # todo.rst 11 | # README.rst) 12 | # ~~~ 13 | 14 | function(sphinx module) 15 | set(stamp_path_ ${CMAKE_CURRENT_BINARY_DIR}/${module}_doc.stamp) 16 | add_custom_command( 17 | OUTPUT ${stamp_path_} 18 | COMMAND env PYTHONPATH=${CMAKE_SOURCE_DIR} 19 | sphinx-build -M html ${CMAKE_CURRENT_SOURCE_DIR} 20 | ${CMAKE_CURRENT_BINARY_DIR} 21 | COMMAND touch ${stamp_path_} 22 | DEPENDS ${ARGN} 23 | conf.py 24 | ${CMAKE_SOURCE_DIR}/doc/conf.py 25 | ${CMAKE_SOURCE_DIR}/doc/sphinx-static/css/cheshire_theme.css 26 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) 27 | add_custom_target(${module}-doc DEPENDS ${stamp_path_}) 28 | add_custom_target( 29 | show-${module}-doc 30 | COMMAND xdg-open ${CMAKE_CURRENT_BINARY_DIR}/html/index.html 31 | DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/cmake_format_doc.stamp 32 | ) 33 | add_dependencies(doc ${module}-doc) 34 | endfunction() 35 | -------------------------------------------------------------------------------- /uchroot/demo/trusty_arm64.conf: -------------------------------------------------------------------------------- 1 | [General] 2 | arch=arm64 3 | directory=/tmp/trusty_arm64 4 | cleanup=true 5 | noauth=true 6 | unpack=true 7 | debootstrap=Ubuntu 8 | aptsources=Ubuntu 9 | omitrequired=true 10 | 11 | [Ubuntu] 12 | packages=adduser apt base-files base-passwd bash bsdutils busybox-initramfs coreutils cpio dash debconf debianutils diffutils dpkg e2fslibs e2fsprogs findutils gcc-4.9-base grep gzip insserv iproute2 klibc-utils libacl1 libattr1 libaudit-common libaudit1 libblkid1 libbz2-1.0 libc-bin libc6 libcap2 libcgmanager0 libcomerr2 libdb5.3 libdbus-1-3 libdebconfclient0 libdrm2 libgcc1 libjson-c2 libjson0 libklibc libkmod2 liblzma5 libmount1 libncurses5 libncursesw5 libnih-dbus1 libnih1 libpam-modules libpam-modules-bin libpam-runtime libpam0g libpcre3 libplymouth2 libpng12-0 libprocps3 libselinux1 libsemanage-common libsemanage1 libsepol1 libslang2 libss2 libtinfo5 libudev1 libustr-1.0-1 libuuid1 locales login lsb-base mawk mount multiarch-support nano ncurses-base ncurses-bin passwd perl-base patch python sed sensible-utils sysv-rc sysvinit-utils tar tzdata ubuntu-keyring util-linux zlib1g 13 | 14 | source=http://www.ports.ubuntu.com/ubuntu-ports 15 | keyring=ubuntu-keyring 16 | suite=trusty 17 | components=main universe 18 | omitdebsrc=true -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Requirements (based on ubuntu 16.04) 2 | # apt-get install 3 | # python-pip 4 | # clang 5 | # clang-format 6 | # cmake 7 | # gcc 8 | # g++ 9 | # gnuradio-dev 10 | # libcurl4-openssl-dev 11 | # libfuse-dev 12 | # libgoogle-glog-dev 13 | # libmagic-dev 14 | # libudev-dev 15 | # libvulkan-dev 16 | # libx11-xcb-dev 17 | # ninja-build 18 | # 19 | # pip install --upgrade pip 20 | # pip install --user 21 | # cpplint 22 | # autopep8 23 | # file-magic 24 | # flask 25 | # oauth2client 26 | # pygerrit2 27 | # pylint 28 | # recommonmark 29 | # sphinx 30 | # sqlalchemy 31 | 32 | mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) 33 | current_dir := $(notdir $(patsubst %/,%,$(dir $(mkfile_path)))) 34 | 35 | export PYTHONPATH=$(current_dir) 36 | 37 | % : .build/cmake_clang/Makefile \ 38 | .build/cmake_gnu/Makefile 39 | @echo "" 40 | @echo "clang-build" 41 | @echo "-----------" 42 | cd .build/cmake_clang && env -u MAKELEVEL $(MAKE) $@ 43 | 44 | @echo "" 45 | @echo "GNU-build" 46 | @echo "-----------" 47 | cd .build/cmake_gnu && env -u MAKELEVEL $(MAKE) $@ 48 | 49 | all: 50 | 51 | test: all 52 | 53 | 54 | 55 | .build/cmake_clang/Makefile: 56 | @echo "" 57 | @echo "Configuring clang cmake Build" 58 | mkdir -p .build/cmake_clang 59 | cd .build/cmake_clang \ 60 | && env CC=clang CXX=clang++ cmake -DCMAKE_BUILD_TYPE=Debug ../../ 61 | touch .build/cmake_clang/CMakeCache.txt 62 | 63 | .build/cmake_gnu/Makefile: 64 | @echo "" 65 | @echo "Configuring GNU cmake Build" 66 | mkdir -p .build/cmake_gnu 67 | cd .build/cmake_gnu \ 68 | && env cmake -DCMAKE_BUILD_TYPE=Debug ../../ 69 | touch .build/cmake_gnu/CMakeCache.txt 70 | -------------------------------------------------------------------------------- /uchroot/doc/changelog.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Changelog 3 | ========= 4 | 5 | ----------- 6 | v0.1 series 7 | ----------- 8 | 9 | v0.1.4 10 | ------ 11 | 12 | * remove newlines from log messages 13 | * add inotify and signalfd apis to glibc ctypes wrapper 14 | * if binary executable is not specified but argument vector is specified, then 15 | use first element of argument vector as binary executable 16 | * if binary executable starts with forward slash, don't use use exec version 17 | that does path lookup 18 | * update dump_constants helper to output in python, json, or human format 19 | * automatically load config file from the root of the target filesystem 20 | * update documentation theme to something based on rtd 21 | 22 | v0.1.3 23 | ------ 24 | 25 | * Intercept requests for /proc mount and use the MS_REC flag on those 26 | * "allow" `setgroups` as it is now used by apt-get 27 | 28 | v0.1.2 29 | ------ 30 | 31 | * Add argparse remainder so that argv can be specified more conveniently in 32 | command line execution. 33 | * Increase information included in warning of mount failure 34 | 35 | v0.1.1 36 | ------ 37 | 38 | * Assert newuidmap helper programs exist before forking for easier debug 39 | * Add config VARDOCS, argparse helpstrings, and --dump-config command line 40 | options 41 | 42 | v0.1.0 43 | ------ 44 | 45 | Initial publication. This script has been in use in various places for 46 | around a year in various incarnations. Hopefully it is useful to others. 47 | 48 | * Enter a chroot with a user and mount namespace 49 | * Optionally bind mount filesystem components into the target fs after 50 | creating the mount namespace and before chroot. This means that these 51 | mounts are invisible outside the call to uchroot. 52 | * Optionally copy a qemu binary into the target rootfs before chrooting. 53 | This can be used to uchroot a foreign architecture rootfs. 54 | -------------------------------------------------------------------------------- /uchroot/demo/trusty_arm64.py: -------------------------------------------------------------------------------- 1 | 2 | # The directory to chroot into 3 | rootfs = "/tmp/trusty_arm64" 4 | 5 | # List of paths to bind into the new root directory. These binds are 6 | # done inside a mount namespace and will not be reflected outside 7 | # the process tree started by the script. 8 | binds = [ 9 | # "/dev/pts", 10 | "/dev/urandom", 11 | # "/etc/group", 12 | # "/etc/passwd", 13 | "/etc/resolv.conf", 14 | ] 15 | 16 | # If specified, indicates the path to a qemu instance that should be bound 17 | # into the mount namespace of the jail 18 | qemu = "/usr/bin/qemu-aarch64-static" 19 | 20 | # After entering the jail, assume this [uid, gid]. [0, 0] for root. 21 | identity = (0, 0) 22 | 23 | # uids in the namespace starting at 1 are mapped to uids outside the 24 | # namespace starting with this value and up to this many ids. Note that 25 | # the uid range outside the namespace must lie within the current users 26 | # allowed subordinate uids. See (or modify) /etc/subid for the range 27 | # available to your user. 28 | uid_range = (100000, 65536) 29 | 30 | # Aame as uid_map above, but for gids. 31 | gid_range = (100000, 65536) 32 | 33 | # Set the current working directory to this inside the jail 34 | cwd = "/" 35 | 36 | # The following variables specify what to execute after chrooting into the jail 37 | 38 | # The path of the program to execute 39 | exbin = "/bin/bash" 40 | 41 | # The argument vector to expose as argv,argc to the called process 42 | argv = ["bash"], 43 | 44 | # The environment of the called process. Use an empty dictionary for an 45 | # empty environment, or None to use the host environment. 46 | env = { 47 | # Any environment variable encountered as a list will be join()ed using 48 | # path separator (':') 49 | "PATH": [ 50 | # "/usr/local/sbin", 51 | # "/usr/local/bin", 52 | "/usr/sbin", 53 | "/usr/bin", 54 | "/sbin", 55 | "/bin" 56 | ], 57 | "DEBIAN_FRONTEND": "noninteractive", 58 | "DEBCONF_NONINTERACTIVE_SEEN": "true", 59 | "LC_ALL": "C", 60 | "LANGUAGE": "C", 61 | "LANG": "C" 62 | } 63 | -------------------------------------------------------------------------------- /uchroot/demo/sources-arm64.list: -------------------------------------------------------------------------------- 1 | # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to 2 | # newer versions of the distribution. 3 | 4 | deb http://ports.ubuntu.com/ubuntu-ports/ trusty main restricted universe multiverse 5 | deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty main restricted universe multiverse 6 | 7 | ## Major bug fix updates produced after the final release of the 8 | ## distribution. 9 | deb http://ports.ubuntu.com/ubuntu-ports/ trusty-updates main restricted universe multiverse 10 | deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-updates main restricted universe multiverse 11 | 12 | ## Uncomment the following two lines to add software from the 'universe' 13 | ## repository. 14 | ## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu 15 | ## team. Also, please note that software in universe WILL NOT receive any 16 | ## review or updates from the Ubuntu security team. 17 | # deb http://ports.ubuntu.com/ubuntu-ports/ trusty universe 18 | # deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty universe 19 | # deb http://ports.ubuntu.com/ubuntu-ports/ trusty-updates universe 20 | # deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-updates universe 21 | 22 | ## N.B. software from this repository may not have been tested as 23 | ## extensively as that contained in the main release, although it includes 24 | ## newer versions of some applications which may provide useful features. 25 | ## Also, please note that software in backports WILL NOT receive any review 26 | ## or updates from the Ubuntu security team. 27 | # deb http://ports.ubuntu.com/ubuntu-ports/ trusty-backports main restricted 28 | # deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-backports main restricted 29 | 30 | deb http://ports.ubuntu.com/ubuntu-ports/ trusty-security main restricted universe multiverse 31 | deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-security main restricted universe multiverse 32 | # deb http://ports.ubuntu.com/ubuntu-ports/ trusty-security universe 33 | # deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-security universe 34 | # deb http://ports.ubuntu.com/ubuntu-ports/ trusty-security multiverse 35 | # deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-security multiverse 36 | 37 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(cheshirekow) 3 | enable_testing() 4 | set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) 5 | 6 | include(${CMAKE_SOURCE_DIR}/cmake/codestyle.cmake) 7 | include(${CMAKE_SOURCE_DIR}/cmake/ctest_helpers.cmake) 8 | include(${CMAKE_SOURCE_DIR}/cmake/doctools.cmake) 9 | include(${CMAKE_SOURCE_DIR}/cmake/pkgconfig.cmake) 10 | 11 | find_package(Threads REQUIRED) 12 | pkg_find(PKG eigen3 13 | PKG fontconfig 14 | PKG freetype2 15 | PKG fuse 16 | PKG gnuradio-osmosdr 17 | PKG gnuradio-filter 18 | PKG libcurl 19 | PKG libglog 20 | PKG librtlsdr 21 | PKG libudev 22 | PKG vulkan 23 | PKG x11-xcb) 24 | 25 | include_directories(${CMAKE_SOURCE_DIR}) 26 | include_directories(${CMAKE_SOURCE_DIR}/third_party/googletest/include) 27 | include_directories(${CMAKE_SOURCE_DIR}/third_party/re2) 28 | include_directories(${CMAKE_SOURCE_DIR}/third_party/fmt/include) 29 | include_directories(${CMAKE_SOURCE_DIR}/third_party/glm) 30 | 31 | set(CXX_STANDARD "c++11" CACHE STRING "argument to '-std=' for C++ compiler") 32 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=${CXX_STANDARD}") 33 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic") 34 | 35 | add_custom_target(format) 36 | add_custom_target(lint) 37 | 38 | add_custom_target(doc) 39 | 40 | set(ENV{PYTHONPATH} ${CMAKE_SOURCE_DIR}) 41 | set(CTEST_ENVIRONMENT "PYTHONPATH=${CMAKE_SOURCE_DIR}") 42 | 43 | set_property(GLOBAL PROPERTY gloal_doc_files "") 44 | 45 | # NOTE(josh): search through the list of child directories and add any that 46 | # actually contain a listfile. While globs are evil, this is necessary for 47 | # sparse checkouts. We can and should correctly add dependencies for this glob 48 | # in order to retrigger cmake. 49 | file(GLOB children RELATIVE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/*) 50 | foreach(child ${children}) 51 | if(EXISTS ${CMAKE_SOURCE_DIR}/${child}/CMakeLists.txt) 52 | message("Enabling subdirectory '${child}' for this checkout") 53 | add_subdirectory(${child}) 54 | endif() 55 | endforeach() 56 | 57 | # NOTE(josh): some sparse checkouts don't include doxygen 58 | if(EXISTS ${CMAKE_SOURCE_DIR}/doxy.config.in) 59 | # configure the doxygen configuration 60 | # NOTE(josh): maybe want to expose this for editor integration 61 | # ~~~ 62 | # set(DOXY_WARN_FORMAT "\"$file($line) : $text \"") 63 | # set(DOXY_WARN_FORMAT "\"$file:$line: $text \"") 64 | # ~~~ 65 | configure_file("${PROJECT_SOURCE_DIR}/doxy.config.in" 66 | "${PROJECT_BINARY_DIR}/doxy.config") 67 | 68 | add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/doxy.stamp 69 | COMMAND doxygen ${PROJECT_BINARY_DIR}/doxy.config 70 | COMMAND touch ${PROJECT_BINARY_DIR}/doxy.stamp 71 | DEPENDS ${PROJECT_BINARY_DIR}/doxy.config 72 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) 73 | 74 | add_custom_target(doxygen DEPENDS ${PROJECT_BINARY_DIR}/doxy.stamp) 75 | add_dependencies(doc doxygen) 76 | endif() 77 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import sphinx_rtd_theme 4 | from recommonmark.parser import CommonMarkParser 5 | from recommonmark.transform import AutoStructify 6 | 7 | filepath = os.path.realpath(__file__) 8 | if os.path.islink(filepath): 9 | dirname = os.path.dirname(filepath) 10 | _, project = os.path.split(dirname) 11 | else: 12 | project = 'tangentsky' 13 | 14 | # General information about the project. 15 | docname = project + u'doc' 16 | title = project + ' Documentation' 17 | copyright = u'2018-2019, Josh Bialkowski' # pylint: disable=W0622 18 | author = u'Josh Bialkowski' 19 | 20 | # If your documentation needs a minimal Sphinx version, state it here. 21 | # needs_sphinx = '1.0' 22 | 23 | extensions = [ 24 | 'sphinx.ext.autodoc', 25 | 'sphinx.ext.intersphinx', 26 | 'sphinx.ext.todo', 27 | 'sphinx.ext.coverage', 28 | 'sphinx.ext.mathjax', 29 | 'sphinx.ext.viewcode' 30 | ] 31 | mathjax_path = ( 32 | "https://cdnjs.cloudflare.com/ajax/libs/mathjax/" 33 | "2.7.5/MathJax.js?config=TeX-MML-AM_CHTML") 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = [] 37 | 38 | source_parsers = { 39 | '.md': CommonMarkParser, 40 | } 41 | source_suffix = ['.rst', '.md'] 42 | master_doc = 'index' 43 | 44 | version = u'0.1.0' 45 | release = u'0.1.0' 46 | language = None 47 | exclude_patterns = [ 48 | '.sphinx_build', 49 | 'Thumbs.db', 50 | '.DS_Store' 51 | ] 52 | 53 | pygments_style = 'sphinx' 54 | todo_include_todos = True 55 | 56 | html_theme = 'sphinx_rtd_theme' 57 | # html_theme = "alabaster" 58 | numfig = True 59 | math_numfig = True 60 | math_number_all = True 61 | math_eqref_format = "Eq. {number}" 62 | 63 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 64 | html_theme_options = { 65 | "collapse_navigation": True, 66 | "display_version": True, 67 | "sticky_navigation": True, 68 | } 69 | 70 | html_static_path = ["sphinx-static"] 71 | html_style = "css/cheshire_theme.css" 72 | 73 | # Custom sidebar templates, must be a dictionary that maps document names 74 | # to template names. 75 | # This is required for the alabaster theme 76 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 77 | html_sidebars = { 78 | '**': [ 79 | 'globaltoc.html', # NOTE(josh): added 80 | 'relations.html', # needs 'show_related': True theme option to display 81 | 'searchbox.html', 82 | ] 83 | } 84 | 85 | htmlhelp_basename = project + 'doc' 86 | 87 | latex_elements = { 88 | # The paper size ('letterpaper' or 'a4paper'). 89 | # 'papersize': 'letterpaper', 90 | # The font size ('10pt', '11pt' or '12pt'). 91 | # 'pointsize': '10pt', 92 | # Additional stuff for the LaTeX preamble. 93 | # 'preamble': '', 94 | # Latex figure (float) alignment 95 | # 'figure_align': 'htbp', 96 | } 97 | 98 | latex_documents = [ 99 | (master_doc, project + '.tex', title, author, 'manual'), 100 | ] 101 | 102 | man_pages = [ 103 | (master_doc, project, title, 104 | [author], 1) 105 | ] 106 | 107 | texinfo_documents = [ 108 | (master_doc, project, title, 109 | author, project, 'One line description of project.', 110 | 'Miscellaneous'), 111 | ] 112 | 113 | intersphinx_mapping = {'https://docs.python.org/': None} 114 | 115 | # Advanced markdown 116 | def setup(app): 117 | app.add_config_value('recommonmark_config', { 118 | 'auto_code_block': True, 119 | # 'url_resolver': lambda url: github_doc_root + url, 120 | 'auto_toc_tree_section': 'Contents', 121 | 'enable_auto_toc_tree': True, 122 | 'enable_eval_rst': True, 123 | }, True) 124 | app.add_transform(AutoStructify) 125 | -------------------------------------------------------------------------------- /uchroot/dump_constants.py: -------------------------------------------------------------------------------- 1 | """ 2 | Compile and execute a small program to get the value of certain glibc 3 | constants. 4 | """ 5 | 6 | import json 7 | import logging 8 | import os 9 | import subprocess 10 | import sys 11 | import tempfile 12 | 13 | # A very simple c-program to print the value of certain glibc 14 | # constants for the current system. 15 | GET_CONSTANTS_PROGRAM = r""" 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #define PRINT_CONST(X) printf(" \"%s\" : \"0x%x\",\n", #X, X) 24 | 25 | 26 | 27 | 28 | int main(int argc, char** argv) { 29 | printf("{\n"); 30 | PRINT_CONST(CLONE_NEWUSER); 31 | PRINT_CONST(CLONE_NEWNS); 32 | PRINT_CONST(IN_NONBLOCK); 33 | PRINT_CONST(IN_CLOEXEC); 34 | 35 | PRINT_CONST(IN_ACCESS); 36 | PRINT_CONST(IN_ATTRIB); 37 | PRINT_CONST(IN_CLOSE_WRITE); 38 | PRINT_CONST(IN_CLOSE_NOWRITE ); 39 | PRINT_CONST(IN_CREATE); 40 | PRINT_CONST(IN_DELETE); 41 | PRINT_CONST(IN_DELETE_SELF); 42 | PRINT_CONST(IN_MODIFY); 43 | PRINT_CONST(IN_MOVE_SELF); 44 | PRINT_CONST(IN_MOVED_FROM); 45 | PRINT_CONST(IN_MOVED_TO); 46 | PRINT_CONST(IN_OPEN); 47 | 48 | PRINT_CONST(MS_BIND); 49 | PRINT_CONST(MS_REC); 50 | 51 | PRINT_CONST(SFD_NONBLOCK); 52 | PRINT_CONST(SFD_CLOEXEC); 53 | 54 | PRINT_CONST(SIG_BLOCK); 55 | PRINT_CONST(SIG_UNBLOCK); 56 | PRINT_CONST(SIG_SETMASK); 57 | 58 | printf(" \"dummy\" : 0\n"); 59 | printf("}\n"); 60 | } 61 | """ 62 | 63 | 64 | def get_constants(): 65 | """ 66 | Write out the source for, compile, and run a simple c-program that prints 67 | the value of needed glibc constants. Read the output of that program and 68 | store the value of constants in a Constants object. Return that object. 69 | """ 70 | with tempfile.NamedTemporaryFile(mode='wb', prefix='print_constants', 71 | suffix='.cc', delete=False) as outfile: 72 | src_path = outfile.name 73 | outfile.write(GET_CONSTANTS_PROGRAM) 74 | 75 | with tempfile.NamedTemporaryFile(mode='wb', prefix='print_constants', 76 | suffix='.cc', delete=False) as binfile: 77 | bin_path = binfile.name 78 | 79 | try: 80 | os.remove(bin_path) 81 | subprocess.check_call(['gcc', '-o', bin_path, src_path]) 82 | os.remove(src_path) 83 | 84 | constants_str = subprocess.check_output([bin_path]) 85 | os.remove(bin_path) 86 | result = json.loads(constants_str) 87 | result.pop('dummy') 88 | 89 | return result 90 | except subprocess.CalledProcessError: 91 | logging.warning('Failed to compile/execute program to get glibc constants.' 92 | ' Using baked-in values.') 93 | 94 | 95 | def dump_constants(outfile, which_format): 96 | constants = get_constants() 97 | if which_format == "json": 98 | json.dump(constants, outfile, 99 | indent=2, sort_keys=True) 100 | elif which_format == "glibc": 101 | for key, value in sorted(constants.items()): 102 | sys.stdout.write("glibc.{} = {}\n".format(key, value)) 103 | else: 104 | for key, value in sorted(constants.items()): 105 | sys.stdout.write("{} = {}\n".format(key, value)) 106 | 107 | outfile.write('\n') 108 | 109 | 110 | def main(): 111 | import argparse 112 | parser = argparse.ArgumentParser(description=__doc__) 113 | parser.add_argument("-f", "--format", choices=["glibc", "python", "json"], 114 | default="python") 115 | args = parser.parse_args() 116 | dump_constants(sys.stdout, args.format) 117 | 118 | 119 | if __name__ == '__main__': 120 | main() 121 | -------------------------------------------------------------------------------- /doc/sphinx-static/css/cheshire_theme.css: -------------------------------------------------------------------------------- 1 | @import url("theme.css"); 2 | @import url("../pygments.css"); 3 | body { 4 | font-family: "Lato","proxima-nova","Helvetica Neue",Arial,sans-serif; 5 | } 6 | 7 | h1, h2, .rst-content .toctree-wrapper p.caption, h3, h4, h5, h6, legend, p.caption { 8 | font-family: "Lato","proxima-nova","Helvetica Neue",Arial,sans-serif; 9 | } 10 | 11 | /* Use white for navigation background */ 12 | /* navigation background */ 13 | .wy-nav-side { 14 | background-color: #fff; 15 | } 16 | /* search area background color */ 17 | .wy-side-nav-search { 18 | background-color: #fff; 19 | } 20 | /* submenu background color */ 21 | .wy-nav-content-wrap, .wy-menu li.current > a { 22 | background-color: #fff; 23 | } 24 | /* responsive (mobile) titlebar color */ 25 | @media screen and (min-width: 1400px) { 26 | .wy-nav-content { 27 | background-color: #fff; 28 | } 29 | } 30 | 31 | /* Mobile title bar */ 32 | .wy-nav-top { 33 | background-color: #fff; 34 | background-repeat: no-repeat; 35 | background-position: center; 36 | padding: 0; 37 | margin: 0.4045em 0.809em; 38 | color: #333; 39 | } 40 | .wy-nav-top > a { 41 | display: none; 42 | } 43 | 44 | /* shrink the logo image */ 45 | @media screen and (max-width: 768px) { 46 | .wy-side-nav-search>a img.logo { 47 | height: 60px; 48 | } 49 | } 50 | 51 | /* This is needed to ensure that logo above search scales properly */ 52 | .wy-side-nav-search a { 53 | display: block; 54 | } 55 | 56 | .wy-side-nav-search input[type="text"] { 57 | border-color: #007eb9; 58 | } 59 | 60 | 61 | /* This ensures that multiple constructors will remain in separate lines. */ 62 | .rst-content dl:not(.docutils) dt { 63 | display: table; 64 | color: rgba(0, 102, 150, 0.9); 65 | background-color: rgba(0, 111, 185, 0.15); 66 | border-top-color: rgba(0, 82, 150, 0.9); 67 | } 68 | 69 | /* Soften the inline literal color to something a little less angry-red. 70 | This is the same saturation/lightness but the hue is more orange. */ 71 | .rst-content tt.literal, .rst-content tt.literal, .rst-content code.literal { 72 | color: rgb(231, 123, 60) !important; 73 | } 74 | 75 | .rst-content tt.xref, a .rst-content tt, .rst-content tt.xref, 76 | .rst-content code.xref, a .rst-content tt, a .rst-content code { 77 | color: #fff; 78 | } 79 | 80 | /* Soften the code blocks background color so it's not quite so harsh against 81 | the white canvas */ 82 | .rst-content div.highlight { 83 | background-color: rgb(253, 255, 226); 84 | } 85 | 86 | /* Change link colors (except for the menu) */ 87 | 88 | a { 89 | color: #007eb9; 90 | } 91 | 92 | a:hover { 93 | color: #0094b9; 94 | } 95 | 96 | a:visited { 97 | color: #0066b9; 98 | } 99 | 100 | .wy-menu a { 101 | color: #838383; 102 | } 103 | 104 | .wy-menu a:hover { 105 | color: #b3b3b3; 106 | } 107 | 108 | /* Default footer text is quite big */ 109 | footer { 110 | font-size: 80%; 111 | } 112 | 113 | footer .rst-footer-buttons { 114 | font-size: 125%; /* revert footer settings - 1/80% = 125% */ 115 | } 116 | 117 | footer p { 118 | font-size: 100%; 119 | } 120 | 121 | /* For hidden headers that appear in TOC tree */ 122 | /* see http://stackoverflow.com/a/32363545/3343043 */ 123 | .rst-content .hidden-section { 124 | display: none; 125 | } 126 | 127 | nav .hidden-section { 128 | display: inherit; 129 | } 130 | 131 | .wy-side-nav-search>div.version { 132 | color: #000; 133 | } 134 | 135 | /* Provides nice properties to make screenshots stand out clearly. */ 136 | .screenshot { 137 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); 138 | } 139 | 140 | span.eqno { 141 | float: right; 142 | } 143 | 144 | /* NOTE(josh): I added the following to style the epitaph admonition */ 145 | blockquote.epigraph { 146 | background-color: rgb(206, 233, 255); 147 | } 148 | 149 | .epigraph .admonition-title { 150 | background-color: rgb(101, 163, 204); 151 | margin: 0px; 152 | } 153 | 154 | .epigraph .attribution { 155 | background-color: rgb(180, 210, 228); 156 | } 157 | 158 | /* I guess the RTD theme sets this to `nowrap`... 159 | but I think that I definately want it to wrap by default. */ 160 | .wy-table-responsive table td { 161 | /* !important prevents the common CSS stylesheets from overriding 162 | this as on RTD they are loaded after this stylesheet */ 163 | white-space: normal !important; 164 | } 165 | -------------------------------------------------------------------------------- /uchroot/doc/README.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | uchroot 3 | ======= 4 | 5 | Chroot without root priviledges. 6 | 7 | ``uchroot.py`` uses linux user namespaces and mount namespaces to create 8 | chroot jails without root. It's not entirely a no-root solution because it 9 | requires the newuidmap and newgidmap set-uid-root helper functions (on ubuntu, 10 | installed with the uidmap package). 11 | 12 | This requirement is not really necessary if you only need to enter the chroot 13 | jail with a single user id mapped. 14 | 15 | ------------ 16 | Requirements 17 | ------------ 18 | 19 | Requires a linux built with user namespaces enabled (note that red hat does 20 | not by default) and the ``newuidmap`` setuid helper programs (install the 21 | ``newuidmap`` ubuntu package). 22 | 23 | To check if your kernel is built with user namespaces, on ubuntu:: 24 | 25 | ~$ cat /boot/config-`uname -r` | grep CONFIG_USER_NS 26 | CONFIG_USER_NS=y 27 | 28 | On other linuxes, perhaps try:: 29 | 30 | ~$ zcat /proc/config.gz | grep CONFIG_USER_NS 31 | 32 | ----- 33 | Usage 34 | ----- 35 | 36 | :: 37 | 38 | usage: uchroot [-h] [-v] [-l {debug,info,warning,error}] [-s] [-c CONFIG] 39 | [rootfs] 40 | 41 | Chroot without root priviledges This is a pretty simple process spawner that 42 | automates the construction of user and mount namespaces in order to create 43 | chroot jails without root. It's not entirely a no-root solution because it 44 | requires the newuidmap and newgidmap set-uid-root helper functions (on ubuntu, 45 | installed with the uidmap package). This requirement is not necessary if you 46 | only need to enter the chroot jail with a single user id mapped. 47 | 48 | positional arguments: 49 | rootfs path of the rootfs to enter 50 | 51 | optional arguments: 52 | -h, --help show this help message and exit 53 | -v, --version show program's version number and exit 54 | -l {debug,info,warning,error}, --log-level {debug,info,warning,error} 55 | Set the verbosity of messages 56 | -s, --subprocess use subprocess instead of exec 57 | -c CONFIG, --config CONFIG 58 | Path to config file 59 | --argv [ARGV [ARGV ...]] 60 | --cwd CWD 61 | --binds [BINDS [BINDS ...]] 62 | --gid-range [GID_RANGE [GID_RANGE ...]] 63 | --exbin EXBIN 64 | --qemu QEMU 65 | --uid-range [UID_RANGE [UID_RANGE ...]] 66 | --identity [IDENTITY [IDENTITY ...]] 67 | 68 | Advanced configurations can be specified with a configuration file in python 69 | format. Command line arguments override options specified in a configuration 70 | file:: 71 | 72 | # The directory to chroot into 73 | rootfs = "/tmp/rootfs" 74 | 75 | # List of paths to bind into the new root directory. These binds are 76 | # done inside a mount namespace and will not be reflected outside 77 | # the process tree started by the script. 78 | binds = [ 79 | "/dev/urandom", 80 | "/etc/resolv.conf", 81 | ] 82 | 83 | # If specified, indicates the path to a qemu instance that should be bound 84 | # into the mount namespace of the jail 85 | qemu = "/usr/bin/qemu-aarch64-static" 86 | 87 | # After entering the jail, assume this [uid, gid]. [0, 0] for root. 88 | identity = (0, 0) 89 | 90 | # uids in the namespace starting at 1 are mapped to uids outside the 91 | # namespace starting with this value and up to this many ids. Note that 92 | # the uid range outside the namespace must lie within the current users 93 | # allowed subordinate uids. See (or modify) /etc/subid for the range 94 | # available to your user. 95 | uid_range = (100000, 65536) 96 | 97 | # Same as uid_map above, but for gids. 98 | gid_range = (100000, 65536) 99 | 100 | # Set the current working directory to this inside the jail 101 | cwd = "/" 102 | 103 | # The following variables specify what to execute after chrooting into the jail 104 | # ----------------------------------------------------------------------------- 105 | 106 | # The path of the program to execute 107 | exbin = "/bin/bash" 108 | 109 | # The argument vector to expose as argv,argc to the called process 110 | argv = ["bash"], 111 | 112 | # The environment of the called process. Use an empty dictionary for an 113 | # empty environment, or None to use the host environment. 114 | env = { 115 | # Any environment variable encountered as a list will be join()ed using 116 | # path separator (':') 117 | "PATH": [ 118 | # "/usr/local/sbin", 119 | # "/usr/local/bin", 120 | "/usr/sbin", 121 | "/usr/bin", 122 | "/sbin", 123 | "/bin" 124 | ], 125 | "DEBIAN_FRONTEND": "noninteractive", 126 | "DEBCONF_NONINTERACTIVE_SEEN": "true", 127 | "LC_ALL": "C", 128 | "LANGUAGE": "C", 129 | "LANG": "C" 130 | } 131 | -------------------------------------------------------------------------------- /cmake/codestyle.cmake: -------------------------------------------------------------------------------- 1 | # porcelain for format and lint rules 2 | 3 | # Create format and lint rules for module files 4 | # 5 | # usage: 6 | # ~~~ 7 | # format_and_lint(module 8 | # bar.h bar.cc 9 | # CMAKE CMakeLists.txt test/CMakeLists.txt 10 | # CC foo.h foo.cc 11 | # PY foo.py) 12 | # ~~~ 13 | 14 | # Will create rules `${module}_lint` and `${module}_format` using the standard 15 | # code formatters and lint checkers for the appropriate language. These 16 | # tools are: 17 | # 18 | # ~~~ 19 | # CMAKE: 20 | # formatter: cmake-format 21 | # 22 | # CPP: 23 | # formatter: clang-format 24 | # linter: cpplint 25 | # 26 | # PYTHON: 27 | # formatter: autopep8 28 | # linter: pylint 29 | # ~~~ 30 | function(format_and_lint module) 31 | set(cmake_files_) 32 | set(cc_files_) 33 | set(py_files_) 34 | set(js_files_) 35 | set(unknown_files_) 36 | set(state_ "AUTO") 37 | 38 | foreach(arg ${ARGN}) 39 | # assign by filename 40 | if(state_ STREQUAL "AUTO") 41 | if(arg STREQUAL "CMAKE" OR arg STREQUAL "CC" OR arg STREQUAL "PY") 42 | set(state_ SPECIFIC) 43 | string(TOLOWER ${arg} typename_) 44 | set(active_list_ ${typename}_files_) 45 | else() 46 | if(arg MATCHES ".*\.cmake" OR arg MATCHES ".*CMakeLists.txt") 47 | list(APPEND cmake_files_ ${arg}) 48 | elseif(arg MATCHES ".*\.py") 49 | list(APPEND py_files_ ${arg}) 50 | elseif(arg MATCHES ".*\.(cc|h)") 51 | list(APPEND cc_files_ ${arg}) 52 | elseif(arg MATCHES ".*\.js(\.tpl)?") 53 | list(APPEND js_files_ ${arg}) 54 | else() 55 | list(APPEND unknown_files_ ${arg}) 56 | endif() 57 | endif() 58 | elseif(state_ STREQUAL "SPECIFIC") 59 | if(arg STREQUAL "CMAKE" OR arg STREQUAL "CC" OR arg STREQUAL "PY") 60 | string(TOLOWER ${arg} typename_) 61 | set(active_list_ ${typename}_files_) 62 | else() 63 | list(APPEND ${active_list_} ${arg}) 64 | endif() 65 | endif() 66 | endforeach() 67 | 68 | set(fmtcmds_) 69 | set(depfiles_) 70 | if(cmake_files_) 71 | list(APPEND fmtcmds_ COMMAND env PYTHONPATH=${CMAKE_SOURCE_DIR} 72 | python -Bm cmake_format -i ${cmake_files_}) 73 | list(APPEND depfiles_ ${cmake_files_} 74 | ${CMAKE_SOURCE_DIR}/.cmake-format.py) 75 | endif() 76 | if(cc_files_) 77 | list(APPEND fmtcmds_ COMMAND clang-format-6.0 -style file -i ${cc_files_}) 78 | list(APPEND lntcmds_ 79 | COMMAND clang-tidy-6.0 -p ${CMAKE_BINARY_DIR} ${cc_files_}) 80 | list(APPEND lntcmds_ COMMAND cpplint ${cc_files_}) 81 | list(APPEND depfiles_ ${cc_files_} 82 | ${CMAKE_SOURCE_DIR}/.clang-format 83 | ${CMAKE_SOURCE_DIR}/CPPLINT.cfg) 84 | endif() 85 | if(py_files_) 86 | list(APPEND fmtcmds_ COMMAND autopep8 -i ${py_files_}) 87 | list(APPEND lntcmds_ COMMAND env PYTHONPATH=${CMAKE_SOURCE_DIR} 88 | pylint ${py_files_}) 89 | # NOTE(josh): flake8 tries to use semaphores which fail in our containers 90 | # https://bugs.python.org/issue3770 (probably due to /proc/shmem or 91 | # something not being mounted) 92 | list(APPEND lntcmds_ COMMAND env PYTHONPATH=${CMAKE_SOURCE_DIR} 93 | flake8 --jobs 1 ${py_files_}) 94 | list(APPEND depfiles_ ${py_files_} 95 | ${CMAKE_SOURCE_DIR}/.flake8 96 | ${CMAKE_SOURCE_DIR}/.pep8 97 | ${CMAKE_SOURCE_DIR}/pylintrc) 98 | endif() 99 | if(js_files_) 100 | # list(APPEND fmtcmds_ COMMAND esformat ${js_files_}) 101 | list(APPEND lntcmds_ COMMAND eslint ${js_files_}) 102 | list(APPEND depfiles_ ${js_files_} 103 | ${CMAKE_SOURCE_DIR}/.eslintrc.js) 104 | endif() 105 | 106 | set(fmtstamp_ ${CMAKE_CURRENT_BINARY_DIR}/${module}_format.stamp) 107 | add_custom_command(OUTPUT ${fmtstamp_} 108 | ${fmtcmds_} 109 | COMMAND touch ${fmtstamp_} 110 | DEPENDS ${depfiles_} 111 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 112 | add_custom_target(${module}_format DEPENDS ${fmtstamp_}) 113 | add_dependencies(format ${module}_format) 114 | 115 | set(lntstamp_ ${CMAKE_CURRENT_BINARY_DIR}/${module}_lint.stamp) 116 | add_custom_command(OUTPUT ${lntstamp_} 117 | ${lntcmds_} 118 | COMMAND touch ${lntstamp_} 119 | DEPENDS ${depfiles_} 120 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 121 | add_custom_target(${module}_lint DEPENDS ${lntstamp_}) 122 | add_dependencies(lint ${module}_lint) 123 | 124 | if(unknown_files_) 125 | string(REPLACE ";" "\n " filelist_ "${unknown_files_}") 126 | message(WARNING 127 | "The following files will not be linted/formatted because their" 128 | " extension is not recognized: \n ${filelist_}") 129 | endif() 130 | endfunction() 131 | -------------------------------------------------------------------------------- /uchroot/__main__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chroot without root priviledges 3 | 4 | This is a pretty simple process spawner that automates the construction of 5 | user and mount namespaces in order to create chroot jails without root. It's not 6 | entirely a no-root solution because it requires the newuidmap and newgidmap 7 | set-uid-root helper functions (on ubuntu, installed with the uidmap package). 8 | This requirement is not necessary if you only need to enter the chroot 9 | jail with a single user id mapped. 10 | """ 11 | 12 | import argparse 13 | import inspect 14 | import io 15 | import logging 16 | import os 17 | import sys 18 | 19 | import uchroot 20 | 21 | logger = logging.getLogger(__name__) 22 | 23 | if sys.version_info >= (3, 0, 0): 24 | STRING_TYPES = (str,) 25 | else: 26 | STRING_TYPES = (str, unicode) 27 | 28 | 29 | def parse_bool(string): 30 | if string.lower() in ('y', 'yes', 't', 'true', '1', 'yup', 'yeah', 'yada'): 31 | return True 32 | if string.lower() in ('n', 'no', 'f', 'false', '0', 'nope', 'nah', 'nada'): 33 | return False 34 | 35 | logger.warning("Ambiguous truthiness of string '%s' evalutes to 'FALSE'", 36 | string) 37 | return False 38 | 39 | 40 | def setup_parser(parser, config): 41 | """ 42 | Configure argparse object 43 | """ 44 | parser.add_argument('-v', '--version', action='version', 45 | version=uchroot.VERSION) 46 | parser.add_argument('-l', '--log-level', default='info', 47 | choices=['debug', 'info', 'warning', 'error'], 48 | help='Set the verbosity of messages') 49 | parser.add_argument('-s', '--subprocess', action='store_true', 50 | help='use subprocess instead of exec') 51 | parser.add_argument('-c', '--config', help='Path to config file') 52 | parser.add_argument('--dump-config', action='store_true', 53 | help='Dump default config and exit') 54 | 55 | for key, value in config.items(): 56 | helpstr = uchroot.VARDOCS.get(key, None) 57 | if key == 'rootfs': 58 | continue 59 | # NOTE(josh): argparse store_true isn't what we want here because we want 60 | # to distinguish between "not specified" = "default" and "specified" 61 | elif isinstance(value, bool): 62 | parser.add_argument('--' + key.replace('_', '-'), nargs='?', default=None, 63 | const=True, type=parse_bool, help=helpstr) 64 | elif isinstance(value, STRING_TYPES + (int, float)) or value is None: 65 | parser.add_argument('--' + key.replace('_', '-')) 66 | # NOTE(josh): argparse behavior is that if the flag is not specified on 67 | # the command line the value will be None, whereas if it's specified with 68 | # no arguments then the value will be an empty list. This exactly what we 69 | # want since we can ignore `None` values. 70 | elif isinstance(value, list): 71 | if value: 72 | argtype = type(value[0]) 73 | else: 74 | argtype = None 75 | parser.add_argument('--' + key.replace('_', '-'), nargs='*', 76 | type=argtype, help=helpstr) 77 | elif isinstance(value, tuple): 78 | if value: 79 | argtype = type(value[0]) 80 | else: 81 | argtype = None 82 | parser.add_argument('--' + key.replace('_', '-'), nargs=len(value), 83 | type=argtype, help=helpstr) 84 | 85 | parser.add_argument('rootfs', nargs='?', 86 | help='path of the rootfs to enter') 87 | parser.add_argument('remainder', metavar='ARGV', 88 | nargs=argparse.REMAINDER, 89 | help='command and arguments') 90 | 91 | 92 | def reusable_main(argv): 93 | config = uchroot.Main().as_dict() 94 | config.update({"exbin": None, "argv": None, "env": None}) 95 | parser = argparse.ArgumentParser(description=__doc__) 96 | setup_parser(parser, config) 97 | args = parser.parse_args(argv) 98 | logger.setLevel(getattr(logging, args.log_level.upper())) 99 | 100 | configpath = None 101 | if args.config: 102 | configpath = args.config 103 | elif args.rootfs is not None: 104 | for tryfile in [".uchroot.py", ".uchroot.cfg"]: 105 | trypath = os.path.join(args.rootfs, tryfile) 106 | if os.path.exists(trypath): 107 | configpath = trypath 108 | logger.info("autoloading config from %s", configpath) 109 | break 110 | 111 | if configpath: 112 | with io.open(configpath, encoding='utf8') as infile: 113 | # pylint: disable=W0122 114 | exec(infile.read(), config) 115 | 116 | if args.dump_config: 117 | uchroot.dump_config(sys.stdout) 118 | sys.exit(0) 119 | 120 | if args.remainder: 121 | if args.argv is None: 122 | args.argv = [] 123 | args.argv.extend(args.remainder) 124 | 125 | for key, value in vars(args).items(): 126 | if value is not None and key in config: 127 | config[key] = value 128 | 129 | knownkeys = uchroot.Main.get_field_names() + uchroot.Exec.get_field_names() 130 | unknownkeys = [] 131 | for key, value in config.items(): 132 | if key.startswith('_'): 133 | continue 134 | 135 | if key in knownkeys: 136 | continue 137 | 138 | if inspect.ismodule(value): 139 | continue 140 | 141 | unknownkeys.append(key) 142 | 143 | if unknownkeys: 144 | logger.warning("Unrecognized config variables: %s", ", ".join(unknownkeys)) 145 | 146 | mainobj = uchroot.Main(**config) 147 | execobj = uchroot.Exec(**config) 148 | 149 | if args.subprocess: 150 | execobj.subprocess(preexec_fn=mainobj) 151 | else: 152 | # enter the jail 153 | mainobj() 154 | # and start the requested program 155 | execobj() 156 | logger.error("Failed to start a shell") 157 | 158 | return 1 159 | 160 | 161 | def main(): 162 | format_str = '%(levelname)-4s %(filename)s[%(lineno)-3s] : %(message)s' 163 | logging.basicConfig(level=logging.INFO, 164 | format=format_str, 165 | datefmt='%Y-%m-%d %H:%M:%S', 166 | filemode='w') 167 | return reusable_main(sys.argv[1:]) 168 | 169 | 170 | if __name__ == '__main__': 171 | sys.exit(main()) 172 | -------------------------------------------------------------------------------- /uchroot/doc/multistrap_example.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Example with multistrap 3 | ======================= 4 | 5 | Let's create a simple ubuntu ``trusty`` ``arm64`` container that we can run 6 | under emulation on an ``amd64`` (i.e. ``x64``) system using ``multistrap`` and 7 | ``uchroot``. 8 | 9 | Fix multistrap 10 | ============== 11 | 12 | Multistrap is a one-file perl script that does a similar job as ``debootstrap`` 13 | but works well for cross-architecture bootstrapping. There's a bug in multistrap 14 | distributed with ``trusty`` which can be fixed with this patch:: 15 | 16 | --- /usr/sbin/multistrap 2014-02-21 23:27:42.000000000 -0800 17 | +++ multistrap 2017-02-16 10:22:26.675399614 -0800 18 | @@ -986,7 +986,7 @@ 19 | } 20 | # reinstall set 21 | foreach my $reinst (sort @reinstall) { 22 | - system ("$str $env chroot $dir apt-get --reinstall -y $forceyes install $reinst"); 23 | + system ("$str $env chroot $dir apt-get --reinstall -y install $reinst"); 24 | } 25 | &run_native_hooks_end(sort @{$hooks{'N'}}) if (defined $hooks{'N'}); 26 | return $retval; 27 | 28 | 29 | First install the ``multistrap`` package, and then 30 | patch the script. 31 | 32 | On a trusty system, assuming you've cloned this repo into your home directory, 33 | run the following:: 34 | 35 | :~$ sudo apt-get install multistrap 36 | :~$ cd /tmp 37 | :/tmp$ cp /usr/sbin/multistrap ./ 38 | :/tmp$ patch-p0 < ~/uchroot/demo/multistrap.patch 39 | 40 | Bootstrap the rootfs 41 | ==================== 42 | 43 | Multistrap takes a config file. For this demo, use this config file (as 44 | ``trusty_arm64.conf``):: 45 | 46 | [General] 47 | arch=arm64 48 | directory=/tmp/trusty_arm64 49 | cleanup=true 50 | noauth=true 51 | unpack=true 52 | debootstrap=Ubuntu 53 | aptsources=Ubuntu 54 | omitrequired=true 55 | 56 | [Ubuntu] 57 | packages=adduser apt base-files base-passwd bash bsdutils busybox-initramfs coreutils cpio dash debconf debianutils diffutils dpkg e2fslibs e2fsprogs findutils gcc-4.9-base grep gzip insserv iproute2 klibc-utils libacl1 libattr1 libaudit-common libaudit1 libblkid1 libbz2-1.0 libc-bin libc6 libcap2 libcgmanager0 libcomerr2 libdb5.3 libdbus-1-3 libdebconfclient0 libdrm2 libgcc1 libjson-c2 libjson0 libklibc libkmod2 liblzma5 libmount1 libncurses5 libncursesw5 libnih-dbus1 libnih1 libpam-modules libpam-modules-bin libpam-runtime libpam0g libpcre3 libplymouth2 libpng12-0 libprocps3 libselinux1 libsemanage-common libsemanage1 libsepol1 libslang2 libss2 libtinfo5 libudev1 libustr-1.0-1 libuuid1 locales login lsb-base mawk mount multiarch-support nano ncurses-base ncurses-bin passwd perl-base patch python sed sensible-utils sysv-rc sysvinit-utils tar tzdata ubuntu-keyring util-linux zlib1g 58 | 59 | source=http://www.ports.ubuntu.com/ubuntu-ports 60 | keyring=ubuntu-keyring 61 | suite=trusty 62 | components=main universe 63 | omitdebsrc=true 64 | 65 | 66 | Execute multistrap with (assuming you patched it, otherwise just use 67 | ``multistrap``):: 68 | 69 | /tmp/multistrap -f demo/trusty_arm64.conf 70 | 71 | You should see:: 72 | 73 | ... 74 | I: Extracting ubuntu-keyring_2012.05.19_all.deb... 75 | I: Extracting zlib1g_1%3a1.2.8.dfsg-1ubuntu1_arm64.deb... 76 | I: Unpacking complete. 77 | I: Tidying up apt cache and list data. 78 | I: Tidying up apt cache and list data. 79 | 80 | Multistrap system installed successfully in /tmp/trusty_arm64/. 81 | 82 | Tweak the rootfs 83 | ================ 84 | 85 | When using multistrap, there are often a number of configuration hacks required 86 | to get dpkg to configure packages correctly. For this minimal rootfs we need the 87 | following tweaks:: 88 | 89 | :~/uchroot$ ln -s ../usr/lib/insserv/insserv /tmp/trusty_arm64/sbin/insserv 90 | :~/uchroot$ ln -s mawk /tmp/trusty_arm64/usr/bin/awk 91 | :~/uchroot$ cp demo/sources-arm64.list /tmp/trusty_arm64/etc/apt/sources.list 92 | :~/uchroot$ cp demo/base_files.patch /tmp/trusty_arm64/ 93 | :~/uchroot$ rm /tmp/trusty_arm64/etc/apt/sources.list.d/multistrap* 94 | 95 | 96 | Enter the container 97 | =================== 98 | 99 | 100 | Now use the config file in ``demo/trusty_arm64.json`` to ``uchroot`` into the 101 | rootfs:: 102 | 103 | :~/uchroot$ python uchroot.py demo/trusty_arm64.json 104 | 105 | Finish up a few tweaks and configure packages 106 | ============================================= 107 | 108 | You should see a root shell prompt (don't worry about "i-have-no-name"). Let's 109 | finish configuring the rootfs:: 110 | 111 | :/# /var/lib/dpkg/info/dash.preinst install 112 | :/# echo "dash dash/sh boolean true" > debconf-set-selections 113 | :/# echo "America/Los_Angeles" > /etc/timezone 114 | :/# patch -p0 < base_files.patch 115 | :/# rm base_files.patch 116 | :/# dpkg --configure -a 117 | 118 | You should see lots of messages for install hooks and package setup. There seems 119 | to be a problem where ``python-minimal`` may be configured before 120 | ``/etc/passwd`` and ``/etc/groups`` is written, so you'll probably see:: 121 | 122 | Errors were encountered while processing: 123 | python2.7-minimal 124 | python-minimal 125 | python 126 | python2.7 127 | 128 | Just run ``dpkg --configure -a`` again, and you should see:: 129 | 130 | Setting up python2.7-minimal (2.7.6-8) ... 131 | Setting up python-minimal (2.7.5-5ubuntu3) ... 132 | Setting up python2.7 (2.7.6-8) ... 133 | Setting up python (2.7.5-5ubuntu3) ... 134 | 135 | Play! 136 | ===== 137 | 138 | You can now exit the ``uchroot``, and then re-enter and you're all set up with a 139 | minimal trusty ``arm64`` emulated system. Install packages as usual with 140 | ``apt``. For instance:: 141 | 142 | :/# apt-get update 143 | :/# apt-get install python-pip 144 | 145 | When you exit the chroot, take a look through the filesystem. Anything that 146 | would have been owned by root inside the container is owned by your user outside 147 | the container. Otherwise, note that all the files are owned by users with very 148 | high ``uid`` and ``gid``. They are the mapped ids inside the user namespace. To 149 | understand what the system would have looked like if you really were root, 150 | subtract ``100000`` from all of the uids and gids. 151 | -------------------------------------------------------------------------------- /cmake/pkgconfig.cmake: -------------------------------------------------------------------------------- 1 | # Porcelain tools for using pkg config. Better than cmake's builtins. 2 | 3 | # Execute pkg-config and store flags in the cache. Sets the variable 4 | # `pkg_errno` in the parent scope to the return value of the subprocess 5 | # call. 6 | function(_pkg_query outvar arg) 7 | execute_process(COMMAND pkg-config --cflags-only-I ${arg} 8 | RESULT_VARIABLE _pkg_err 9 | OUTPUT_VARIABLE _pkg_out 10 | OUTPUT_STRIP_TRAILING_WHITESPACE) 11 | if(NOT _pkg_err EQUAL 0) 12 | set(pkg_errno 1 PARENT_SCOPE) 13 | return() 14 | endif() 15 | 16 | # Strip "-I" from include directories in the form of "-I/path/to" 17 | string(REGEX REPLACE "-I" "" _include_dirs "${_pkg_out}") 18 | # Convert space-separated list to semicolon-separated cmake-list 19 | string(REGEX REPLACE " +" ";" _include_list "${_include_dirs}") 20 | 21 | set(pkg_${outvar}_includedirs ${_include_list} CACHE STRING 22 | "include directories for ${outvar}" FORCE) 23 | 24 | execute_process(COMMAND pkg-config --cflags-only-other ${arg} 25 | RESULT_VARIABLE _pkg_err 26 | OUTPUT_VARIABLE _pkg_out 27 | OUTPUT_STRIP_TRAILING_WHITESPACE) 28 | if(NOT _pkg_err EQUAL 0) 29 | set(pkg_errno 1 PARENT_SCOPE) 30 | return() 31 | endif() 32 | 33 | set(pkg_${outvar}_cflags ${_pkg_out} CACHE STRING 34 | "cflags directories for ${outvar}" FORCE) 35 | 36 | execute_process(COMMAND pkg-config --libs-only-L ${arg} 37 | RESULT_VARIABLE _pkg_err 38 | OUTPUT_VARIABLE _pkg_out 39 | OUTPUT_STRIP_TRAILING_WHITESPACE) 40 | if(NOT _pkg_err EQUAL 0) 41 | set(pkg_errno 1 PARENT_SCOPE) 42 | return() 43 | endif() 44 | 45 | set(pkg_${outvar}_libdirs ${_pkg_out} CACHE STRING 46 | "library directories for ${outvar}" FORCE) 47 | 48 | execute_process(COMMAND pkg-config --libs-only-l ${arg} 49 | RESULT_VARIABLE _pkg_err 50 | OUTPUT_VARIABLE _pkg_out 51 | OUTPUT_STRIP_TRAILING_WHITESPACE) 52 | if(NOT _pkg_err EQUAL 0) 53 | set(pkg_errno 1 PARENT_SCOPE) 54 | return() 55 | endif() 56 | 57 | set(pkg_${outvar}_libs ${_pkg_out} CACHE STRING 58 | "library directories for ${outvar}" FORCE) 59 | 60 | set(pkg_${outvar}_name ${arg} CACHE STRING 61 | "selected name which worked as an argument to pkg-config" FORCE) 62 | 63 | set(pkg_errno 0 PARENT_SCOPE) 64 | endfunction() 65 | 66 | # Execute pkg-config for each name in the list 67 | # Usage _pkg_query_loop( [ [ [...]]]) 68 | function(_pkg_query_loop name) 69 | if(pkg_${outvar}_found) 70 | return() 71 | endif() 72 | set(outvar ${name}) 73 | set(names ${ARGN}) 74 | if(NOT names) 75 | set(names ${name}) 76 | endif() 77 | 78 | set(pkg_errno 1) 79 | foreach(_qname ${names}) 80 | _pkg_query(${outvar} ${_qname}) 81 | if(pkg_errno EQUAL 0) 82 | set(pkg_${outvar}_found TRUE CACHE BOOL "${outvar} was found" FORCE) 83 | return() 84 | endif() 85 | endforeach() 86 | 87 | set(pkg_${outvar}_found FALSE CACHE BOOL "${outvar} was not found" FORCE) 88 | endfunction() 89 | 90 | # Find system packages using pkg-config. 91 | # 92 | # usage: 93 | # pkg_find(PKG libfoo NAMES libfoo libfoo-1 libfoox 94 | # PKG libbar NAMES libarx 95 | # PKG libbaz) 96 | # 97 | # Execute pkg-config to get flags for each of the given library names. 98 | # Each library is specified with a line `PKG [NAMES , ...]`. 99 | # If the library might have different names on different supported systems 100 | # you may specify a list of names to attempt. The first one that `pkg-config` 101 | # exists with `0` for will be used. If `NAMES` is provided, then `` will 102 | # not be attempted (so duplicate it if you want to). `` will be used 103 | # for the key in the pkg-config database cache. 104 | function(pkg_find) 105 | set(state_ "PARSE_PKG") 106 | set(name_) 107 | set(names_) 108 | 109 | foreach(arg ${ARGN}) 110 | if(state_ STREQUAL "PARSE_PKG") 111 | if(arg STREQUAL "PKG") 112 | set(state_ "PARSE_NAME") 113 | set(name_) 114 | set(names_) 115 | else() 116 | message(FATAL_ERROR "malformed pkg_find, " 117 | "expected 'PKG' but got '${arg}'") 118 | endif() 119 | elseif(state_ STREQUAL "PARSE_NAME") 120 | set(name_ ${arg}) 121 | set(state_ "PARSE_KEYWORD") 122 | elseif(state_ STREQUAL "PARSE_KEYWORD") 123 | if(arg STREQUAL "PKG") 124 | _pkg_query_loop(${name_} ${names_}) 125 | set(name_) 126 | set(names_) 127 | set(state_ "PARSE_NAME") 128 | elseif(arg STREQUAL "NAMES") 129 | set(state_ "PARSE_NAMES") 130 | else() 131 | message(FATAL_ERROR "malformed pkg_find, expected keyword " 132 | "'PKG' or 'NAMES' but got '${arg}'") 133 | endif() 134 | elseif(state_ STREQUAL "PARSE_NAMES") 135 | if(arg STREQUAL "PKG") 136 | _pkg_query_loop(${name_} ${names_}) 137 | set(name_) 138 | set(names_) 139 | set(state_ "PARSE_NAME") 140 | elseif(arg STREQUAL "NAMES") 141 | message(FATAL_ERROR "malformed pkg_find, found literal " 142 | "'NAMES' when expecting library names") 143 | else() 144 | list(APPEND names_ ${arg}) 145 | endif() 146 | endif() 147 | endforeach() 148 | 149 | if(name_) 150 | _pkg_query_loop(${name_} ${names_}) 151 | endif() 152 | endfunction() 153 | 154 | # Add flags to compile/link options for the target according to the output of 155 | # pkg-config. Asserts that the given pkg-config packages were found 156 | function(target_pkg_depends target pkg0) 157 | foreach(pkgname ${pkg0} ${ARGN}) 158 | if(NOT pkg_${pkgname}_found) 159 | message(FATAL_ERROR "pkg-config package ${pkgname} is not enumerated, but" 160 | " required by ${target}") 161 | endif() 162 | if(NOT ${pkg_${pkgname}_found}) 163 | message(FATAL_ERROR "pkg-config package ${pkgname} is not found, but" 164 | " required by ${target}") 165 | endif() 166 | if(pkg_${pkgname}_includedirs) 167 | # TODO(josh): passthrough things like 168 | # SYSTEM, BEFORE, INTERFACE|PUBLIC|PRIVATE 169 | target_include_directories(${target} SYSTEM PUBLIC 170 | ${pkg_${pkgname}_includedirs}) 171 | endif() 172 | if(pkg_${pkgname}_cflags) 173 | # TODO(josh): passthrough things like 174 | # BEFORE, INTERFACE|PUBLIC|PRIVATE 175 | target_compile_options(${target} PUBLIC ${pkg_${pkgname}_cflags}) 176 | endif() 177 | if(pkg_${pkgname}_libdirs) 178 | get_target_property(lflags_ ${target} LINK_FLAGS) 179 | if(lflags_) 180 | list(APPEND lflags_ ${pkg_${pkgname}_lflags}) 181 | set_target_properties(${target} PROPERTIES LINK_FLAGS ${lflags_}) 182 | else() 183 | set_target_properties(${target} PROPERTIES LINK_FLAGS 184 | ${pkg_${pkgname}_lflags}) 185 | endif() 186 | 187 | endif() 188 | if(pkg_${pkgname}_libs) 189 | # Passthrough options like INTERFACE|PUBLIC|PRIVATE and 190 | # debug|optimized|general 191 | target_link_libraries(${target} PUBLIC ${pkg_${pkgname}_libs}) 192 | endif() 193 | endforeach() 194 | endfunction() 195 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=CVS 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=yes 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | # A comma-separated list of package or module names from where C extensions may 25 | # be loaded. Extensions are loading into the active Python interpreter and may 26 | # run arbitrary code 27 | extension-pkg-whitelist= 28 | 29 | 30 | [MESSAGES CONTROL] 31 | 32 | # Enable the message, report, category or checker with the given id(s). You can 33 | # either give multiple identifier separated by comma (,) or put this option 34 | # multiple time. See also the "--disable" option for examples. 35 | #enable= 36 | 37 | # Disable the message, report, category or checker with the given id(s). You 38 | # can either give multiple identifiers separated by comma (,) or put this 39 | # option multiple times (only on the command line, not in the configuration 40 | # file where it should appear only once).You can also use "--disable=all" to 41 | # disable everything first and then reenable specific checks. For example, if 42 | # you want to run only the similarities checker, you can use "--disable=all 43 | # --enable=similarities". If you want to run only the classes checker, but have 44 | # no Warning level messages displayed, use"--disable=all --enable=classes 45 | # --disable=W" 46 | disable= 47 | bad-option-value, 48 | cyclic-import, 49 | duplicate-code, 50 | file-ignored, 51 | fixme, 52 | locally-disabled, 53 | logging-format-interpolation, 54 | missing-docstring, 55 | no-name-in-module, 56 | no-self-use, 57 | star-args, 58 | too-few-public-methods, 59 | too-many-ancestors, 60 | too-many-boolean-expressions, 61 | unidiomatic-typecheck, 62 | useless-object-inheritance 63 | 64 | 65 | [REPORTS] 66 | 67 | # Set the output format. Available formats are text, parseable, colorized, msvs 68 | # (visual studio) and html. You can also give a reporter class, eg 69 | # mypackage.mymodule.MyReporterClass. 70 | output-format=colorized 71 | 72 | # Put messages in a separate file for each module / package specified on the 73 | # command line instead of printing them on stdout. Reports (if any) will be 74 | # written in a file name "pylint_global.[txt|html]". 75 | files-output=no 76 | 77 | # Tells whether to display a full report or only the messages 78 | reports=no 79 | 80 | # Python expression which should return a note less than 10 (10 is the highest 81 | # note). You have access to the variables errors warning, statement which 82 | # respectively contain the number of errors / warnings messages and the total 83 | # number of statements analyzed. This is used by the global evaluation report 84 | # (RP0004). 85 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 86 | 87 | # Add a comment according to your evaluation note. This is used by the global 88 | # evaluation report (RP0004). 89 | comment=no 90 | 91 | # Template used to display messages. This is a python new-style format string 92 | # used to format the message information. See doc for all details 93 | msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} 94 | 95 | 96 | [TYPECHECK] 97 | 98 | # Tells whether missing members accessed in mixin class should be ignored. A 99 | # mixin class is detected if its name ends with "mixin" (case insensitive). 100 | ignore-mixin-members=yes 101 | 102 | # List of classes names for which member attributes should not be checked 103 | # (useful for classes with attributes dynamically set). 104 | ignored-classes= 105 | cairo, 106 | cv2, 107 | GitError, 108 | git_watch.inotify, 109 | jinja2, 110 | matplotlib.cm, 111 | numpy, 112 | numpy.random, 113 | plotly.graph_objs, 114 | scoped_session, 115 | SQLAlchemy, 116 | SQLObject, 117 | tensorflow, 118 | TokenKind, 119 | twisted.internet.reactor, 120 | urllib, 121 | Workspace, 122 | 123 | # When zope mode is activated, add a predefined set of Zope acquired attributes 124 | # to generated-members. 125 | zope=no 126 | 127 | # List of members which are set dynamically and missed by pylint inference 128 | # system, and so shouldn't trigger E0201 when accessed. Python regular 129 | # expressions are accepted. 130 | generated-members=REQUEST,acl_users,aq_parent,query 131 | 132 | 133 | [MISCELLANEOUS] 134 | 135 | # List of note tags to take in consideration, separated by a comma. 136 | notes=FIXME,XXX,TODO,NOTE 137 | 138 | 139 | [BASIC] 140 | 141 | # Required attributes for module, separated by a comma 142 | required-attributes= 143 | 144 | # List of builtins function names that should not be used, separated by a comma 145 | bad-functions=filter,apply,input 146 | 147 | # Regular expression which should only match correct module names 148 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 149 | 150 | # Regular expression which should only match correct module level names 151 | const-rgx=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$ 152 | 153 | # Regular expression which should only match correct class names 154 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 155 | 156 | # Regular expression which should only match correct function names 157 | function-rgx=[a-z_][a-z0-9_]{2,40}$ 158 | 159 | # Regular expression which should only match correct method names 160 | method-rgx=[a-z_][a-z0-9_]{2,40}|[a-z]{2,20}_[T|R]_[a-z]{3,20}$ 161 | 162 | # Regular expression which should only match correct instance attribute names 163 | attr-rgx=[a-z_][a-z0-9_]{2,40}$ 164 | 165 | # Regular expression which should only match correct argument names 166 | argument-rgx=[a-z_]([a-z0-9_]{2,40}|[a-z0-9]{2,20}_[T|R]_[a-z0-9_]{3,20})$ 167 | 168 | # Regular expression which should only match correct variable names 169 | # Special-case support for foo_T_bar style names 170 | variable-rgx=[a-z_]([a-z0-9_]{2,40}|[a-z0-9]{2,20}_[T|R]_[a-z0-9_]{3,20})$ 171 | 172 | # Regular expression which should only match correct attribute names in class 173 | # bodies 174 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__)|[a-z]{2,20}_[T|R]_[a-z0-9_]{3,20})$ 175 | 176 | # Regular expression which should only match correct list comprehension / 177 | # generator expression variable names 178 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 179 | 180 | # Good variable names which should always be accepted, separated by a comma 181 | good-names=i,j,k,f,ex,Run,_,d,dt,p,x,y,z,w,h,c,xy,op,xx,yy,ax,t,db,fd 182 | 183 | # Bad variable names which should always be refused, separated by a comma 184 | bad-names=foo,baz,toto,tutu,tata 185 | 186 | # Regular expression which should only match function or class names that do 187 | # not require a docstring. 188 | no-docstring-rgx=__.*__ 189 | 190 | # Minimum line length for functions/classes that require docstrings, shorter 191 | # ones are exempt. 192 | docstring-min-length=-1 193 | 194 | 195 | [SIMILARITIES] 196 | 197 | # Minimum lines number of a similarity. 198 | min-similarity-lines=6 199 | 200 | # Ignore comments when computing similarities. 201 | ignore-comments=yes 202 | 203 | # Ignore docstrings when computing similarities. 204 | ignore-docstrings=yes 205 | 206 | # Ignore imports when computing similarities. 207 | ignore-imports=yes 208 | 209 | 210 | [FORMAT] 211 | 212 | # Maximum number of characters on a single line. 213 | max-line-length=80 214 | 215 | # Regexp for a line that is allowed to be longer than the limit. 216 | ignore-long-lines=^\s*(# )?(:see: )??$ 217 | 218 | # Allow the body of an if to be on the same line as the test if there is no 219 | # else. 220 | single-line-if-stmt=no 221 | 222 | # List of optional constructs for which whitespace checking is disabled 223 | no-space-check=trailing-comma,dict-separator 224 | 225 | # Maximum number of lines in a module 226 | max-module-lines=1000 227 | 228 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 229 | # tab). 230 | indent-string=' ' 231 | 232 | 233 | [VARIABLES] 234 | 235 | # Tells whether we should check for unused import in __init__ files. 236 | init-import=no 237 | 238 | # A regular expression matching the beginning of the name of dummy variables 239 | # (i.e. not used). 240 | # dummy-variables-rgx=(_+[a-zA-Z0-9]*?$) 241 | 242 | # List of additional names supposed to be defined in builtins. Remember that 243 | # you should avoid to define new builtins when possible. 244 | additional-builtins=unicode 245 | 246 | 247 | [IMPORTS] 248 | 249 | # Deprecated modules which should not be used, separated by a comma 250 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 251 | 252 | # Create a graph of every (i.e. internal and external) dependencies in the 253 | # given file (report RP0402 must not be disabled) 254 | import-graph= 255 | 256 | # Create a graph of external dependencies in the given file (report RP0402 must 257 | # not be disabled) 258 | ext-import-graph= 259 | 260 | # Create a graph of internal dependencies in the given file (report RP0402 must 261 | # not be disabled) 262 | int-import-graph= 263 | 264 | 265 | [CLASSES] 266 | 267 | # List of interface methods to ignore, separated by a comma. This is used for 268 | # instance to not check methods defines in Zope's Interface base class. 269 | ignore-iface-methods= 270 | adaptWith, 271 | deferred, 272 | extends, 273 | getBases, 274 | getDescriptionFor, 275 | getDoc, 276 | getName, 277 | getTaggedValue, 278 | getTaggedValueTags, 279 | isEqualOrExtendedBy, 280 | isImplementedBy, 281 | isImplementedByInstancesOf, 282 | is_implemented_by 283 | names, 284 | namesAndDescriptions, 285 | queryDescriptionFor, 286 | setTaggedValue, 287 | 288 | # List of method names used to declare (i.e. assign) instance attributes. 289 | defining-attr-methods=__init__,__new__,setUp 290 | 291 | # List of valid names for the first argument in a class method. 292 | valid-classmethod-first-arg=cls 293 | 294 | # List of valid names for the first argument in a metaclass class method. 295 | valid-metaclass-classmethod-first-arg=mcs 296 | 297 | 298 | [DESIGN] 299 | 300 | # Maximum number of arguments for function / method 301 | max-args=10 302 | 303 | # Argument names that match this expression will be ignored. Default to name 304 | # with leading underscore 305 | ignored-argument-names=_.*, cc 306 | 307 | # Maximum number of locals for function / method body 308 | max-locals=30 309 | 310 | # Maximum number of return / yield for function / method body 311 | max-returns=10 312 | 313 | # Maximum number of branch for function / method body 314 | max-branches=30 315 | 316 | # Maximum number of statements in function / method body 317 | max-statements=60 318 | 319 | # Maximum number of parents for a class (see R0901). 320 | max-parents=20 321 | 322 | # Maximum number of attributes for a class (see R0902). 323 | max-attributes=15 324 | 325 | # Minimum number of public methods for a class (see R0903). 326 | min-public-methods=1 327 | 328 | # Maximum number of public methods for a class (see R0904). 329 | max-public-methods=100 330 | 331 | 332 | [EXCEPTIONS] 333 | 334 | # Exceptions that will emit a warning when being caught. Defaults to 335 | # "Exception" 336 | overgeneral-exceptions=Exception 337 | -------------------------------------------------------------------------------- /uchroot/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chroot without root using linux user namespaces to circumvent the need for 3 | root priviledges. 4 | 5 | Based on: https://gist.github.com/cheshirekow/fe1451e245d1a0855ad3d1dca115aeca 6 | """ 7 | 8 | # NOTE(josh): see http://man7.org/linux/man-pages/man5/subuid.5.html on 9 | # subordinate UIDs. 10 | # https://lwn.net/Articles/532593/ 11 | 12 | import ctypes 13 | import errno 14 | import inspect 15 | import logging 16 | import os 17 | import pprint 18 | import pwd 19 | import json 20 | import re 21 | import subprocess 22 | import sys 23 | import tempfile 24 | import textwrap 25 | 26 | VERSION = '0.1.4' 27 | 28 | if sys.version_info < (3, 0, 0): 29 | STRING_TYPES = (str, unicode) 30 | else: 31 | STRING_TYPES = (str,) 32 | 33 | logger = logging.getLogger(__name__) 34 | 35 | 36 | def get_glibc(): 37 | """ 38 | Return a ctypes wrapper around glibc. Only wraps functions needed by this 39 | script. 40 | """ 41 | 42 | glibc = ctypes.CDLL('libc.so.6', use_errno=True) 43 | 44 | # http://man7.org/linux/man-pages/man2/getuid.2.html 45 | glibc.getuid.restype = ctypes.c_uint # gid_t, uint32_t on my system 46 | glibc.getuid.argtypes = [] 47 | 48 | # http://man7.org/linux/man-pages/man2/getgid.2.html 49 | glibc.getgid.restype = ctypes.c_uint # gid_t, uint32_t on my system 50 | glibc.getgid.argtypes = [] 51 | 52 | # http://man7.org/linux/man-pages/man2/unshare.2.html 53 | glibc.unshare.restype = ctypes.c_int 54 | glibc.unshare.argtypes = [ctypes.c_int] 55 | 56 | # http://man7.org/linux/man-pages/man2/getpid.2.html 57 | glibc.getpid.restype = ctypes.c_int # pid_t, int32_t on my system 58 | glibc.getpid.argtypes = [] 59 | 60 | # http://man7.org/linux/man-pages/man2/chroot.2.html 61 | glibc.chroot.restype = ctypes.c_int 62 | glibc.chroot.argtypes = [ctypes.c_char_p] 63 | 64 | # http://man7.org/linux/man-pages/man2/setresuid.2.html 65 | glibc.setresuid.restype = ctypes.c_int 66 | glibc.setresuid.argtypes = [ctypes.c_uint, ctypes.c_uint, ctypes.c_uint] 67 | glibc.setresgid.restype = ctypes.c_int 68 | glibc.setresgid.argtypes = [ctypes.c_uint, ctypes.c_uint, ctypes.c_uint] 69 | 70 | # http://man7.org/linux/man-pages/man2/mount.2.html 71 | glibc.mount.restype = ctypes.c_int 72 | glibc.mount.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, 73 | ctypes.c_uint, # unsigned long 74 | ctypes.c_void_p] 75 | 76 | # http://man7.org/linux/man-pages/man2/inotify_init.2.html 77 | glibc.inotify_init.restype = ctypes.c_int 78 | glibc.inotify_init.argtypes = [] 79 | 80 | glibc.inotify_init1.restype = ctypes.c_int 81 | glibc.inotify_init1.argtypes = [ctypes.c_int] 82 | 83 | # http://man7.org/linux/man-pages/man2/inotify_add_watch.2.html 84 | glibc.inotify_add_watch.restype = ctypes.c_int 85 | glibc.inotify_add_watch.argtypes = [ 86 | ctypes.c_int, ctypes.c_char_p, ctypes.c_uint32] 87 | 88 | # http://man7.org/linux/man-pages/man2/inotify_rm_watch.2.html 89 | glibc.inotify_rm_watch.restype = ctypes.c_int 90 | glibc.inotify_rm_watch.argtypes = [ctypes.c_int, ctypes.c_int] 91 | 92 | # http://man7.org/linux/man-pages/man2/signalfd.2.html 93 | glibc.signalfd.restype = ctypes.c_int 94 | glibc.signalfd.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int] 95 | 96 | glibc.CLONE_NEWNS = 0x20000 97 | glibc.CLONE_NEWUSER = 0x10000000 98 | glibc.IN_ACCESS = 0x1 99 | glibc.IN_ATTRIB = 0x4 100 | glibc.IN_CLOEXEC = 0x80000 101 | glibc.IN_CLOSE_NOWRITE = 0x10 102 | glibc.IN_CLOSE_WRITE = 0x8 103 | glibc.IN_CREATE = 0x100 104 | glibc.IN_DELETE = 0x200 105 | glibc.IN_DELETE_SELF = 0x400 106 | glibc.IN_MODIFY = 0x2 107 | glibc.IN_MOVED_FROM = 0x40 108 | glibc.IN_MOVED_TO = 0x80 109 | glibc.IN_MOVE_SELF = 0x800 110 | glibc.IN_NONBLOCK = 0x800 111 | glibc.IN_OPEN = 0x20 112 | glibc.MS_BIND = 0x1000 113 | glibc.MS_REC = 0x4000 114 | glibc.SFD_CLOEXEC = 0x80000 115 | glibc.SFD_NONBLOCK = 0x800 116 | 117 | return glibc 118 | 119 | 120 | def get_subid_range(subid_path, username, uid): 121 | """Return the subordinate user/group id and count for the given user.""" 122 | 123 | with open(subid_path, 'r') as subuid: 124 | for line in subuid: 125 | subuid_name, subuid_min, subuid_count = line.strip().split(':') 126 | if subuid_name == username: 127 | return (int(subuid_min), int(subuid_count)) 128 | 129 | try: 130 | subuid_uid = int(subuid_name) 131 | if subuid_uid == uid: 132 | return (int(subuid_min), int(subuid_count)) 133 | except ValueError: 134 | pass 135 | 136 | raise ValueError("user {}({}) not found in subid file {}".format( 137 | username, uid, subid_path)) 138 | 139 | 140 | def write_id_map(id_map_path, id_outside, subid_range): 141 | """ 142 | Write uid_map or gid_map. 143 | NOTE(josh): doesn't work. We need CAP_SETUID (CAP_SETGID) in the *parent* 144 | namespace to be allowed to do this. Evidently, that is why there the 145 | setuid-root newuidmap/newgidmap programs exist. 146 | """ 147 | with open(id_map_path, 'wb') as id_map: 148 | logger.debug("Writing : %s (fd=%d)", id_map_path, id_map.fileno()) 149 | id_map.write("{id_inside} {id_outside} {count}\n".format( 150 | id_inside=0, id_outside=id_outside, count=1)) 151 | id_map.write("{id_inside} {id_outside} {count}\n".format( 152 | id_inside=1, id_outside=subid_range[0], count=subid_range[1])) 153 | 154 | 155 | def write_setgroups(pid): 156 | setgroups_path = '/proc/{}/setgroups'.format(pid) 157 | with open(setgroups_path, 'wb') as setgroups: 158 | logger.debug("Writing : %s (fd=%d)", setgroups_path, setgroups.fileno()) 159 | # NOTE(josh): was previously "deny", but apt-get calls this so we must 160 | # allow it if we want to use apt-get. Look into this more. 161 | setgroups.write(b"allow\n") 162 | 163 | 164 | def set_id_map(idmap_bin, pid, id_outside, subid_range): 165 | """Set uid_map or gid_map through subprocess calls.""" 166 | logger.debug("Calling %s", idmap_bin) 167 | subprocess.check_call(['/usr/bin/{}'.format(idmap_bin), str(pid), 168 | '0', str(id_outside), '1', 169 | '1', str(subid_range[0]), str(subid_range[1])]) 170 | 171 | 172 | def make_sure_is_dir(need_dir, source): 173 | """ 174 | Ensure that the given path is a directory, removing a regular file if 175 | there is one at that location, creating the directory and all its 176 | parents if needed. 177 | """ 178 | 179 | if not os.path.isdir(need_dir): 180 | if os.path.exists(need_dir): 181 | logger.warning("removing rootfs bind target %s because it" 182 | " is not a directory\n", need_dir) 183 | os.remove(need_dir) 184 | logger.warning("creating rootfs directory %s because it is " 185 | " needed to bind mount %s.\n", need_dir, source) 186 | os.makedirs(need_dir) 187 | 188 | 189 | def make_sure_is_file(need_path, source): 190 | """ 191 | Ensure that the parent directory of need_path exists, and that there is 192 | a regular file at that location, creating them if needed. 193 | """ 194 | make_sure_is_dir(os.path.dirname(need_path), source) 195 | 196 | if not os.path.lexists(need_path): 197 | logger.warning("creating rootfs regular file %s because it " 198 | " is a requested mount-point for %s\n", need_path, source) 199 | with open(need_path, 'wb') as touchfile: 200 | touchfile.write('# written by uchroot'.encode("utf-8")) 201 | 202 | 203 | def enter(read_fd, write_fd, rootfs=None, binds=None, qemu=None, identity=None, 204 | cwd=None): 205 | """ 206 | Chroot into rootfs with a new user and mount namespace, then execute 207 | the desired command. 208 | """ 209 | # pylint: disable=too-many-locals,too-many-statements 210 | 211 | if not binds: 212 | binds = [] 213 | if not identity: 214 | identity = [0, 0] 215 | if not cwd: 216 | cwd = '/' 217 | 218 | glibc = get_glibc() 219 | uid = glibc.getuid() 220 | gid = glibc.getgid() 221 | 222 | logger.debug("Before unshare, uid=%d, gid=%d", uid, gid) 223 | # --------------------------------------------------------------------- 224 | # Create User Namespace 225 | # --------------------------------------------------------------------- 226 | 227 | # First, unshare the user namespace and assume admin capability in the 228 | # new namespace 229 | err = glibc.unshare(glibc.CLONE_NEWUSER) 230 | if err != 0: 231 | raise OSError(err, "Failed to unshared user namespace", None) 232 | 233 | # write a uid/pid map 234 | pid = glibc.getpid() 235 | logger.debug("My pid: %d", pid) 236 | 237 | # Notify the helper that we have created the new namespace, and we need 238 | # it to set our uid/gid map 239 | logger.debug("Waiting for helper to set my uid/gid map") 240 | os.write(write_fd, b"#") 241 | 242 | # Wait for the helper to finish setting our uid/gid map 243 | os.read(read_fd, 1) 244 | logger.debug("Helper has finished setting my uid/gid map") 245 | 246 | # --------------------------------------------------------------------- 247 | # Create Mount Namespace 248 | # --------------------------------------------------------------------- 249 | err = glibc.unshare(glibc.CLONE_NEWNS) 250 | if err != 0: 251 | logger.error('Failed to unshare mount namespace') 252 | 253 | null_ptr = ctypes.POINTER(ctypes.c_char)() 254 | for bind_spec in binds: 255 | if isinstance(bind_spec, (list, tuple)): 256 | source, dest = bind_spec 257 | elif ':' in bind_spec: 258 | source, dest = bind_spec.split(':') 259 | else: 260 | source = bind_spec 261 | dest = bind_spec 262 | 263 | dest = dest.lstrip('/') 264 | rootfs_dest = os.path.join(rootfs, dest) 265 | logger.debug('Binding: %s -> %s', source, rootfs_dest) 266 | assert os.path.exists(source),\ 267 | "source directory to bind does not exist {}".format(source) 268 | 269 | # Create the mountpoint if it is not already in the rootfs 270 | if os.path.isdir(source): 271 | make_sure_is_dir(rootfs_dest, source) 272 | else: 273 | make_sure_is_file(rootfs_dest, source) 274 | 275 | if source.lstrip('/') == 'proc': 276 | # NOTE(josh): user isn't allowed to mount proc without MS_REC, see 277 | # https://stackoverflow.com/a/23435317 278 | result = glibc.mount(source.encode("utf-8"), 279 | rootfs_dest.encode("utf-8"), b"proc", 280 | glibc.MS_REC | glibc.MS_BIND, null_ptr) 281 | elif source.lstrip("/") == "dev/pts": 282 | result = glibc.mount(b"/dev/pts", 283 | rootfs_dest.encode("utf-8"), b"devpts", 284 | 0, null_ptr) 285 | else: 286 | # NOTE(josh): MS_REC is needed if the source to bind contains mounted 287 | # filesystems somewhere in it's subtree. Otherwise our unpriviledged 288 | # mount namespace would be able to see tree's outside of it's 289 | # namespace without permission. 290 | # TODO(josh): we should probably warn if this is required. It is 291 | # rather odd. 292 | result = glibc.mount(source.encode("utf-8"), 293 | rootfs_dest.encode("utf-8"), null_ptr, 294 | glibc.MS_BIND | glibc.MS_REC, null_ptr) 295 | if result == -1: 296 | err = ctypes.get_errno() 297 | logger.warning('Failed to mount %s -> %s [%s](%d) %s', 298 | source, rootfs_dest, 299 | errno.errorcode.get(err, '??'), err, 300 | os.strerror(err)) 301 | 302 | if qemu: 303 | dest = qemu.lstrip('/') 304 | rootfs_dest = os.path.join(rootfs, dest) 305 | make_sure_is_dir(os.path.dirname(rootfs_dest), qemu) 306 | logger.debug("Installing %s", qemu) 307 | with open(rootfs_dest, 'wb') as outfile: 308 | with open(qemu, 'rb') as infile: 309 | chunk = infile.read(1024 * 4) 310 | while chunk: 311 | outfile.write(chunk) 312 | chunk = infile.read(1024 * 4) 313 | 314 | os.chmod(rootfs_dest, 0o755) 315 | 316 | # --------------------------------------------------------------------- 317 | # Chroot 318 | # --------------------------------------------------------------------- 319 | 320 | # Now chroot into the desired directory 321 | err = glibc.chroot(rootfs.encode("utf-8")) 322 | if err != 0: 323 | logger.error("Failed to chroot") 324 | raise OSError(err, "Failed to chroot", rootfs) 325 | 326 | # Set the cwd 327 | os.chdir(cwd) 328 | 329 | # Now drop admin in our namespace. Drop gid first, since losing UID privelidge 330 | # will prevent us from dropping it second. 331 | err = glibc.setresgid(identity[1], identity[1], identity[1]) 332 | if err: 333 | logger.error("Failed to set gid") 334 | 335 | err = glibc.setresuid(identity[0], identity[0], identity[0]) 336 | if err != 0: 337 | logger.error("Failed to set uid") 338 | 339 | 340 | def validate_id_range(requested_range, allowed_range): 341 | """Check that the requested id range lies within the users allowed id 342 | range.""" 343 | assert (requested_range[0] > 0) and (requested_range[1] > 0), \ 344 | "id maps must satisfy min ({}) > 0 and count ({}) > 0".format( 345 | requested_range[0], requested_range[1]) 346 | 347 | min_requested = requested_range[0] 348 | max_requested = min_requested + requested_range[1] 349 | min_allowed = allowed_range[0] 350 | max_allowed = min_allowed + allowed_range[1] 351 | 352 | assert (min_requested >= min_allowed and max_requested <= max_allowed), \ 353 | "id range ({}, {}) is not contained your allowed range ({}, {})".format( 354 | requested_range[0], requested_range[1], allowed_range[0], 355 | allowed_range[1]) 356 | 357 | 358 | def set_userns_idmap(chroot_pid, uid_range, gid_range): 359 | """Writes uid/gid maps for the chroot process.""" 360 | uid = os.getuid() 361 | gid = os.getgid() 362 | username = pwd.getpwuid(uid)[0] 363 | 364 | subuid_range = get_subid_range('/etc/subuid', username, uid) 365 | if uid_range: 366 | validate_id_range(uid_range, subuid_range) 367 | else: 368 | uid_range = subuid_range 369 | 370 | subgid_range = get_subid_range('/etc/subgid', username, uid) 371 | if gid_range: 372 | validate_id_range(gid_range, subgid_range) 373 | else: 374 | gid_range = subgid_range 375 | 376 | set_id_map('newuidmap', chroot_pid, uid, uid_range) 377 | try: 378 | write_setgroups(chroot_pid) 379 | except IOError: 380 | logger.exception("Failed to write setgroups") 381 | set_id_map('newgidmap', chroot_pid, gid, gid_range) 382 | 383 | 384 | def main(rootfs, binds=None, qemu=None, identity=None, uid_range=None, 385 | gid_range=None, cwd=None): 386 | """Fork off a helper subprocess, enter the chroot jail. Wait for the helper 387 | to call the setuid-root helper programs and configure the uid map of the 388 | jail, then return.""" 389 | 390 | for idmap_bin in ['newuidmap', 'newgidmap']: 391 | assert os.path.exists('/usr/bin/{}'.format(idmap_bin)), \ 392 | "Missing required binary '{}'".format(idmap_bin) 393 | 394 | # Pipes used to synchronize between the helper process and the chroot 395 | # process. Could also use eventfd, but this is simpler because python 396 | # already has os.pipe() 397 | helper_read_fd, primary_write_fd = os.pipe() 398 | primary_read_fd, helper_write_fd = os.pipe() 399 | 400 | parent_pid = os.getpid() 401 | child_pid = os.fork() 402 | 403 | if child_pid == 0: 404 | # Wait for the primary to create its new namespace 405 | os.read(helper_read_fd, 1) 406 | 407 | # Set the uid/gid map using the setuid helper programs 408 | set_userns_idmap(parent_pid, uid_range, gid_range) 409 | # Inform the primary that we have finished setting its uid/gid map. 410 | os.write(helper_write_fd, b'#') 411 | 412 | # NOTE(josh): using sys.exit() will interfere with the interpreter in the 413 | # parent process. 414 | # see: https://docs.python.org/3/library/os.html#os._exit 415 | os._exit(0) # pylint: disable=protected-access 416 | else: 417 | enter(primary_read_fd, primary_write_fd, rootfs, binds, qemu, 418 | identity, cwd) 419 | 420 | 421 | def process_environment(env_dict): 422 | """Given an environment dictionary, merge any lists with pathsep and return 423 | the new dictionary.""" 424 | 425 | out_dict = {} 426 | for key, value in env_dict.items(): 427 | if isinstance(value, list): 428 | out_dict[key] = ':'.join(value) 429 | elif isinstance(value, STRING_TYPES): 430 | out_dict[key] = value 431 | else: 432 | out_dict[key] = str(value) 433 | return out_dict 434 | 435 | 436 | # exec defaults 437 | DEFAULT_BIN = '/bin/bash' 438 | DEFAULT_ARGV = ['bash'] 439 | DEFAULT_PATH = ['/usr/sbin', '/usr/bin', '/sbin', '/bin'] 440 | 441 | 442 | def serialize(obj): 443 | """ 444 | Return a serializable representation of the object. If the object has an 445 | `as_dict` method, then it will call and return the output of that method. 446 | Otherwise return the object itself. 447 | """ 448 | if hasattr(obj, 'as_dict'): 449 | fun = getattr(obj, 'as_dict') 450 | if callable(fun): 451 | return fun() 452 | 453 | return obj 454 | 455 | 456 | class ConfigObject(object): 457 | """ 458 | Provides simple serialization to a dictionary based on the assumption that 459 | all args in the __init__() function are fields of this object. 460 | """ 461 | 462 | @classmethod 463 | def get_field_names(cls): 464 | """ 465 | Return a list of field names, extracted from kwargs to __init__(). 466 | The order of fields in the tuple representation is the same as the order 467 | of the fields in the __init__ function 468 | """ 469 | # NOTE(josh): args[0] is `self` 470 | if sys.version_info >= (3, 5, 0): 471 | sig = getattr(inspect, 'signature')(cls.__init__) 472 | return [field for field, _ in list(sig.parameters.items())[1:-1]] 473 | 474 | return getattr(inspect, 'getargspec')(cls.__init__).args[1:] 475 | 476 | def as_dict(self): 477 | """ 478 | Return a dictionary mapping field names to their values only for fields 479 | specified in the constructor 480 | """ 481 | return {field: serialize(getattr(self, field)) 482 | for field in self.get_field_names()} 483 | 484 | 485 | def get_default(obj, default): 486 | """ 487 | If obj is not `None` then return it. Otherwise return default. 488 | """ 489 | if obj is None: 490 | return default 491 | 492 | return obj 493 | 494 | 495 | class Exec(ConfigObject): 496 | """ 497 | Simple object to hold together the path, argument vector, and environment 498 | of an exec call. 499 | """ 500 | 501 | def __init__(self, exbin=None, argv=None, env=None, 502 | **_): # pylint: disable=W0613 503 | logger.debug("Exec({}, {}, {})".format(exbin, argv, env)) 504 | if exbin: 505 | self.exbin = exbin 506 | if not argv: 507 | argv = [exbin.split('/')[-1]] 508 | else: 509 | if argv: 510 | self.exbin = argv[0] 511 | else: 512 | self.exbin = DEFAULT_BIN 513 | argv = [os.path.basename(DEFAULT_BIN)] 514 | 515 | if argv: 516 | self.argv = argv 517 | else: 518 | self.argv = DEFAULT_ARGV 519 | 520 | if env is not None: 521 | self.env = process_environment(env) 522 | else: 523 | self.env = process_environment(dict(PATH=DEFAULT_PATH)) 524 | 525 | def __call__(self): 526 | logger.debug('Executing %s', self.exbin) 527 | if "/" in self.exbin: 528 | return os.execve(self.exbin, self.argv, self.env) 529 | 530 | return os.execvpe(self.exbin, self.argv, self.env) 531 | 532 | def subprocess(self, preexec_fn=None): 533 | logger.debug('Subprocessing %s', self.exbin) 534 | return subprocess.call(self.argv, executable=self.exbin, env=self.env, 535 | preexec_fn=preexec_fn) 536 | 537 | 538 | class Main(ConfigObject): 539 | """ 540 | Simple bind for subprocess prexec_fn. 541 | """ 542 | 543 | def __init__(self, 544 | rootfs=None, 545 | binds=None, 546 | qemu=None, 547 | identity=None, 548 | uid_range=None, 549 | gid_range=None, 550 | cwd=None, 551 | extra_preexec_fn=None, 552 | **_): # pylint: disable=W0613 553 | self.rootfs = rootfs 554 | self.binds = get_default(binds, []) 555 | self.qemu = qemu 556 | self.identity = get_default(identity, (0, 0)) 557 | 558 | uid = os.getuid() 559 | username = pwd.getpwuid(uid)[0] 560 | self.uid_range = get_default( 561 | uid_range, get_subid_range('/etc/subuid', username, uid)) 562 | self.gid_range = get_default( 563 | gid_range, get_subid_range('/etc/subgid', username, uid)) 564 | self.cwd = get_default(cwd, '/') 565 | self.extra_preexec_fn = extra_preexec_fn 566 | 567 | def __call__(self): 568 | kwargs = self.as_dict() 569 | kwargs.pop("extra_preexec_fn", None) 570 | main(**kwargs) 571 | if self.extra_preexec_fn is not None: 572 | self.extra_preexec_fn() 573 | 574 | 575 | class Container(ConfigObject): 576 | """ 577 | Simple object to maintain the configuration of a chroot between subprocess 578 | calls. Has the same interface as the subprocess module. 579 | """ 580 | 581 | def __init__(self, 582 | rootfs=None, 583 | binds=None, 584 | qemu=None, 585 | identity=None, 586 | uid_range=None, 587 | gid_range=None, 588 | cwd=None, 589 | **_): # pylint: disable=W0613 590 | self.rootfs = rootfs 591 | self.binds = get_default(binds, []) 592 | self.qemu = qemu 593 | self.identity = get_default(identity, (0, 0)) 594 | 595 | uid = os.getuid() 596 | username = pwd.getpwuid(uid)[0] 597 | self.uid_range = get_default( 598 | uid_range, get_subid_range('/etc/subuid', username, uid)) 599 | self.gid_range = get_default( 600 | gid_range, get_subid_range('/etc/subgid', username, uid)) 601 | self.cwd = get_default(cwd, '/') 602 | 603 | def _callfun(self, funname, *args, **kwargs): 604 | uchroot_args = self.as_dict() 605 | uchroot_args["extra_preexec_fn"] = kwargs.pop("preexec_fn", None) 606 | uchroot_args["cwd"] = kwargs.pop("cwd", "/") 607 | 608 | kwargs["preexec_fn"] = Main(**uchroot_args) 609 | return getattr(subprocess, funname)(*args, **kwargs) 610 | 611 | def Popen(self, *args, **kwargs): # pylint: disable=C0103 612 | return self._callfun("Popen", *args, **kwargs) 613 | 614 | def call(self, *args, **kwargs): 615 | return self._callfun("call", *args, **kwargs) 616 | 617 | def check_call(self, *args, **kwargs): 618 | return self._callfun("check_call", *args, **kwargs) 619 | 620 | def check_output(self, *args, **kwargs): 621 | return self._callfun("check_output", *args, **kwargs) 622 | 623 | 624 | def parse_config(config_path): 625 | """ 626 | Open the config file as json, strip comments, load it and return the 627 | resulting dictionary. 628 | """ 629 | 630 | stripped_json_str = '' 631 | 632 | # NOTE(josh): strip comments out of the config file. 633 | with open(config_path, 'rb') as infile: 634 | for line in infile: 635 | line = re.sub('//.*$', '', line).rstrip() 636 | if line: 637 | stripped_json_str += line 638 | stripped_json_str += '\n' 639 | 640 | try: 641 | return json.loads(stripped_json_str) 642 | except (ValueError, KeyError): 643 | logger.error('Failed to decode json:%s', stripped_json_str) 644 | raise 645 | 646 | 647 | VARDOCS = { 648 | "rootfs": "The directory to chroot into", 649 | "binds": 650 | """ 651 | List of paths to bind into the new root directory. These binds are 652 | done inside a mount namespace and will not be reflected outside 653 | the process tree started by the script. 654 | """, 655 | "qemu": 656 | """ 657 | If specified, indicates the path to a qemu instance that should be bound 658 | into the mount namespace of the jail 659 | """, 660 | "identity": 661 | "After entering the jail, assume this [uid, gid]. (0, 0) for root.", 662 | "uid_range": 663 | """ 664 | uids in the namespace starting at 1 are mapped to uids outside the 665 | namespace starting with this value and up to this many ids. Note that 666 | the uid range outside the namespace must lie within the current users 667 | allowed subordinate uids. See (or modify) /etc/subid for the range 668 | available to your user. 669 | """, 670 | "gid_range": 671 | "Same as uid_map above, but for gids.", 672 | "cwd": "Set the current working directory to this inside the jail", 673 | "excbin": "The path of the program to execute", 674 | "argv": "The argument vector to expose as argv,argc to the called process", 675 | "env": 676 | """ 677 | The environment of the called process. Use an empty dictionary for an 678 | empty environment, or None to use the host environment. 679 | Any environment variable encountered as a list will be join()ed using 680 | path separator (':') 681 | """, 682 | } 683 | 684 | 685 | def dump_config(outfile): 686 | """ 687 | Dump the default configuration to ``outfile``. 688 | """ 689 | 690 | config = Main().as_dict() 691 | config.update(Exec().as_dict()) 692 | 693 | ppr = pprint.PrettyPrinter(indent=2) 694 | fieldnames = Main.get_field_names() + Exec.get_field_names() 695 | for key in fieldnames: 696 | helptext = VARDOCS.get(key, None) 697 | if helptext: 698 | for line in textwrap.wrap(helptext, 78): 699 | outfile.write('# ' + line + '\n') 700 | value = config[key] 701 | if isinstance(value, dict): 702 | outfile.write('{} = {}\n\n'.format(key, json.dumps(value, indent=2))) 703 | else: 704 | outfile.write('{} = {}\n\n'.format(key, ppr.pformat(value))) 705 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------