pax_global_header00006660000000000000000000000064144276335330014524gustar00rootroot0000000000000052 comment=bd5a5f63d72f8210d8cee76195eb9f0749e5bd70 proot-5.4.0/000077500000000000000000000000001442763353300126755ustar00rootroot00000000000000proot-5.4.0/.dockerignore000066400000000000000000000000751442763353300153530ustar00rootroot00000000000000AUTHORS contrib COPYING doc ISSUE_TEMPLATE test/docker *.rst proot-5.4.0/.github/000077500000000000000000000000001442763353300142355ustar00rootroot00000000000000proot-5.4.0/.github/workflows/000077500000000000000000000000001442763353300162725ustar00rootroot00000000000000proot-5.4.0/.github/workflows/main.yml000066400000000000000000000036141442763353300177450ustar00rootroot00000000000000name: build on: push: branches: - master pull_request: types: [opened, synchronize, reopened] jobs: build: name: "${{ matrix.SECCOMP == '1' && 'seccomp' || 'no-seccomp' }}" runs-on: ubuntu-latest env: BUILD_WRAPPER_OUT_DIR: tmp strategy: fail-fast: false matrix: SECCOMP: [ 0, 1 ] steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Install sonar-scanner and build-wrapper uses: sonarsource/sonarcloud-github-c-cpp@v1 - name: Install build dependencies run: | sudo apt-get update -qq sudo apt-get install -qq clang-tools-12 curl gdb lcov libarchive-dev libtalloc-dev sloccount strace swig uthash-dev python3-dev lzop - name: Gather analytics run: sloccount --details . - name: Build elf loader, proot, and care run: | build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make -C src loader.elf loader-m32.elf build.h build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} env CFLAGS=--coverage LDFLAGS=--coverage make -C src proot care V=1 - name: Execute test suite continue-on-error: true timeout-minutes: 10 run: | build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} env ${{ matrix.SECCOMP == '0' && 'PROOT_NO_SECCOMP=1' || '' }} PATH=/bin:/usr/bin:/sbin:/usr/sbin:$PWD/src make -C test -j $(nproc) QUIET_LOG=$PWD/test.log - name: Output test log if: always() continue-on-error: true run: ([ -f test.log ] && cat test.log) || true - name: Run sonar-scanner env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }} run: | sonar-scanner --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" proot-5.4.0/.gitignore000066400000000000000000000004211442763353300146620ustar00rootroot00000000000000*.d *.gcda *.gcno *.html *.info *.o *.1 doc/*.h *rpm-spec doc/public_html/ gcov-latest/ public/ src/.check_process_vm.res src/.check_seccomp_filter.res src/build.h src/care* src/loader* src/proot* test/* !test/*.sh.* !test/*.sh !test/*.c !test/*.py !test/*.mk !test/docker proot-5.4.0/.gitlab-ci.yml000066400000000000000000000023421442763353300153320ustar00rootroot00000000000000image: gcc:7.4.0 stages: - check - dist - coverage - static-analysis - deploy before_script: - apt-get update -qq - apt-get install -qq clang-tools-6.0 curl docutils-common gdb lcov libarchive-dev libtalloc-dev strace swig uthash-dev xsltproc check: stage: check script: - make -C src proot care - timeout --signal=SIGKILL 5m make -C test allow_failure: true dist: stage: dist script: - LDFLAGS="${LDFLAGS} -static" make -C src proot GIT=false after_script: - mkdir -p dist - cp src/proot dist/ - cd dist - sha256sum ./proot > proot.sha256sum - md5sum ./proot > proot.md5sum artifacts: paths: - dist gcov: stage: coverage script: - /bin/sh ./util/coverage.sh artifacts: paths: - gcov-latest scan-build: stage: static-analysis script: - scan-build-6.0 make -C src proot after_script: - cp -R /tmp/scan-build-* scan-build-latest artifacts: paths: - scan-build-latest pages: stage: deploy script: - /bin/sh ./util/dist.sh dependencies: - dist - gcov - scan-build artifacts: paths: - public site: stage: deploy script: - /bin/sh ./util/site.sh only: - master allow_failure: true proot-5.4.0/.gitmodules000066400000000000000000000001261442763353300150510ustar00rootroot00000000000000[submodule "lib/uthash"] path = lib/uthash url = https://github.com/proot-me/uthash proot-5.4.0/.mailmap000066400000000000000000000002211442763353300143110ustar00rootroot00000000000000Lucas Ramage <43783393+oxr463@users.noreply.github.com> Lucas Ramage proot-5.4.0/AUTHORS000066400000000000000000000021431442763353300137450ustar00rootroot00000000000000The copyright holder for PRoot and CARE is STMicroelectronics, these tools were originally developed in the Compilation Expertise Center by: Cédric VINCENT Original author, maintainer. Rémi DURAFFORT Improved the -0 feature, many experiments, bug reports, fixes, and articles. Christophe GUILLON Improved CARE, plugin experiments, bug reports, and fixes. Yves JANIN Support for the i386 architecture, many experiments and bug reports. Antoine MOYNAULT Many experiments and bug reports. Claire ROBINE Many bug fixes. Clément BAZIN Plugins interface, dependency tracker plugin, and bug reports. Christian BERTIN Valuable support. Denis FERRANTI Valuable support. Paul GHALEB User manual review. Lucas Ramage Website re-design, documentation, maintainer. proot-5.4.0/CHANGELOG.rst000066400000000000000000000116161442763353300147230ustar00rootroot00000000000000Changelog ========= All notable changes to this project will be documented in this file. The format is based on `Keep a Changelog`_, and this project adheres to `Semantic Versioning`_. Unreleased ---------- Please see `Unreleased Changes`_ for more information. 5.4.0 - 2023-05-13 ------------------ Added ~~~~~ - faccessat2 syscall - Enable SonarCloud for GitHub Actions - Include uthash v2.3.0 as submodule - Disable mixed execution with new --mixed-mode option Changed ~~~~~~~ - Rename test-0cf405b0.c to fix_memory_corruption_execve_proc_self_exe.c Fixed ~~~~~ - Android compatibility with cwd - Running test-0cf405b0 for newer versions of glibc - Running test-25069c12 and test-25069c13 on newer kernels 5.3.1 - 2022-04-24 ------------------ Changed ~~~~~~~ - Error out when trying to set PTRACE_O_TRACESECCOMP under ptrace emulation. - Set the restart_how field in a newly created child tracee. Removed ~~~~~~~ - Unnecessary dependency of PRoot on libarchive. - Changelog target from doc makefile. Fixed ~~~~~ - Incorrect year for 5.3.0 release in changelog and manual. 5.3.0 - 2022-01-04 ------------------ Added ~~~~~ - Link to repository on website. - Support for utimensat_time64 on 32bit architectures. - Install LZOP on CI for CARE archive extraction. - Enable GitHub Actions for testing. - Message for stopping and starting of tracees. - Python 3 support in tests. - Support for statx syscall. - Test case for sysexit handler. Changed ~~~~~~~ - Update wording in manual regarding rootfs. - Change restart_original_syscall to not use chained syscall. - Access sockfd in the chained getsocketname via the original version. - Pin Debian 8 for docker image. - Make sure not to fake too old an kernel release. - Ensure the stack is aligned for AArch64 and X86 for SIMD code. - Include /bin in PATH during tests. - Kernel version detection for kernels 5.0 and newer. - Allow a higher initial heap size in test. - Allow the value of AT_HWCAP to be empty. - Do not unconditionally use PTRACE_CONT when recieving a useless SECCOMP event. - Do not treat libarchive warnings as errors. - canon: call bindings substitution on '/' component of user path. Removed ~~~~~~~ - Remove special handling of syscall avoider number on ARM. - Delete roadmap.rst file. - Remove Travis CI configuration. - Remove preprocessor directives and associated code. Fixed ~~~~~ - Fchmod permissions for loader. - Test compilation on ARM. - Includes in tests. - Handling of receiving seccomp after normal ptrace event. - Waitpid on zombies. - Extraction of wrapped file. - Archive suffix handling. - Improve docker test skip detection. - Event handling on newer kernels. - Command line handler for the python extension. - Linking against the swig generated symbol for the python extension. - Linking on python 3.8 and newer. - Regression in socket name shortening. - Test caused by shell optimization. - Test failure due to increased shebang limit. - Handling of fstatat on new kernels. - Seccomp event handling logic causing sysexit events to be missed. - fake_id0: Fix POKE_MEM_ID to call poke_uint32 instead of poke_uint16. 5.2.0 - 2021-09-01 ------------------ Added ~~~~~ - GitLab CI/CD pipelines for static binaries. - Python extension. - Secure disclosure instructions. - Vagrantfiles for kernel-specific testing. - Support for Musl libc. - Use shellcheck for scripts. - link2symlink extension. - Contributor scripts care2docker.sh, and care_rearchiver.sh - Clang scan-build and gcov/lcov for source code analysis. - Trivial chroot using relative paths. - port_mapper extension. - Commandline option --kill-on-exit. - Hidden PROOT_TMPDIR option. - Support for sudo via fake_id0 extension. Changed ~~~~~~~ - Started using top-level changelog instead of individual ones. - Limit testsuite to five minutes. - Updated release instructions. - Renamed tests to test. - Replace .exe file extension with .elf for loader binaries. - Use LC_ALL instead of LANG. - Semantics for HOST_PATH extension event arguments. Removed ~~~~~~~ - Disabled, deprecated, or unreliable tests. - Drop Coverity from Travis CI. - Cross-compiling scripts for Slackware. - FHS assumptions from tests. - References to proot.me domain. Fixed ~~~~~ - Error-code handling in substitute_binding_stat. - Prevent tracees from becoming undumpable. - Merged patches for detecting kernels >= 4.8. - GIT_VERSION for development binaries. - Replace mktemp with mkstemp. - File permissions for test scripts. - Filter renamteat2 syscall. - Honor GNU standards regarding DESTDIR variable. - Cleanup tmp on non-ext file systems. - Reallocation of heap for CLONE_VM on execve syscall. - Non-executable stack for binaries. .. _Unreleased Changes: https://github.com/proot-me/proot/compare/v5.4.0...master .. _Keep a Changelog: https://keepachangelog.com/en/1.0.0 .. _Semantic Versioning: https://semver.org/spec/v2.0.0.html proot-5.4.0/COPYING000066400000000000000000000432541442763353300137400ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. proot-5.4.0/HACKING.rst000066400000000000000000000032321442763353300144730ustar00rootroot00000000000000============================== Contributing to PRoot / CARE ============================== ----------------------------------------------------------- An introduction to contributing to PRoot / CARE development ----------------------------------------------------------- The PRoot Team welcomes, and depends, on contributions from students and collaborators in the open source and academic communities. Contributions can be made in a number of ways, a few examples are: - Code patches via pull requests - Documentation improvements - Bug reports and patch reviews Reporting an Issue ================== Please include as much detail as you can. Let us know your host kernel version, e.g, :code:`uname -a`, host/guest distribution, e.g. :code:`cat /etc/os-release`, and the :code:`PRoot`/:code:`CARE` version number. If you get an error please include the full error and/or traceback. Issues are tracked on GitHub at , or can also be sent via the `mailing list `_. Submitting Pull Requests ======================== Once you are happy with your changes or you are ready for some feedback, push it to your fork and send a pull request. For a change to be accepted it will most likely need to have updated tests and/or documentation. New features **require** additional tests in order to be included in a future release. Resources ========= - `Contributing to Open Source Projects `_ - `How To Ask Questions The Smart Way `_ - `Indentation Style - Wikipedia `_ proot-5.4.0/ISSUE_TEMPLATE000066400000000000000000000004231442763353300150020ustar00rootroot00000000000000## Expected Behavior ## Actual Behavior ## Steps to Reproduce the Problem 1. 2. 3. ## Specifications - Proot/Care version: - Kernel version: - Host distribution: - Guest distribution: ## Command Output ```terminal Inline log ``` -OR- [Attached log]() proot-5.4.0/README.rst000066400000000000000000000040351442763353300143660ustar00rootroot00000000000000PRoot ===== *chroot, mount --bind, and binfmt_misc without privilege/setup for Linux* Build status ============ **Please take the PRoot Usage Survey for 2023!** .. image:: https://img.shields.io/badge/survey-2023-green?style=flat-square :target: https://www.surveymonkey.com/r/7GVXS7W -- .. image:: https://img.shields.io/gitlab/pipeline/proot/proot.svg?label=gitlab-ci&style=flat-square :target: https://gitlab.com/proot/proot/pipelines .. image:: https://img.shields.io/badge/scan--build-latest-yellow.svg?style=flat-square :target: https://proot.gitlab.io/proot/reports/scan-build .. image:: https://img.shields.io/badge/lcov-latest-6688D4.svg?style=flat-square :target: https://proot.gitlab.io/proot/reports/lcov .. image:: https://img.shields.io/cii/summary/2444.svg?label=cii-best-practices&style=flat-square :target: https://bestpractices.coreinfrastructure.org/projects/2444 .. image:: https://img.shields.io/badge/DOI-10.5281%2Fzenodo.5371409-blue?style=flat-square :target: https://doi.org/10.5281/zenodo.5371409 Compiling ========= The following commands can be used to compile PRoot and CARE:: make -C src loader.elf loader-m32.elf build.h # first build the config and loader make -C src proot care # then compile PRoot and CARE make -C test # run test suite |asciicast| .. |asciicast| image:: https://asciinema.org/a/315367.svg :target: https://asciinema.org/a/315367 Dependencies ============ - `libarchive `_ - `libtalloc `_ - `uthash `_ (only required for building CARE) Manuals ======= - `PRoot `_ - `CARE `_ Support ======= - `Mailing List `_ - `Forum `_ - `Chat `_ License ======= SPDX-License-Identifier: `GPL-2.0-or-later `_ proot-5.4.0/contrib/000077500000000000000000000000001442763353300143355ustar00rootroot00000000000000proot-5.4.0/contrib/care2docker.sh000077500000000000000000000027121442763353300170620ustar00rootroot00000000000000#!/bin/bash # shellcheck disable=SC1003 # shellcheck disable=SC2016 set -euo pipefail IFS=$'\n\t' #### # # CARE archive to Docker image converter. # # There is absolutely **no garanty** this works for every CARE archive. # This essentially writes the archive's rootfs on top of the base's so there # might be some cases where this breaks. # Just here to simplify basic conversions. # # Prerequisites: # - a folder containing a CARE archive # - docker installed # Usage: # - ./care2docker.sh # # Copyright Jonathan Passerat-Palmbach, 2016 # #### archive=$(readlink -f "$1") basedir=$(dirname "${archive}") pushd "${basedir}" "${archive}" popd archive_name=$(basename "${archive}" | cut -d '.' -f1) tar cf "${basedir}/${archive_name}_rootfs.tar" --xform="s,$(echo "${basedir}" | cut -d '/' -f 2-)/${archive_name}/rootfs/,," "${basedir}/${archive_name}"/rootfs/* cat << EOF > "${basedir}"/Dockerfile FROM debian:jessie ADD "${archive_name}_rootfs.tar" / $(echo -n "ENV " ; grep -e \''.*=.*'\'' \\' "${basedir}/${archive_name}/re-execute.sh" | sed -e "s/.\(.*\)=/\1='/g" | sed -e '$ s/ \\//g') WORKDIR $(grep -e '^-w '\''.*'\'' \\' "${basedir}/${archive_name}/re-execute.sh" | sed -e 's/^-w '\''\(.*\)'\'' \\/\1/g') ENTRYPOINT ["$(grep -e '\[ $nbargs -ne 0 \] || set -- \\' -A 1 "${basedir}/${archive_name}/re-execute.sh" | tail -n1 | sed -e 's/'\''\(.*\)'\'' \\/\1/g' )"] EOF cd "${basedir}" || exit 1 docker build -t "${archive_name}" . proot-5.4.0/contrib/care_rearchiver.sh000077500000000000000000000031611442763353300200210ustar00rootroot00000000000000#!/bin/bash set -euo pipefail IFS=$'\n\t' #### # # CARE re-archiver. Updates a CARE archive in .tgz.bin format. # Prerequisites: # - a folder containing the extraction of the CARE archive # Usage: # - add / remove / modify files in the folder # - run care_rearchiver.sh archive.tgz.bin archive # # Copyright Jonathan Passerat-Palmbach, 2016 # #### archive="$1" mode="$2" archive_size=$(printf "%d" "0x$(tail -c 8 "${archive}" | hexdump -e '16/1 "%02X"' | cut -d ' ' -f1)") file_size=$(stat -c %s "${archive}") footer_size=21 extracting_program_size=$((file_size - archive_size - footer_size)) archive_no_bin=$(basename "${archive}" .bin) function unarchive() { dd if="${archive}" of="${archive_no_bin}" bs=1 skip="${extracting_program_size}" count="${archive_size}" tar zxf "${archive_no_bin}" } function archive() { folder_name=$(basename "${archive_no_bin}" .tgz) new_archive="${folder_name}_new.tgz.bin" tar zcf "${archive_no_bin}" "${folder_name}" new_archive_size=$(stat -c %s "${archive_no_bin}") # add extracter dd if="${archive}" of="${new_archive}" bs=1 skip=0 count=${extracting_program_size} # add new archive dd if="${archive_no_bin}" of="${new_archive}" bs=1 skip=0 seek=${extracting_program_size} count="${new_archive_size}" ## add footer # I_LOVE_PIZZA echo -en \\x49\\x5f\\x4c\\x4f\\x56\\x45\\x5f\\x50\\x49\\x5a\\x5a\\x41\\x00 >> "${new_archive}" # size echo -en "$(printf "%016X" "${new_archive_size}" | sed -e 's/\([0-9A-F]\{2\}\)/\\\x\1/g')" >> "${new_archive}" chmod +x "${new_archive}" rm "${archive_no_bin}" } if [ "${mode}" == "archive" ]; then archive else unarchive fi proot-5.4.0/doc/000077500000000000000000000000001442763353300134425ustar00rootroot00000000000000proot-5.4.0/doc/GNUmakefile000066400000000000000000000025441442763353300155210ustar00rootroot00000000000000DOC_DIR = . SITE_DIR = ${DOC_DIR}/public_html SUFFIX += RST2MAN = rst2man${SUFFIX} RST2XML = rst2xml${SUFFIX} RST2HTML = rst2html${SUFFIX} OUTPUTS = proot/man.1 proot.h proot/rpm-spec proot/index.html care.h care/index.html all: $(OUTPUTS) # dist dist: $(OUTPUTS) @mkdir -p ${SITE_DIR}/care @cp care/index.html ${SITE_DIR}/care/index.html @cp care/stylesheets/website.css ${SITE_DIR}/care/care.css @cp stylesheets/website.css ${SITE_DIR}/care @cp proot/index.html ${SITE_DIR}/index.html @cp proot/stylesheets/website.css ${SITE_DIR}/proot.css @cp stylesheets/website.css ${SITE_DIR}/ @cp ${DOC_DIR}/../COPYING ${SITE_DIR}/ @cp ${DOC_DIR}/../README.rst ${SITE_DIR}/ @cp ${DOC_DIR}/../HACKING.rst ${SITE_DIR}/ %/man.1: %/manual.rst $(RST2MAN) $< $@ %.xml: %.rst $(RST2XML) --no-doctype $< $@ %.html: %.rst $(RST2HTML) $< $@ # Workaround to avoid unescaped C character. %/manual-quoted.rst: %/manual.rst sed 's/"/\\\\"/g' $^ > $@ %.h: %/stylesheets/cli.xsl %/manual-quoted.xml xsltproc --output $@ $^ %/rpm-spec: %/stylesheets/rpm-spec.xsl %/manual.xml xsltproc --output $@ $^ echo "* $(shell date +'%a %b %d %Y') PRoot Team " >> $@ %/index.html: stylesheets/website.xsl %/stylesheets/website.xsl %/manual.xml xsltproc --output $@ $*/stylesheets/website.xsl $*/manual.xml clean: rm -f *.xml $(OUTPUTS) *-quoted.* public_html proot-5.4.0/doc/care/000077500000000000000000000000001442763353300143545ustar00rootroot00000000000000proot-5.4.0/doc/care/manual.rst000066400000000000000000000377061442763353300164000ustar00rootroot00000000000000====== CARE ====== ------------------------------------------------- Comprehensive Archiver for Reproducible Execution ------------------------------------------------- :Date: 2023-05-13 :Version: 2.3.0 :Manual section: 1 Synopsis ======== **care** [*option*] ... *command* Description =========== CARE monitors the execution of the specified command to create an *archive* that contains all the material required to *re-execute* it in the same context. That way, the command will be reproducible everywhere, even on Linux systems that are supposed to be not compatible with the original Linux system. CARE is typically useful to get reliable bug reports, demonstrations, `artifact evaluation`_, tutorials, portable applications, minimal rootfs, file-system coverage, ... By design, CARE does not record events at all. Instead, it archives environment variables and accessed file-system components -- before modification -- during the so-called *initial* execution. Then, to reproduce this execution, the ``re-execute.sh`` script embedded into the archive restores the environment variables and relaunches the command confined into the saved file-system. That way, both *initial* and *reproduced* executions should produce the same results as they use the same context, assuming they do not rely on external events -- like key strokes or network packets -- or that these external events are replayed manually or automatically, using umockdev_ for instance. That means it is possible to alter explicitly the reproduced executions by changing content of the saved file-system, or by replaying different external events. .. _umockdev: https://github.com/martinpitt/umockdev .. _artifact evaluation: http://www.artifact-eval.org Privacy ------- To ensure that no sensitive file can possibly leak into the archive, CARE *conceals* recursively the content of ``$HOME`` and ``/tmp``, that is, they appear empty during the original execution. Although, for consistency reasons, the content of ``$PWD`` is *revealed* even if it is nested into the two previous paths. As a consequence, a program executed under CARE may behave unexpectedly because a required path is not accessible anymore. In this case, such a path has to be revealed explicitly. For details, see the options ``--concealed-path`` and ``--revealed-path``, and the file ``concealed-accesses.txt`` as well. It is advised to inspect the archived content before sharing it. Options ======= The command-line interface is composed of two parts: first CARE's options, then the command to launch. This section describes the options supported by CARE, that is, the first part of its command-line interface. -o path, --output=path Archive in *path*, its suffix specifies the format. The suffix of *path* is used to select the archive format, it can be one of the following: ========= ======================================================== suffix comment ========= ======================================================== / don't archive, copy into the specified directory instead .tar most common archive format .cpio most portable archive format, it can archive sockets too ?.gz most common compression format, but slow ?.lzo fast compression format, but uncommon ?.bin see ``Self-extracting format`` section ?.?.bin see ``Self-extracting format`` section .bin see ``Self-extracting format`` section .raw recommended archive format, use `care -x` to extract ========= ======================================================== where "?" means the suffix must be combined with another one. For examples: ".tar.lzo", ".cpio.gz", ".tar.bin", ".cpio.lzo.bin", ... If this option is not specified, the default output path is ``care-.bin`` or ``care-.raw``, depending on whether CARE was built with self-extracting format support or not. -c path, --concealed-path=path Make *path* content appear empty during the original execution. Some paths may contain sensitive data that should never be archived. This is typically the case for most of the files in: * $HOME * /tmp That's why these directories are recursively *concealed* from the original execution, unless the ``-d`` option is specified. Concealed paths appear empty during the original execution, as a consequence their original content can't be accessed nor archived. -r path, --revealed-path=path Make *path* content accessible when nested in a concealed path. Concealed paths might make the original execution with CARE behave differently from an execution without CARE. For example, a lot of ``No such file or directory`` errors might appear. The solution is to *reveal* recursively any required paths that would be nested into a *concealed* path. Note that ``$PWD`` is *revealed*, unless the ``-d`` option is specified. -p path, --volatile-path=path Don't archive *path* content, reuse actual *path* instead. Some paths contain only communication means with programs that can't be monitored by CARE, like the kernel or a remote server. Such paths are said *volatile*; they shouldn't be archived, instead they must be accessed from the *actual* rootfs during the re-execution. This is typically the case for the following pseudo file-systems, sockets, and authority files: * /dev * /proc * /sys * /run/shm * /tmp/.X11-unix * /tmp/.ICE-unix * $XAUTHORITY * $ICEAUTHORITY * /var/run/dbus/system_bus_socket * /var/tmp/kdecache-$LOGNAME This is also typically the case for any other fifos or sockets. These paths are considered *volatile*, unless the ``-d`` option is specified. -e name, --volatile-env=name Don't archive *name* env. variable, reuse actual value instead. Some environment variables are used to communicate with programs that can't be monitored by CARE, like remote servers. Such environment variables are said *volatile*; they shouldn't be archived, instead they must be accessed from the *actual* environment during the re-execution. This is typically the case for the following ones: * DISPLAY * http_proxy * https_proxy * ftp_proxy * all_proxy * HTTP_PROXY * HTTPS_PROXY * FTP_PROXY * ALL_PROXY * DBUS_SESSION_BUS_ADDRESS * SESSION_MANAGER * XDG_SESSION_COOKIE These environment variables are considered *volatile*, unless the ``-d`` option is specified. -m value, --max-archivable-size=value Set the maximum size of archivable files to *value* megabytes. To keep the CPU time and the disk space used by the archiver reasonable, files whose size exceeds *value* megabytes are truncated down to 0 bytes. The default is 1GB, unless the ``-d`` option is specified. A negative *value* means no limit. -d, --ignore-default-config Don't use the default options. -x file, --extract=file Extract content of the archive *file*, then exit. It is recommended to use this option to extract archives created by CARE because most extracting tools -- that are not based on libarchive -- are too limited to extract them correctly. -v value, --verbose=value Set the level of debug information to *value*. The higher the integer *value* is, the more detailed debug information is printed to the standard error stream. A negative *value* makes CARE quiet except on fatal errors. -V, --version, --about Print version, copyright, license and contact, then exit. -h, --help, --usage Print the user manual, then exit. Exit Status =========== If an internal error occurs, ``care`` returns a non-zero exit status, otherwise it returns the exit status of the last terminated program. When an error has occurred, the only way to know if it comes from the last terminated program or from ``care`` itself is to have a look at the error message. Files ===== The output archive contains the following files: ``re-execute.sh`` start the re-execution of the initial command as originally specified. It is also possible to specify an alternate command. For example, assuming ``gcc`` was archived, it can be re-invoked differently: $ ./re-execute.sh gcc --version gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2 $ echo 'int main(void) { return puts(\"OK\"); }' > rootfs/foo.c $ ./re-execute.sh gcc -Wall /foo.c $ foo.c: In function "main": $ foo.c:1:1: warning: implicit declaration of function "puts" ``rootfs/`` directory where all the files used during the original execution were archived, they will be required for the reproduced execution. ``proot`` virtualization tool invoked by re-execute.sh to confine the reproduced execution into the rootfs. It also emulates the missing kernel features if needed. ``concealed-accesses.txt`` list of accessed paths that were concealed during the original execution. Its main purpose is to know what are the paths that should be revealed if the the original execution didn't go as expected. It is absolutely useless for the reproduced execution. Limitations =========== It's not possible to use GDB, strace, or any programs based on *ptrace* under CARE yet. This latter is also based on this syscall, but the Linux kernel allows only one *ptracer* per process. This will be fixed in a future version of CARE thanks to a ptrace emulator. Example ======= In this example, Alice wants to report to Bob that the compilation of PRoot v2.4 raises an unexpected warning:: alice$ make -C PRoot-2.4/src/ make: Entering directory `PRoot-2.4/src' [...] CC path/proc.o ./path/proc.c: In function 'readlink_proc': ./path/proc.c:132:3: warning: ignoring return value of 'strtol' [...] Technically, Alice uses Ubuntu 11.04 for x86, whereas Bob uses Slackware 13.37 on x86_64. Both distros are supposed to be shipped with GCC 4.5.2, however Bob is not able to reproduce this issue on his system:: bob$ make -C PRoot-2.4/src/ make: Entering directory `PRoot-2.4/src' [...] CC path/proc.o [...] Since they don't have much time to investigate this issue by iterating between each other, they decide to use CARE. First, Alice prepends ``care`` to her command:: alice$ care make -C PRoot-2.4/src/ care info: concealed path: $HOME care info: concealed path: /tmp care info: revealed path: $PWD care info: ---------------------------------------------------------------------- make: Entering directory `PRoot-2.4/src' [...] CC path/proc.o ./path/proc.c: In function 'readlink_proc': ./path/proc.c:132:3: warning: ignoring return value of 'strtol' [...] care info: ---------------------------------------------------------------------- care info: Hints: care info: - search for "conceal" in `care -h` if the execution didn't go as expected. care info: - use `./care-130213072430.bin` to extract the output archive. Then she sends the ``care-130213072430.bin`` file to Bob. Now, he should be able to reproduce her issue on his system:: bob$ ./care-130213072430.bin [...] bob$ ./care-130213072430/re-execute.sh make: Entering directory `PRoot-2.4/src' [...] CC path/proc.o ./path/proc.c: In function 'readlink_proc': ./path/proc.c:132:3: warning: ignoring return value of 'strtol' [...] So far so good! This compiler warning doesn't make sense to Bob since ``strtol`` is used there to check a string format; the return value is useless, only the ``errno`` value matters. Further investigations are required, so Bob re-execute Alice's GCC differently to get more details:: bob$ ./care-130213072430/re-execute.sh gcc --version gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2 Copyright (C) 2010 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. The same invocation on his system returns something slightly different:: bob$ gcc --version gcc (GCC) 4.5.2 Copyright (C) 2010 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. This confirms that both GCC versions are the same, however Alice's one seems to have been modified by Ubuntu. Although, according to the web page related to this Ubuntu package [#]_, no changes regarding ``strtol`` were made. So Bob decides to search into the files coming from Alice's system, that is, the ``rootfs`` directory in the archive:: bob$ grep -wIrl strtol ./care-130213072430/rootfs care-130213072430/rootfs/usr/include/inttypes.h care-130213072430/rootfs/usr/include/stdlib.h [...] Here, the file ``usr/include/stdlib.h`` contains a declaration of ``strtol`` with the "warn unused result" attribute. On Ubuntu, this file belongs to the EGLIBC package, and its related web page [#]_ shows that this attribute was actually wrongly introduced by the official EGLIBC developers. Ultimately Bob should notify them in this regard. Thanks to CARE, Bob was able to reproduce the issue reported by Alice without effort. For investigations purpose, he was able to re-execute programs differently and to search into the relevant files. .. [#] https://launchpad.net/ubuntu/oneiric/+source/gcc-4.5/4.5.2-8ubuntu4 .. [#] https://launchpad.net/ubuntu/+source/eglibc/2.13-0ubuntu13.2 Self-extracting format ====================== The self-extracting format used by CARE starts with an extracting program, followed by a regular archive, and it ends with a special footer. This latter contains the signature "I_LOVE_PIZZA" followed by the size of the embedded archive:: +------------------------+ | extracting program | +------------------------+ | | | embedded archive | | | +------------------------+ | uint8_t signature[13] | | uint64_t archive_size | # big-endian +------------------------+ The command ``care -x`` can be used against a self-extracting archive, even if they were not build for the same architecture. For instance, a self-extracting archive produced for ARM can be extracted with a ``care`` program built for x86_64, and vice versa. It is also possible to use external tools to extract the embedded archive, for example:: $ care -o foo.tar.gz.bin /usr/bin/echo OK [...] OK [...] $ hexdump -C foo.tar.gz.bin | tail -3 0015b5b0 00 b0 2e 00 49 5f 4c 4f 56 45 5f 50 49 5a 5a 41 |....I_LOVE_PIZZA| 0015b5c0 00 00 00 00 00 00 12 b4 13 |.........| 0015b5c9 $ file_size=`stat -c %s foo.tar.gz.bin` $ archive_size=$((16#12b413)) $ footer_size=21 $ skip=$(($file_size - $archive_size - $footer_size)) $ dd if=foo.tar.gz.bin of=foo.tar.gz bs=1 skip=$skip count=$archive_size 1225747+0 records in 1225747+0 records out 1225747 bytes (1.2 MB) copied, 2.99546 s, 409 kB/s $ file foo.tar.gz foo.tar.gz: gzip compressed data, from Unix $ tar -tzf foo.tar.gz foo/rootfs/usr/ [...] foo/re-execute.sh foo/README.txt foo/proot Downloads ========= CARE is heavily based on PRoot_, that's why they are both hosted in the same repository: https://github.com/proot-me/proot. Previous CARE releases were packaged at https://github.com/proot-me/proot-static-build/releases, however, that repository has since been archived. The latest builds can be found under the job artifacts for the `GitLab CI/CD Pipelines `_ for each commit. .. _PRoot: https://proot-me.github.io Colophon ======== Visit https://proot-me.github.io/care for help, bug reports, suggestions, patches, ... Copyright (C) 2023 PRoot Developers, licensed under GPL v2 or later. :: _____ ____ _____ ____ / __/ __ | __ \ __| / /_/ | / __| \_____|__|__|__|__\____| proot-5.4.0/doc/care/stylesheets/000077500000000000000000000000001442763353300167305ustar00rootroot00000000000000proot-5.4.0/doc/care/stylesheets/cli.xsl000066400000000000000000000124121442763353300202270ustar00rootroot00000000000000 /* This file is automatically generated from the documentation. EDIT AT YOUR OWN RISK. */ #ifndef CARE_CLI_H #define CARE_CLI_H #include "cli/cli.h" #ifndef VERSION #define VERSION "" #endif #define CARE_MAX_SIZE 1024 static int pre_initialize_bindings(Tracee *, const Cli *, size_t, char *const *, size_t); static int post_initialize_bindings(Tracee *, const Cli *, size_t, char *const *, size_t); static Cli care_cli = { .version = VERSION, .name = "care", .pre_initialize_bindings = pre_initialize_bindings, .post_initialize_bindings = post_initialize_bindings, .options = { END_OF_OPTIONS, }, }; #endif /* CARE_CLI_H */ .subtitle = " ", .synopsis = " ", .colophon = " ", .logo = "\ ", static char const *default_concealed_paths[] = { NULL, }; static char const *default_revealed_paths[] = { NULL, }; static char const *default_volatile_paths[] = { NULL, }; static char const *default_volatile_envars[] = { NULL, }; { .class = " ", .arguments = { { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_ , .description = " ", .detail = NULL, }, * * { .name = " ", .separator = ' ', .value = " " \0', .value = NULL }, static int handle_option_ (Tracee *tracee, const Cli *cli, const char *value); proot-5.4.0/doc/care/stylesheets/website.css000066400000000000000000000002421442763353300211020ustar00rootroot00000000000000@import url("website.css"); h1 { color: SkyBlue; } #contents a:hover { border-bottom: 2px solid SkyBlue; } a { border-bottom: 1px solid SkyBlue; } proot-5.4.0/doc/care/stylesheets/website.xsl000066400000000000000000000041551442763353300211270ustar00rootroot00000000000000 <xsl:value-of select="document/title" /> — <xsl:value-of select="document/subtitle" />

CARE

Support

Feel free to send your questions, bug reports, suggestions, and patches to the mailing-list or to the forum, or chat with us on Gitter; but please be sure that your answer isn't in the user manual first.

top
proot-5.4.0/doc/howto-devel.rst000066400000000000000000000012331442763353300164300ustar00rootroot00000000000000How to setup a development environment for PRoot? ================================================= This document provides instructions for preparing a system for developing PRoot and CARE. Docker ------ The following command will attempt to build an image for each supported distribution:: make -C test check-test-docker.sh V=1 Vagrant ------- **Note**: this requires installing the `vagrant-sshfs`_ plugin. .. _vagrant-sshfs: https://github.com/dustymabe/vagrant-sshfs The following command will initialize a virtual machine for each supported distribution:: for distro in alpine centos debian; do cd "test/vagrant/${distro}" vagrant up done proot-5.4.0/doc/howto-release.rst000066400000000000000000000031151442763353300167520ustar00rootroot00000000000000How to make a release of PRoot? =============================== This document summarizes checks that must be performed before releasing PRoot or CARE. Checks ------ + Sanity checks: * All supported atchitectures and distributions both with and without seccomp support enabled: make -C test make -C test memcheck CFLAGS=-fsanitize=address LDFLAGS=-lasan make -C test V=1 2>&1 | grep talloc + Functional checks: * No regressions must appear with respect to :code:`test/validation.mk` and to the configurations tested in the previous release (:code:`git tag -l`). + Performance checks: * The following command must not suffer from unexpected performance regression:: time proot -R / perl -e 'system("/usr/bin/true") for (1..10000)' where :code:`/usr/bin/true` is a symlink to :code:`/bin/true`. + Static analysis: :code:`gcov`/:code:`lcov` and clang :code:`scan-build` must not report new issues. All shell scripts must pass :code:`shellcheck`. Static Binaries --------------- The following commands will generate statically-linked binaries which can be optionally distributed for each release:: make -C src clean loader.elf loader-m32.elf build.h LDFLAGS="${LDFLAGS} -static" make -C src proot care Documentation Update -------------------- 1. Update the :code:`doc/changelog.rst` file. 2. Update the release number in the :code:`doc/proot/manual.rst` file. 3. Generate the documentation:: make -C doc 4. Regenerate the website:: SITE_DIR=../../proot-me.github.io make -eC doc dist # relative to doc directory proot-5.4.0/doc/proot/000077500000000000000000000000001442763353300146055ustar00rootroot00000000000000proot-5.4.0/doc/proot/manual.rst000066400000000000000000000553301442763353300166220ustar00rootroot00000000000000======= PRoot ======= ------------------------------------------------------------------------- ``chroot``, ``mount --bind``, and ``binfmt_misc`` without privilege/setup ------------------------------------------------------------------------- :Date: 2023-05-13 :Version: 5.4.0 :Manual section: 1 Synopsis ======== **proot** [*option*] ... [*command*] Description =========== PRoot is a user-space implementation of ``chroot``, ``mount --bind``, and ``binfmt_misc``. This means that users don't need any privileges or setup to do things like using an arbitrary directory as the new root filesystem, making files accessible somewhere else in the filesystem hierarchy, or executing programs built for another CPU architecture transparently through QEMU user-mode. Also, developers can use PRoot as a generic Linux process instrumentation engine thanks to its extension mechanism, see CARE_ for an example. Technically PRoot relies on ``ptrace``, an unprivileged system-call available in every Linux kernel. The new root file-system, a.k.a *guest rootfs*, typically contains a Linux distribution. By default PRoot confines the execution of programs to the guest rootfs only, however users can use the built-in *mount/bind* mechanism to access files and directories from the actual root file-system, a.k.a *host rootfs*, just as if they were part of the guest rootfs. When the guest Linux distribution is made for a CPU architecture incompatible with the host one, PRoot uses the CPU emulator QEMU user-mode to execute transparently guest programs. It's a convenient way to develop, to build, and to validate any guest Linux packages seamlessly on users' computer, just as if they were in a *native* guest environment. That way all of the cross-compilation issues are avoided. PRoot can also *mix* the execution of host programs and the execution of guest programs emulated by QEMU user-mode. This is useful to use host equivalents of programs that are missing from the guest rootfs and to speed up build-time by using cross-compilation tools or CPU-independent programs, like interpreters. It is worth noting that the guest kernel is never involved, regardless of whether QEMU user-mode is used or not. Technically, when guest programs perform access to system resources, PRoot translates their requests before sending them to the host kernel. This means that guest programs can use host resources (devices, network, ...) just as if they were "normal" host programs. .. _CARE: https://proot-me.github.io/care Options ======= The command-line interface is composed of two parts: first PRoot's options (optional), then the command to launch (``/bin/sh`` if not specified). This section describes the options supported by PRoot, that is, the first part of its command-line interface. Regular options --------------- -r path, --rootfs=path Use *path* as the new guest root file-system, default is ``/``. The specified *path* typically contains a Linux distribution where all new programs will be confined. The default rootfs is ``/`` when none is specified, this makes sense when the bind mechanism is used to relocate host files and directories, see the ``-b`` option and the ``Examples`` section for details. It is recommended to use the ``-R`` or ``-S`` options instead. -b path, --bind=path, -m path, --mount=path Make the content of *path* accessible in the guest rootfs. This option makes any file or directory of the host rootfs accessible in the confined environment just as if it were part of the guest rootfs. By default the host path is bound to the same path in the guest rootfs but users can specify any other location with the syntax: ``-b *host_path*:*guest_location*``. If the guest location is a symbolic link, it is dereferenced to ensure the new content is accessible through all the symbolic links that point to the overlaid content. In most cases this default behavior shouldn't be a problem, although it is possible to explicitly not dereference the guest location by appending it the ``!`` character: ``-b *host_path*:*guest_location!*``. -q command, --qemu=command Execute guest programs through QEMU as specified by *command*. Each time a guest program is going to be executed, PRoot inserts the QEMU user-mode *command* in front of the initial request. That way, guest programs actually run on a virtual guest CPU emulated by QEMU user-mode. The native execution of host programs is still effective and the whole host rootfs is bound to ``/host-rootfs`` in the guest environment. -w path, --pwd=path, --cwd=path Set the initial working directory to *path*. Some programs expect to be launched from a given directory but do not perform any ``chdir`` by themselves. This option avoids the need for running a shell and then entering the directory manually. -v value, --verbose=value Set the level of debug information to *value*. The higher the integer *value* is, the more detailed debug information is printed to the standard error stream. A negative *value* makes PRoot quiet except on fatal errors. -V, --version, --about Print version, copyright, license and contact, then exit. -h, --help, --usage Print the version and the command-line usage, then exit. Extension options ----------------- The following options enable built-in extensions. Technically developers can add their own features to PRoot or use it as a Linux process instrumentation engine thanks to its extension mechanism, see the sources for further details. -k string, --kernel-release=string Make current kernel appear as kernel release *string*. If a program is run on a kernel older than the one expected by its GNU C library, the following error is reported: "FATAL: kernel too old". To be able to run such programs, PRoot can emulate some of the features that are available in the kernel release specified by *string* but that are missing in the current kernel. -0, --root-id Make current user appear as "root" and fake its privileges. Some programs will refuse to work if they are not run with "root" privileges, even if there is no technical reason for that. This is typically the case with package managers. This option allows users to bypass this kind of limitation by faking the user/group identity, and by faking the success of some operations like changing the ownership of files, changing the root directory to ``/``, ... Note that this option is quite limited compared to ``fakeroot``. -i string, --change-id=string Make current user and group appear as *string* "uid:gid". This option makes the current user and group appear as *uid* and *gid*. Likewise, files actually owned by the current user and group appear as if they were owned by *uid* and *gid* instead. Note that the ``-0`` option is the same as ``-i 0:0``. -p string, --port=string Map ports to others with the syntax as *string* "port_in:port_out ...". This option makes PRoot intercept bind and connect system calls, and change the port they use. The port map is specified with the syntax: ``-b *port_in*:*port_out*``. For example, an application that runs a MySQL server binding to 5432 wants to cohabit with other similar application, but doesn't have an option to change its port. PRoot can be used here to modify this port: ``proot -p 5432:5433 myapplication``. With this command, the MySQL server will be bound to the port 5433. This command can be repeated multiple times to map multiple ports. -n, --netcoop Activates the network cooperation mode. This option makes PRoot intercept bind() system calls and change the port they are binding to to 0. With this, the system will allocate an available port. Each time this is done, a new entry is added to the port mapping entries, so that corresponding connect() system calls use the same resulting port. Alias options ------------- The following options are aliases for handy sets of options. -R path Alias: ``-r *path*`` + a couple of recommended ``-b``. Programs isolated in *path*, a guest rootfs, might still need to access information about the host system, as it is illustrated in the ``Examples`` section of the manual. These host information are typically: user/group definition, network setup, run-time information, users' files, ... On all Linux distributions, they all lie in a couple of host files and directories that are automatically bound by this option: * /etc/host.conf * /etc/hosts * /etc/hosts.equiv * /etc/mtab * /etc/netgroup * /etc/networks * /etc/passwd * /etc/group * /etc/nsswitch.conf * /etc/resolv.conf * /etc/localtime * /dev/ * /sys/ * /proc/ * /tmp/ * /run/ * /var/run/dbus/system_bus_socket * $HOME * *path* -S path Alias: ``-0 -r *path*`` + a couple of recommended ``-b``. This option is useful to safely create and install packages into the guest rootfs. It is similar to the ``-R`` option except it enables the ``-0`` option and binds only the following minimal set of paths to avoid unexpected changes on host files: * /etc/host.conf * /etc/hosts * /etc/nsswitch.conf * /etc/resolv.conf * /dev/ * /sys/ * /proc/ * /tmp/ * /run/shm * $HOME * *path* Exit Status =========== If an internal error occurs, ``proot`` returns a non-zero exit status, otherwise it returns the exit status of the last terminated program. When an error has occurred, the only way to know if it comes from the last terminated program or from ``proot`` itself is to have a look at the error message. Files ===== PRoot reads links in ``/proc//fd/`` to support `openat(2)`-like syscalls made by the guest programs. Examples ======== In the following examples the directories ``/mnt/slackware-8.0`` and ``/mnt/armslack-12.2/`` contain a Linux distribution respectively made for x86 CPUs and ARM CPUs. ``chroot`` equivalent --------------------- To execute a command inside a given Linux distribution, just give ``proot`` the path to the guest rootfs followed by the desired command. The example below executes the program ``cat`` to print the content of a file:: proot -r /mnt/slackware-8.0/ cat /etc/motd Welcome to Slackware Linux 8.0 The default command is ``/bin/sh`` when none is specified. Thus the shortest way to confine an interactive shell and all its sub-programs is:: proot -r /mnt/slackware-8.0/ $ cat /etc/motd Welcome to Slackware Linux 8.0 ``mount --bind`` equivalent --------------------------- The bind mechanism enables one to relocate files and directories. This is typically useful to trick programs that perform access to hard-coded locations, like some installation scripts:: proot -b /tmp/alternate_opt:/opt $ cd to/sources $ make install [...] install -m 755 prog "/opt/bin" [...] # prog is installed in "/tmp/alternate_opt/bin" actually As shown in this example, it is possible to bind over files not even owned by the user. This can be used to *overlay* system configuration files, for instance the DNS setting:: ls -l /etc/hosts -rw-r--r-- 1 root root 675 Mar 4 2011 /etc/hosts :: proot -b ~/alternate_hosts:/etc/hosts $ echo '1.2.3.4 google.com' > /etc/hosts $ resolveip google.com IP address of google.com is 1.2.3.4 $ echo '5.6.7.8 google.com' > /etc/hosts $ resolveip google.com IP address of google.com is 5.6.7.8 Another example: on most Linux distributions ``/bin/sh`` is a symbolic link to ``/bin/bash``, whereas it points to ``/bin/dash`` on Debian and Ubuntu. As a consequence a ``#!/bin/sh`` script tested with Bash might not work with Dash. In this case, the binding mechanism of PRoot can be used to set non-disruptively ``/bin/bash`` as the default ``/bin/sh`` on these two Linux distributions:: proot -b /bin/bash:/bin/sh [...] Because ``/bin/sh`` is initially a symbolic link to ``/bin/dash``, the content of ``/bin/bash`` is actually bound over this latter:: proot -b /bin/bash:/bin/sh $ md5sum /bin/sh 089ed56cd74e63f461bef0fdfc2d159a /bin/sh $ md5sum /bin/bash 089ed56cd74e63f461bef0fdfc2d159a /bin/bash $ md5sum /bin/dash 089ed56cd74e63f461bef0fdfc2d159a /bin/dash In most cases this shouldn't be a problem, but it is still possible to strictly bind ``/bin/bash`` over ``/bin/sh`` -- without dereferencing it -- by specifying the ``!`` character at the end:: proot -b '/bin/bash:/bin/sh!' $ md5sum /bin/sh 089ed56cd74e63f461bef0fdfc2d159a /bin/sh $ md5sum /bin/bash 089ed56cd74e63f461bef0fdfc2d159a /bin/bash $ md5sum /bin/dash c229085928dc19e8d9bd29fe88268504 /bin/dash ``chroot`` + ``mount --bind`` equivalent ---------------------------------------- The two features above can be combined to make any file from the host rootfs accessible in the confined environment just as if it were initially part of the guest rootfs. It is sometimes required to run programs that rely on some specific files:: proot -r /mnt/slackware-8.0/ $ ps -o tty,command Error, do this: mount -t proc none /proc works better with:: proot -r /mnt/slackware-8.0/ -b /proc $ ps -o tty,command TT COMMAND ? bash ? proot -b /proc /mnt/slackware-8.0/ ? sh ? ps -o tty,command Actually there's a bunch of such specific files, that's why PRoot provides the option ``-R`` to bind automatically a pre-defined list of recommended paths:: proot -R /mnt/slackware-8.0/ $ ps -o tty,command TT COMMAND pts/6 bash pts/6 proot -R /mnt/slackware-8.0/ pts/6 sh pts/6 ps -o tty,command ``chroot`` + ``mount --bind`` + ``su`` equivalent ------------------------------------------------- Some programs will not work correctly if they are not run by the "root" user, this is typically the case with package managers. PRoot can fake the root identity and its privileges when the ``-0`` (zero) option is specified:: proot -r /mnt/slackware-8.0/ -0 # id uid=0(root) gid=0(root) [...] # mkdir /tmp/foo # chmod a-rwx /tmp/foo # echo 'I bypass file-system permissions.' > /tmp/foo/bar # cat /tmp/foo/bar I bypass file-system permissions. This option is typically required to create or install packages into the guest rootfs. Note it is *not* recommended to use the ``-R`` option when installing packages since they may try to update bound system files, like ``/etc/group``. Instead, it is recommended to use the ``-S`` option. This latter enables the ``-0`` option and binds only paths that are known to not be updated by packages:: proot -S /mnt/slackware-8.0/ # installpkg perl.tgz Installing package perl... ``chroot`` + ``mount --bind`` + ``binfmt_misc`` equivalent ---------------------------------------------------------- PRoot uses QEMU user-mode to execute programs built for a CPU architecture incompatible with the host one. From users' point-of-view, guest programs handled by QEMU user-mode are executed transparently, that is, just like host programs. To enable this feature users just have to specify which instance of QEMU user-mode they want to use with the option ``-q``:: proot -R /mnt/armslack-12.2/ -q qemu-arm $ cat /etc/motd Welcome to ARMedSlack Linux 12.2 The parameter of the ``-q`` option is actually a whole QEMU user-mode command, for instance to enable its GDB server on port 1234:: proot -R /mnt/armslack-12.2/ -q "qemu-arm -g 1234" emacs PRoot allows one to mix transparently the emulated execution of guest programs and the native execution of host programs in the same file-system namespace. It's typically useful to extend the list of available programs and to speed up build-time significantly. This mixed-execution feature is enabled by default when using QEMU user-mode, and the content of the host rootfs is made accessible through ``/host-rootfs``:: proot -R /mnt/armslack-12.2/ -q qemu-arm $ file /bin/echo [...] ELF 32-bit LSB executable, ARM [...] $ /bin/echo 'Hello world!' Hello world! $ file /host-rootfs/bin/echo [...] ELF 64-bit LSB executable, x86-64 [...] $ /host-rootfs/bin/echo 'Hello mixed world!' Hello mixed world! Since both host and guest programs use the guest rootfs as ``/``, users may want to deactivate explicitly cross-filesystem support found in most GNU cross-compilation tools. For example with GCC configured to cross-compile to the ARM target:: proot -R /mnt/armslack-12.2/ -q qemu-arm $ export CC=/host-rootfs/opt/cross-tools/arm-linux/bin/gcc $ export CFLAGS="--sysroot=/" # could be optional indeed $ ./configure; make As with regular files, a host instance of a program can be bound over its guest instance. Here is an example where the guest binary of ``make`` is overlaid by the host one:: proot -R /mnt/armslack-12.2/ -q qemu-arm -b /usr/bin/make $ which make /usr/bin/make $ make --version # overlaid GNU Make 3.82 Built for x86_64-slackware-linux-gnu It's worth mentioning that even when mixing the native execution of host programs and the emulated execution of guest programs, they still believe they are running in a native guest environment. As a demonstration, here is a partial output of a typical ``./configure`` script:: checking whether the C compiler is a cross-compiler... no Downloads ========= PRoot ----- The source code for PRoot and CARE are hosted in the same repository on `GitHub `_. Previous PRoot releases were packaged at https://github.com/proot-me/proot-static-build/releases, however, that repository has since been archived. The latest builds can be found under the job artifacts for the `GitLab CI/CD Pipelines `_ for each commit. The following commands can be used to download the latest x86_64 binary for convenience:: curl -LO https://proot.gitlab.io/proot/bin/proot chmod +x ./proot proot --version Rootfs ------ The following URLs contain rootfs archives that can be freely downloaded. Note that ``mknod`` errors reported by ``tar`` when extracting these archives can be safely ignored since special files are typically bound (see ``-R`` option for details). * https://download.openvz.org/template/precreated * https://images.linuxcontainers.org/images * http://distfiles.gentoo.org/releases * http://cdimage.ubuntu.com/ubuntu-core * https://archlinuxarm.org/about/downloads * https://alpinelinux.org/downloads Technically such rootfs archive can be created by running the following command on the expected Linux distribution:: tar --one-file-system --create --gzip --file my_rootfs.tar.gz / Ecosystem ========= The following ecosystem has developed around PRoot since it has been made publicly available. Projects using PRoot or CARE ---------------------------- * `ATOS `_: find automatically C/C++ compiler options that provide best optimizations. * CARE_: archive material used during an execution to make it reproducible on any Linux system. * `Debian noroot `_: use Debian Linux on Android without root access. * `GNURoot `_: use several Linux distros on Android without root access. * `JuNest `_: use Arch Linux on any Linux distros without root access. * `OPAM2Debian `_: create Debian packages which contains a fully compiled OPAM installation. * `OpenMOLE `_: execute programs on distributed computing environments. * `Polysquare Travis Container `_: use several Linux distros on Travis-CI without root access. * `Portable PyPy `_: portable 32 and 64 bit x86 PyPy binaries. * `SIO Workers `_: batch long-term computations with Python. Third party packages -------------------- Binaries from the Downloads_ section are likely more up-to-date. * `Alpine Linux `_ * `Arch Linux `_ * `Debian `_ * `Gentoo `_ * `NixOS `_ * `Termux `_ * `Ubuntu `_ * `University of Chicago RCC `_ * `Void Linux `_ Public material about PRoot or CARE ----------------------------------- * articles on `Rémi's blog `_. Rémi (a.k.a Ivoire) is one of the PRoot developers. * presentation "`Software engineering tools based on syscall instrumentation `_" during FOSDEM 2014. * presentation "`SW testing & Reproducing a LAVA failures locally using CARE `_" during Linaro Connect USA 2014 * presentation and essay "`CARE: the Comprehensive Archiver for Reproducible Execution `_" (`essay `_) during TRUST 2014 * presentation "`An Introduction to the CARE tool (dead link) <#>`_" during HiPEAC CSW 2013 * presentation and essay "`PRoot: a Step Forward for QEMU User-Mode `_" (`proceedings `_) during QUF'11 * tutorial "`How to install nix in home (on another distribution) `_" Companies using PRoot or CARE internally ---------------------------------------- * STMicroelectronics * Sony * Ericsson * Cisco * Gogo * Infinite Omicron, LLC. See Also ======== chroot(1), mount(8), binfmt_misc, ptrace(2), qemu(1), sb2(1), bindfs(1), fakeroot(1), fakechroot(1) Colophon ======== Visit https://proot-me.github.io for help, bug reports, suggestions, patches, ... Copyright (C) 2023 PRoot Developers, licensed under GPL v2 or later. :: _____ _____ ___ | __ \ __ \_____ _____| |_ | __/ / _ \/ _ \ _| |__| |__|__\_____/\_____/\____| proot-5.4.0/doc/proot/stylesheets/000077500000000000000000000000001442763353300171615ustar00rootroot00000000000000proot-5.4.0/doc/proot/stylesheets/cli.xsl000066400000000000000000000116731442763353300204700ustar00rootroot00000000000000 /* This file is automatically generated from the documentation. EDIT AT YOUR OWN RISK. */ #ifndef PROOT_CLI_H #define PROOT_CLI_H #include "cli/cli.h" #ifndef VERSION #define VERSION "" #endif static int pre_initialize_bindings(Tracee *, const Cli *, size_t, char *const *, size_t); static int post_initialize_command(Tracee *, const Cli *, size_t, char *const *, size_t); static Cli proot_cli = { .version = VERSION, .name = "proot", .pre_initialize_bindings = pre_initialize_bindings, .post_initialize_command = post_initialize_command, .options = { END_OF_OPTIONS, }, }; #endif /* PROOT_CLI_H */ .subtitle = " ", .synopsis = " ", .colophon = " ", .logo = "\ ", static const char *recommended_bindings[] = { NULL, }; static const char *recommended_su_bindings[] = { NULL, }; " ", { .class = " ", .arguments = { { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_ , .description = " ", .detail = " ", }, * * { .name = " ", .separator = ' ', .value = " " \0', .value = NULL }, static int handle_option_ (Tracee *tracee, const Cli *cli, const char *value); proot-5.4.0/doc/proot/stylesheets/rpm-spec.xsl000066400000000000000000000023611442763353300214410ustar00rootroot00000000000000 %define version v Summary : Version : %{version} Release : 1 License : GPL2+ Group : Applications/System Source : proot-%{version}.tar.gz Buildroot : %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) Prefix : /usr Name : proot BuildRequires: libtalloc-devel %if 0%{?suse_version} >= 1210 || 0%{?fedora_version} >= 15 BuildRequires: glibc-static %endif %if !0%{?suse_version} != 0 BuildRequires: which %endif %description %prep %setup -n proot-%{version} %build make -C src %install make -C src install PREFIX=%{buildroot}/%{prefix} install -D doc/proot/man.1 %{buildroot}/%{_mandir}/man1/proot.1 %check env LD_SHOW_AUXV=1 true cat /proc/cpuinfo ./src/proot -V ./src/proot -v 1 true make -C test %clean rm -rf %{buildroot} %files %defattr(-,root,root) %{prefix}/bin/proot %doc %{_mandir}/man1/proot.1* %doc COPYING %doc doc/* %changelog proot-5.4.0/doc/proot/stylesheets/website.css000066400000000000000000000002371442763353300213370ustar00rootroot00000000000000@import url("website.css"); h1 { color: orange; } #contents a:hover { border-bottom: 2px solid orange; } a { border-bottom: 1px solid orange; } proot-5.4.0/doc/proot/stylesheets/website.xsl000066400000000000000000000043511442763353300213560ustar00rootroot00000000000000 <xsl:value-of select="document/title" /> — <xsl:value-of select="document/subtitle" />

PRoot

Support

Feel free to send your questions, bug reports, suggestions, and patches to the mailing-list or to the forum, or chat with us on Gitter; but please be sure that your answer isn't in the user manual first.

top
proot-5.4.0/doc/security.rst000066400000000000000000000010031442763353300160350ustar00rootroot00000000000000How to report security vulnerabilities in PRoot? ================================================ This document provides instruction on privately disclosing security vulnerabilities found in PRoot or CARE. Vulnerabilities --------------- Fortunately, there have yet to be any serious flaws found in the PRoot / CARE source code. Confidential Contacts --------------------- The developers below are to be contacted directly via encrypted email: Lucas Ramage | ramage.lucas@protonmail.com | 0x5D804A8DCFE9DC63 proot-5.4.0/doc/stylesheets/000077500000000000000000000000001442763353300160165ustar00rootroot00000000000000proot-5.4.0/doc/stylesheets/website.css000066400000000000000000000040741442763353300201770ustar00rootroot00000000000000* { padding: 0; margin: 0; color: #333333; line-height: 1.5em; font-family: sans; } html { background-color: #dddddd; } body { background-color: white; color: #444; font-size: 18px; line-height: 1.6; margin: 40px auto; max-width: 725px; padding:0 10px; } #title { margin-left: auto; margin-right: auto; text-align: center; } h1, h2, h3 { line-height: 1.2; } h1 { text-align: center; font-size: 3em; display: inline; } h2 { margin: 1em; margin-bottom: 0.5em; border-bottom: 1px dotted gray; } h3 { margin-left: 1em; margin-top: 1em; } h3 tt { font-style: italic; } #contents { text-align: center; background-color: gray; border-top: 1px solid #dddddd; border-bottom: 1px solid #dddddd; margin-right: -1px; border-right: 1px solid gray; margin-left: -1px; border-left: 1px solid gray; padding-top: 0.5em; padding-bottom: 0.5em; } #contents ul { list-style-type: none; margin: 0; } #contents li { display: inline; padding: 10px; white-space: nowrap; } #contents a { color: white; text-decoration: none; font-weight: bold; border-bottom: none; } #contents a:hover { border-bottom: 2px solid black; } a { text-decoration: none; border-bottom: 1px solid black; } p { text-align: justify; margin-left: 2em; margin-right: 2em; margin-top: 1em; margin-bottom: 0.5em; } ol { margin-left: 5em; margin-right: 2em; margin-top: 0.5em; margin-bottom: 1em; } li { margin-bottom: 0.5em; } table { margin-left: 2em; margin-right: 2em; } pre { background-color: black; color: white; font-family: monospace; margin: 2em ; margin-top: 0.5em ; margin-bottom: 1em ; padding: 0.5em; white-space: pre-wrap; } tt { font-family: monospace; } ul { margin-left: 3em; } li p, li pre { margin-left: 0; } @media print { * { background-color: transparent ! important; border: none ! important; } } proot-5.4.0/doc/stylesheets/website.xsl000066400000000000000000000043661442763353300202210ustar00rootroot00000000000000

      
    
  • # []

    []
    proot-5.4.0/doc/template/000077500000000000000000000000001442763353300152555ustar00rootroot00000000000000proot-5.4.0/doc/template/redirect.html000066400000000000000000000002611442763353300177430ustar00rootroot00000000000000proot-5.4.0/lib/000077500000000000000000000000001442763353300134435ustar00rootroot00000000000000proot-5.4.0/lib/uthash/000077500000000000000000000000001442763353300147375ustar00rootroot00000000000000proot-5.4.0/sonar-project.properties000066400000000000000000000005701442763353300176030ustar00rootroot00000000000000sonar.projectKey=proot-me_proot sonar.organization=proot-me # This is the name and version displayed in the SonarCloud UI. #sonar.projectName=proot #sonar.projectVersion=1.0 # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. #sonar.sources=. # Encoding of the source code. Default is default system encoding #sonar.sourceEncoding=UTF-8 proot-5.4.0/src/000077500000000000000000000000001442763353300134645ustar00rootroot00000000000000proot-5.4.0/src/.check_process_vm.c000066400000000000000000000002431442763353300172220ustar00rootroot00000000000000#include #include int main(void) { return process_vm_readv(0, NULL, 0, NULL, 0, 0) + process_vm_writev(0, NULL, 0, NULL, 0, 0); } proot-5.4.0/src/.check_seccomp_filter.c000066400000000000000000000017161442763353300200460ustar00rootroot00000000000000#include /* prctl(2), PR_* */ #include /* SECCOMP_MODE_FILTER, */ #include /* struct sock_*, */ #include /* AUDIT_ARCH_*, */ #include /* offsetof(3), */ int main(void) { const size_t arch_offset = offsetof(struct seccomp_data, arch); const size_t syscall_offset = offsetof(struct seccomp_data, nr); struct sock_fprog program; #define ARCH_NR AUDIT_ARCH_X86_64 struct sock_filter filter[] = { BPF_STMT(BPF_LD + BPF_W + BPF_ABS, arch_offset), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, AUDIT_ARCH_X86_64, 0, 1), BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_offset), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE) }; program.filter = filter; program.len = sizeof(filter) / sizeof(struct sock_filter); (void) prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); (void) prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program); return 1; } proot-5.4.0/src/GNUmakefile000066400000000000000000000210361442763353300155400ustar00rootroot00000000000000# If you want to build outside of the source tree, use the -f option: # make -f ${SOMEWHERE}/proot/src/GNUmakefile # the VPATH variable must point to the actual makefile directory VPATH := $(dir $(lastword $(MAKEFILE_LIST))) SRC = $(dir $(firstword $(MAKEFILE_LIST))) GIT = git RM = rm INSTALL = install CC = $(CROSS_COMPILE)gcc LD = $(CC) STRIP = $(CROSS_COMPILE)strip OBJCOPY = $(CROSS_COMPILE)objcopy OBJDUMP = $(CROSS_COMPILE)objdump PYTHON = python3 HAS_SWIG := $(shell swig -version 2>/dev/null) PYTHON_MAJOR_VERSION = $(shell ${PYTHON} -c "import sys; print(sys.version_info.major)" 2>/dev/null) PYTHON_EMBED = $(shell ${PYTHON} -c "import sys; print('--embed' if sys.hexversion > 0x03080000 else '')" 2>/dev/null) HAS_PYTHON_CONFIG := $(shell ${PYTHON}-config --ldflags ${PYTHON_EMBED} 2>/dev/null) CPPFLAGS += -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -I. -I$(VPATH) -I$(VPATH)/../lib/uthash/include CFLAGS += -g -Wall -Wextra -O2 CFLAGS += $(shell pkg-config --cflags talloc) LDFLAGS += -Wl,-z,noexecstack LDFLAGS += $(shell pkg-config --libs talloc) CARE_LDFLAGS = $(shell pkg-config --libs libarchive) OBJECTS += \ cli/cli.o \ cli/proot.o \ cli/note.o \ execve/enter.o \ execve/exit.o \ execve/shebang.o \ execve/elf.o \ execve/ldso.o \ execve/auxv.o \ execve/aoxp.o \ path/binding.o \ path/glue.o \ path/canon.o \ path/path.o \ path/proc.o \ path/temp.o \ syscall/seccomp.o \ syscall/syscall.o \ syscall/chain.o \ syscall/enter.o \ syscall/exit.o \ syscall/sysnum.o \ syscall/socket.o \ syscall/heap.o \ syscall/rlimit.o \ tracee/tracee.o \ tracee/mem.o \ tracee/reg.o \ tracee/event.o \ ptrace/ptrace.o \ ptrace/user.o \ ptrace/wait.o \ extension/extension.o \ extension/kompat/kompat.o \ extension/fake_id0/fake_id0.o \ extension/link2symlink/link2symlink.o \ extension/portmap/portmap.o \ extension/portmap/map.o \ loader/loader-wrapped.o define define_from_arch.h $2$1 := $(shell $(CC) $1 -E -dM -DNO_LIBC_HEADER $(SRC)/arch.h | grep -w $2 | cut -f 3 -d ' ') endef $(eval $(call define_from_arch.h,,HAS_LOADER_32BIT)) ifdef HAS_LOADER_32BIT OBJECTS += loader/loader-m32-wrapped.o endif ifneq ($(and $(HAS_SWIG),$(HAS_PYTHON_CONFIG)),) OBJECTS += extension/python/python.o \ extension/python/proot_wrap.o \ extension/python/python_extension.o \ extension/python/proot.o endif CARE_OBJECTS = \ cli/care.o \ cli/care-manual.o \ extension/care/care.o \ extension/care/final.o \ extension/care/extract.o \ extension/care/archive.o .DEFAULT_GOAL = proot all: proot ###################################################################### # Beautified output quiet_GEN = @echo " GEN $@"; $(GEN) quiet_CC = @echo " CC $@"; $(CC) quiet_LD = @echo " LD $@"; $(LD) quiet_INSTALL = @echo " INSTALL $?"; $(INSTALL) V = 0 ifeq ($(V), 0) quiet = quiet_ Q = @ silently = >/dev/null 2>&1 else quiet = Q = silently = endif ###################################################################### # Auto-configuration GIT_VERSION := $(shell git describe --tags `git rev-list --tags --max-count=1`) GIT_COMMIT := $(shell git rev-list --all --max-count=1 | cut -c 1-8) VERSION = $(GIT_VERSION)-$(GIT_COMMIT) CHECK_VERSION = if [ ! -z "$(VERSION)" ]; \ then /bin/echo -e "\#undef VERSION\n\#define VERSION \"$(VERSION)\""; \ fi; ifneq ($(and $(HAS_SWIG),$(HAS_PYTHON_CONFIG)),) CHECK_PYTHON_EXTENSION = /bin/echo -e "\#define HAVE_PYTHON_EXTENSION" endif CHECK_FEATURES = process_vm seccomp_filter CHECK_PROGRAMS = $(foreach feature,$(CHECK_FEATURES),.check_$(feature)) CHECK_OBJECTS = $(foreach feature,$(CHECK_FEATURES),.check_$(feature).o) CHECK_RESULTS = $(foreach feature,$(CHECK_FEATURES),.check_$(feature).res) .SILENT .IGNORE .INTERMEDIATE: $(CHECK_OBJECTS) $(CHECK_PROGRAMS) .check_%.o: .check_%.c -$(COMPILE:echo=false) $(silently) .check_%: .check_%.o -$(LINK:echo=false) $(silently) .check_%.res: .check_% $(Q)if [ -e $< ]; then echo "#define HAVE_$(shell echo $* | tr a-z A-Z)" > $@; else echo "" > $@; fi build.h: $(CHECK_RESULTS) $($(quiet)GEN) $(Q)echo "/* This file is auto-generated, edit at your own risk. */" > $@ $(Q)echo "#ifndef BUILD_H" >> $@ $(Q)echo "#define BUILD_H" >> $@ $(Q)sh -c '$(CHECK_VERSION)' >> $@ $(Q)sh -c '$(CHECK_PYTHON_EXTENSION)' >> $@ $(Q)cat $^ >> $@ $(Q)echo "#endif /* BUILD_H */" >> $@ BUILD_ID_NONE := $(shell if ld --build-id=none --version >/dev/null 2>&1; then echo ',--build-id=none'; fi) ###################################################################### # Build rules COMPILE = $($(quiet)CC) $(CPPFLAGS) $(CFLAGS) -MD -c $(SRC)$< -o $@ LINK = $($(quiet)LD) -o $@ $^ $(LDFLAGS) OBJIFY = $($(quiet)GEN) \ $(OBJCOPY) \ --input-target binary \ --output-target `env LC_ALL=C $(OBJDUMP) -f cli/cli.o | \ grep 'file format' | awk '{print $$4}'` \ --binary-architecture `env LC_ALL=C $(OBJDUMP) -f cli/cli.o | \ grep architecture | cut -f 1 -d , | awk '{print $$2}'` \ $< $@ proot: $(OBJECTS) $(LINK) care: $(OBJECTS) $(CARE_OBJECTS) $(LINK) $(CARE_LDFLAGS) # Special case to compute which files depend on the auto-generated # file "build.h". USE_BUILD_H := $(patsubst $(SRC)%.c,%.o,$(shell grep -E -sl 'include[[:space:]]+"build.h"' $(patsubst %.o,$(SRC)%.c,$(OBJECTS) $(CARE_OBJECTS)))) $(USE_BUILD_H): build.h %.o: %.c @mkdir -p $(dir $@) $(COMPILE) .INTERMEDIATE: manual manual: $(VPATH)/../doc/care/manual.rst $(Q)cp $< $@ cli/care-manual.o: manual cli/cli.o $(OBJIFY) cli/%-licenses.o: licenses cli/cli.o $(OBJIFY) ###################################################################### # Python extension define build_python_extension CPPFLAGS += $(shell ${PYTHON}-config --includes) LDFLAGS += $(shell ${PYTHON}-config --ldflags ${PYTHON_EMBED}) SWIG = swig quiet_SWIG = @echo " SWIG $$@"; swig SWIG_OPT = -python ifeq ($(PYTHON_MAJOR_VERSION), 3) SWIG_OPT += -py3 endif .INTERMEDIATE:python_extension.py python_extension.py: extension/python/python_extension.py $$(Q)cp $$< $$@ extension/python/python_extension.o: python_extension.py cli/cli.o $$(OBJIFY) .SECONDARY: proot_wrap.c proot.py proot_wrap.c proot.py: extension/python/proot.i $$($$(quiet)SWIG) $$(SWIG_OPT) -outcurrentdir -I$$(VPATH) $$(VPATH)/extension/python/proot.i extension/python/proot.o: proot.py cli/cli.o $$(OBJIFY) extension/python/proot_wrap.o: proot_wrap.c $$($$(quiet)CC) $$(CPPFLAGS) $$(CFLAGS) -MD -c $$< -o $$@ endef ifneq ($(and $(HAS_SWIG),$(HAS_PYTHON_CONFIG)),) $(eval $(build_python_extension)) endif ###################################################################### # Build rules for the loader define build_loader LOADER$1_OBJECTS = loader/loader$1.o loader/assembly$1.o $(eval $(call define_from_arch.h,$1,LOADER_ARCH_CFLAGS)) $(eval $(call define_from_arch.h,$1,LOADER_ADDRESS)) LOADER_CFLAGS$1 += -fPIC -ffreestanding $(LOADER_ARCH_CFLAGS$1) LOADER_LDFLAGS$1 += -static -nostdlib -Wl$(BUILD_ID_NONE),-Ttext=$(LOADER_ADDRESS$1),-z,noexecstack loader/loader$1.o: loader/loader.c @mkdir -p $$(dir $$@) $$(COMPILE) $1 $$(LOADER_CFLAGS$1) loader/assembly$1.o: loader/assembly.S @mkdir -p $$(dir $$@) $$(COMPILE) $1 $$(LOADER_CFLAGS$1) loader/loader$1: $$(LOADER$1_OBJECTS) $$($$(quiet)LD) $1 -o $$@ $$^ $$(LOADER_LDFLAGS$1) .INTERMEDIATE: loader$1.elf loader$1.elf: loader/loader$1 $$(Q)cp $$< $$@ $$(Q)$(STRIP) $$@ loader/loader$1-wrapped.o: loader$1.elf cli/cli.o $$(OBJIFY) endef $(eval $(build_loader)) ifdef HAS_LOADER_32BIT $(eval $(call build_loader,-m32)) endif ###################################################################### # Dependencies .DELETE_ON_ERROR: $(OBJECTS) $(CARE_OBJECTS) $(LOADER_OBJECTS) $(LOADER-m32_OBJECTS): $(firstword $(MAKEFILE_LIST)) DEPS = $(OBJECTS:.o=.d) $(CARE_OBJECTS:.o=.d) $(LOADER_OBJECTS:.o=.d) $(LOADER-m32_OBJECTS:.o=.d) $(CHECK_OBJECTS:.o=.d) -include $(DEPS) ###################################################################### # PHONY targets PREFIX ?= /usr/local BINDIR ?= $(PREFIX)/bin .PHONY: clean distclean install install-care uninstall clean distclean: -$(RM) -f $(CHECK_OBJECTS) $(CHECK_PROGRAMS) $(CHECK_RESULTS) $(OBJECTS) $(CARE_OBJECTS) $(LOADER_OBJECTS) $(LOADER-m32_OBJECTS) proot care loader/loader loader/loader-m32 cli/care-manual.o $(DEPS) build.h licenses proot.py proot_wrap.c install: proot $($(quiet)INSTALL) -D $< $(DESTDIR)$(BINDIR)/$< install-care: care $($(quiet)INSTALL) -D $< $(DESTDIR)$(BINDIR)/$< uninstall: -$(RM) -f $(DESTDIR)$(BINDIR)/proot uninstall-care: -$(RM) -f $(DESTDIR)$(BINDIR)/care proot-5.4.0/src/arch.h000066400000000000000000000117751442763353300145650ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef ARCH_H #define ARCH_H #ifndef NO_LIBC_HEADER #include /* linux.git:c0a3a20b */ #include /* AUDIT_ARCH_*, */ #endif typedef unsigned long word_t; typedef unsigned char byte_t; #define SYSCALL_AVOIDER ((word_t) -1) #define SYSTRAP_NUM SYSARG_NUM #define STACK_ALIGNMENT 16 #define OFFSETOF_STATX_UID 20 #define OFFSETOF_STATX_GID 24 #if !defined(ARCH_X86_64) && !defined(ARCH_ARM_EABI) && !defined(ARCH_X86) && !defined(ARCH_SH4) # if defined(__x86_64__) # define ARCH_X86_64 1 # elif defined(__ARM_EABI__) # define ARCH_ARM_EABI 1 # elif defined(__aarch64__) # define ARCH_ARM64 1 # elif defined(__arm__) # error "Only EABI is currently supported for ARM" # elif defined(__i386__) # define ARCH_X86 1 # elif defined(__SH4__) # define ARCH_SH4 1 # else # error "Unsupported architecture" # endif #endif /* Architecture specific definitions. */ #if defined(ARCH_X86_64) #define SYSNUMS_HEADER1 "syscall/sysnums-x86_64.h" #define SYSNUMS_HEADER2 "syscall/sysnums-i386.h" #define SYSNUMS_HEADER3 "syscall/sysnums-x32.h" #define SYSNUMS_ABI1 sysnums_x86_64 #define SYSNUMS_ABI2 sysnums_i386 #define SYSNUMS_ABI3 sysnums_x32 #undef SYSTRAP_NUM #define SYSTRAP_NUM SYSARG_RESULT #define SYSTRAP_SIZE 2 #define SECCOMP_ARCHS { \ { .value = AUDIT_ARCH_X86_64, .nb_abis = 2, .abis = { ABI_DEFAULT, ABI_3 } }, \ { .value = AUDIT_ARCH_I386, .nb_abis = 1, .abis = { ABI_2 } }, \ } #define HOST_ELF_MACHINE {62, 3, 6, 0} #define RED_ZONE_SIZE 128 #define OFFSETOF_STAT_UID_32 24 #define OFFSETOF_STAT_GID_32 28 #define LOADER_ADDRESS 0x600000000000 #define HAS_LOADER_32BIT true #define EXEC_PIC_ADDRESS 0x500000000000 #define INTERP_PIC_ADDRESS 0x6f0000000000 #define EXEC_PIC_ADDRESS_32 0x0f000000 #define INTERP_PIC_ADDRESS_32 0xaf000000 #elif defined(ARCH_ARM_EABI) #define SYSNUMS_HEADER1 "syscall/sysnums-arm.h" #define SYSNUMS_ABI1 sysnums_arm #define SYSTRAP_SIZE 4 #define SECCOMP_ARCHS { { .value = AUDIT_ARCH_ARM, .nb_abis = 1, .abis = { ABI_DEFAULT } } } #define user_regs_struct user_regs #define HOST_ELF_MACHINE {40, 0}; #define RED_ZONE_SIZE 0 #define OFFSETOF_STAT_UID_32 0 #define OFFSETOF_STAT_GID_32 0 #define EM_ARM 40 #define LOADER_ADDRESS 0x10000000 #define EXEC_PIC_ADDRESS 0x0f000000 #define INTERP_PIC_ADDRESS 0x1f000000 #elif defined(ARCH_ARM64) #define SYSNUMS_HEADER1 "syscall/sysnums-arm64.h" #define SYSNUMS_ABI1 sysnums_arm64 #define SYSTRAP_SIZE 4 #ifndef AUDIT_ARCH_AARCH64 #define AUDIT_ARCH_AARCH64 (EM_AARCH64 | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE) #endif #define SECCOMP_ARCHS { { .value = AUDIT_ARCH_AARCH64, .nb_abis = 1, .abis = { ABI_DEFAULT } } } #define HOST_ELF_MACHINE {183, 0}; #define RED_ZONE_SIZE 0 #define OFFSETOF_STAT_UID_32 0 #define OFFSETOF_STAT_GID_32 0 #define LOADER_ADDRESS 0x2000000000 #define EXEC_PIC_ADDRESS 0x3000000000 #define INTERP_PIC_ADDRESS 0x3f00000000 #elif defined(ARCH_X86) #define SYSNUMS_HEADER1 "syscall/sysnums-i386.h" #define SYSNUMS_ABI1 sysnums_i386 #undef SYSTRAP_NUM #define SYSTRAP_NUM SYSARG_RESULT #define SYSTRAP_SIZE 2 #define SECCOMP_ARCHS { { .value = AUDIT_ARCH_I386, .nb_abis = 1, .abis = { ABI_DEFAULT } } } #define HOST_ELF_MACHINE {3, 6, 0}; #define RED_ZONE_SIZE 0 #define OFFSETOF_STAT_UID_32 0 #define OFFSETOF_STAT_GID_32 0 #define LOADER_ADDRESS 0xa0000000 #define LOADER_ARCH_CFLAGS -mregparm=3 #define EXEC_PIC_ADDRESS 0x0f000000 #define INTERP_PIC_ADDRESS 0xaf000000 #elif defined(ARCH_SH4) #define SYSNUMS_HEADER1 "syscall/sysnums-sh4.h" #define SYSNUMS_ABI1 sysnums_sh4 #define SYSTRAP_SIZE 2 #define SECCOMP_ARCHS { } #define user_regs_struct pt_regs #define HOST_ELF_MACHINE {42, 0}; #define RED_ZONE_SIZE 0 #define OFFSETOF_STAT_UID_32 0 #define OFFSETOF_STAT_GID_32 0 #define NO_MISALIGNED_ACCESS 1 #else #error "Unsupported architecture" #endif #endif /* ARCH_H */ proot-5.4.0/src/attribute.h000066400000000000000000000022101442763353300156330ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef ATTRIBUTE_H #define ATTRIBUTE_H #define UNUSED __attribute__((unused)) #define FORMAT(a, b, c) __attribute__ ((format (a, b, c))) #define DONT_INSTRUMENT __attribute__((no_instrument_function)) #define PACKED __attribute__((packed)) #define WEAK __attribute__((weak)) #endif /* ATTRIBUTE_H */ proot-5.4.0/src/cli/000077500000000000000000000000001442763353300142335ustar00rootroot00000000000000proot-5.4.0/src/cli/care.c000066400000000000000000000260031442763353300153120ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of CARE. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* str*(3), */ #include /* assert(3), */ #include /* bzero(3), */ #include /* STAILQ_*, */ #include /* INT_MIN, */ #include /* getpid(2), close(2), */ #include /* printf(3), fflush(3), */ #include /* getcwd(2), */ #include /* errno(3), */ #include "cli/cli.h" #include "cli/note.h" #include "path/binding.h" #include "path/temp.h" #include "extension/extension.h" #include "extension/care/care.h" #include "extension/care/extract.h" #include "attribute.h" /* These should be included last. */ #include "build.h" #include "cli/care.h" static int handle_option_o(Tracee *tracee UNUSED, const Cli *cli, const char *value) { Options *options = talloc_get_type_abort(cli->private, Options); options->output = value; return 0; } static int handle_option_c(Tracee *tracee UNUSED, const Cli *cli, const char *value) { Options *options = talloc_get_type_abort(cli->private, Options); Item *item = queue_item(options, &options->concealed_paths, value); return (item != NULL ? 0 : -1); } static int handle_option_r(Tracee *tracee UNUSED, const Cli *cli, const char *value) { Options *options = talloc_get_type_abort(cli->private, Options); Item *item = queue_item(options, &options->revealed_paths, value); return (item != NULL ? 0 : -1); } static int handle_option_p(Tracee *tracee UNUSED, const Cli *cli, const char *value) { Options *options = talloc_get_type_abort(cli->private, Options); Item *item = queue_item(options, &options->volatile_paths, value); return (item != NULL ? 0 : -1); } static int handle_option_e(Tracee *tracee UNUSED, const Cli *cli, const char *value) { Options *options = talloc_get_type_abort(cli->private, Options); Item *item = queue_item(options, &options->volatile_envars, value); return (item != NULL ? 0 : -1); } static int handle_option_m(Tracee *tracee, const Cli *cli, const char *value) { Options *options = talloc_get_type_abort(cli->private, Options); return parse_integer_option(tracee, &options->max_size, value, "-m"); } static int handle_option_d(Tracee *tracee UNUSED, const Cli *cli, const char *value UNUSED) { Options *options = talloc_get_type_abort(cli->private, Options); options->ignore_default_config = true; return 0; } static int handle_option_v(Tracee *tracee, const Cli *cli UNUSED, const char *value) { int status; status = parse_integer_option(tracee, &tracee->verbose, value, "-v"); if (status < 0) return status; global_verbose_level = tracee->verbose; return 0; } extern unsigned char WEAK _binary_licenses_start; extern unsigned char WEAK _binary_licenses_end; static int handle_option_V(Tracee *tracee UNUSED, const Cli *cli, const char *value UNUSED) { size_t size; print_version(cli); printf("suitable for self-extracting archives (.bin): %s\n", #if defined(CARE_BINARY_IS_PORTABLE) "yes" #else "no" #endif ); printf("\n%s\n", cli->colophon); fflush(stdout); size = &_binary_licenses_end - &_binary_licenses_start; if (size > 0) write(1, &_binary_licenses_start, size); exit_failure = false; return -1; } static int handle_option_x(Tracee *tracee UNUSED, const Cli *cli UNUSED, const char *value) { int status = extract_archive_from_file(value); exit_failure = (status < 0); return -1; } extern unsigned char WEAK _binary_manual_start; extern unsigned char WEAK _binary_manual_end; static int handle_option_h(Tracee *tracee UNUSED, const Cli *cli UNUSED, const char *value UNUSED) { size_t size; size = &_binary_manual_end - &_binary_manual_start; if (size != 0) write(1, &_binary_manual_start, size); else printf("No manual found, please visit https://proot-me.github.io instead.\n"); exit_failure = false; return -1; } /** * Allocate a new binding for the given @tracee that will conceal the * content of @path with an empty file or directory. This function * complains about missing @host path only if @must_exist is true. */ static Binding *new_concealing_binding(Tracee *tracee, const char *path, bool must_exist) { struct stat statl; Binding *binding; const char *temp; int status; status = stat(path, &statl); if (status < 0) { if (must_exist) note(tracee, WARNING, SYSTEM, "can't conceal %s", path); return NULL; } if (S_ISDIR(statl.st_mode)) temp = create_temp_directory(NULL, tracee->tool_name); else temp = create_temp_file(NULL, tracee->tool_name); if (temp == NULL) { note(tracee, WARNING, INTERNAL, "can't conceal %s", path); return NULL; } binding = new_binding(tracee, temp, path, must_exist); if (binding == NULL) return NULL; return binding; } /** * Initialize @tracee's fields that are mandatory for PRoot/CARE but * that are not specifiable on the command line. */ static int pre_initialize_bindings(Tracee *tracee, const Cli *cli, size_t argc, char *const argv[], size_t cursor) { Options *options = talloc_get_type_abort(cli->private, Options); char path[PATH_MAX]; Binding *binding; const char *home; const char *pwd; Item *item; size_t i; if (cursor >= argc) { note(tracee, ERROR, USER, "no command specified"); return -1; } options->command = &argv[cursor]; home = getenv("HOME"); pwd = getenv("PWD"); /* Set these variables to their default values (ie. when not * set), this simplifies the binding setup to the related * files. */ setenv("XAUTHORITY", talloc_asprintf(tracee->ctx, "%s/.Xauthority", home), 0); setenv("ICEAUTHORITY", talloc_asprintf(tracee->ctx, "%s/.ICEauthority", home), 0); /* Enable default option first. */ if (!options->ignore_default_config) { const char *expanded; Binding *binding; int status; /* Bind an empty file/directory over default concealed * paths. */ for (i = 0; default_concealed_paths[i] != NULL; i++) { expanded = expand_front_variable(tracee->ctx, default_concealed_paths[i]); binding = new_concealing_binding(tracee, expanded, false); if (binding != NULL) VERBOSE(tracee, 0, "concealed path: %s %s", default_concealed_paths[i], default_concealed_paths[i] != expanded ? expanded : ""); } /* Bind default revealed paths over concealed * paths. */ for (i = 0; default_revealed_paths[i] != NULL; i++) { expanded = expand_front_variable(tracee->ctx, default_revealed_paths[i]); binding = new_binding(tracee, expanded, NULL, false); if (binding != NULL) VERBOSE(tracee, 0, "revealed path: %s %s", default_revealed_paths[i], default_revealed_paths[i] != expanded ? expanded : ""); } /* Ensure the initial command is accessible. */ status = which(NULL, NULL, path, options->command[0]); if (status < 0) return -1; /* This failure was already noticed by which(). */ binding = new_binding(tracee, path, NULL, false); if (binding != NULL) VERBOSE(tracee, 0, "revealed path: %s", path); /* Sanity check. Note: it is assumed $HOME and $PWD * are canonicalized. */ if (home != NULL && pwd != NULL && strcmp(home, pwd) == 0) note(tracee, WARNING, USER, "$HOME is implicitely revealed since it is the same as $PWD, " "change your current working directory to be sure " "your personal data will be not archivable."); /* Add the default volatile paths to the list of user * volatile paths. */ for (i = 0; default_volatile_paths[i] != NULL; i++) { Item *item; expanded = expand_front_variable(tracee->ctx, default_volatile_paths[i]); item = queue_item(tracee, &options->volatile_paths, expanded); /* Remember the non expanded form, later used * by archive_re_execute_sh(). */ if (item != NULL && expanded != default_volatile_paths[i]) talloc_set_name_const(item, default_volatile_paths[i]); } for (i = 0; default_volatile_envars[i] != NULL; i++) queue_item(tracee, &options->volatile_envars, default_volatile_envars[i]); if (options->max_size == INT_MIN) options->max_size = CARE_MAX_SIZE; } else if (options->max_size == INT_MIN) options->max_size = -1; /* Unlimited. */ VERBOSE(tracee, 1, "max size: %d", options->max_size); /* Bind an empty file/directory over user concealed paths. */ if (options->concealed_paths != NULL) { STAILQ_FOREACH(item, options->concealed_paths, link) { binding = new_concealing_binding(tracee, item->load, true); if (binding != NULL) VERBOSE(tracee, 0, "concealed path: %s", (char *) item->load); } } /* Bind user revealed paths over concealed paths. */ if (options->revealed_paths != NULL) { STAILQ_FOREACH(item, options->revealed_paths, link) { binding = new_binding(tracee, item->load, NULL, true); if (binding != NULL) VERBOSE(tracee, 0, "revealed path: %s", (char *) item->load); } } /* Bind volatile paths over concealed paths. */ if (options->volatile_paths != NULL) { STAILQ_FOREACH(item, options->volatile_paths, link) { binding = new_binding(tracee, item->load, NULL, false); if (binding != NULL) VERBOSE(tracee, 1, "volatile path: %s", (char *) item->load); } } VERBOSE(tracee, 0, "----------------------------------------------------------------------"); /* Initialize @tracee->fs->cwd with a path already canonicalized * as required by care.c:handle_initialization(). */ if (getcwd(path, PATH_MAX) == NULL) { note(tracee, ERROR, SYSTEM, "can't get current working directory"); return -1; } tracee->fs->cwd = talloc_strdup(tracee->fs, path); if (tracee->fs->cwd == NULL) return -1; talloc_set_name_const(tracee->fs->cwd, "$cwd"); /* Initialize @tracee's root (required by PRoot). */ binding = new_binding(tracee, "/", "/", true); if (binding == NULL) return -1; return cursor; } /** * Initialize CARE extensions. */ static int post_initialize_bindings(Tracee *tracee, const Cli *cli, size_t argc UNUSED, char *const argv[] UNUSED, size_t cursor) { Options *options = talloc_get_type_abort(cli->private, Options); int status; status = initialize_extension(tracee, care_callback, (void *) options); if (status < 0) { note(tracee, WARNING, INTERNAL, "can't initialize the care extension"); return -1; } return cursor; } const Cli *get_care_cli(TALLOC_CTX *context) { Options *options; global_tool_name = care_cli.name; options = talloc_zero(context, Options); if (options == NULL) return NULL; options->max_size = INT_MIN; care_cli.private = options; return &care_cli; } proot-5.4.0/src/cli/care.h000066400000000000000000000145431442763353300153250ustar00rootroot00000000000000/* This file is automatically generated from the documentation. EDIT AT YOUR OWN RISK. */ #ifndef CARE_CLI_H #define CARE_CLI_H #include "cli/cli.h" #ifndef VERSION #define VERSION "2.3.0" #endif #define CARE_MAX_SIZE 1024 static char const *default_concealed_paths[] = { "$HOME", "/tmp", NULL, }; static char const *default_revealed_paths[] = { "$PWD", NULL, }; static char const *default_volatile_paths[] = { "/dev", "/proc", "/sys", "/run/shm", "/tmp/.X11-unix", "/tmp/.ICE-unix", "$XAUTHORITY", "$ICEAUTHORITY", "/var/run/dbus/system_bus_socket", "/var/tmp/kdecache-$LOGNAME", NULL, }; static char const *default_volatile_envars[] = { "DISPLAY", "http_proxy", "https_proxy", "ftp_proxy", "all_proxy", "HTTP_PROXY", "HTTPS_PROXY", "FTP_PROXY", "ALL_PROXY", "DBUS_SESSION_BUS_ADDRESS", "SESSION_MANAGER", "XDG_SESSION_COOKIE", NULL, }; static int handle_option_o(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_c(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_r(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_p(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_e(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_m(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_d(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_v(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_V(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_x(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_h(Tracee *tracee, const Cli *cli, const char *value); static int pre_initialize_bindings(Tracee *, const Cli *, size_t, char *const *, size_t); static int post_initialize_bindings(Tracee *, const Cli *, size_t, char *const *, size_t); static Cli care_cli = { .version = VERSION, .name = "care", .subtitle = "Comprehensive Archiver for Reproducible Execution", .synopsis = "care [option] ... command", .colophon = "Visit https://proot-me.github.io for help, bug reports, suggestions, patches, ...\n\ Copyright (C) 2021 PRoot Developers, licensed under GPL v2 or later.", .logo = "\ _____ ____ _____ ____\n\ / __/ __ | __ \\ __|\n\ / /_/ | / __|\n\ \\_____|__|__|__|__\\____|", .pre_initialize_bindings = pre_initialize_bindings, .post_initialize_bindings = post_initialize_bindings, .options = { { .class = "Options", .arguments = { { .name = "-o", .separator = ' ', .value = "path" }, { .name = "--output", .separator = '=', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_o, .description = "Archive in *path*, its suffix specifies the format.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-c", .separator = ' ', .value = "path" }, { .name = "--concealed-path", .separator = '=', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_c, .description = "Make *path* content appear empty during the original execution.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-r", .separator = ' ', .value = "path" }, { .name = "--revealed-path", .separator = '=', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_r, .description = "Make *path* content accessible when nested in a concealed path.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-p", .separator = ' ', .value = "path" }, { .name = "--volatile-path", .separator = '=', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_p, .description = "Don't archive *path* content, reuse actual *path* instead.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-e", .separator = ' ', .value = "name" }, { .name = "--volatile-env", .separator = '=', .value = "name" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_e, .description = "Don't archive *name* env. variable, reuse actual value instead.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-m", .separator = ' ', .value = "value" }, { .name = "--max-archivable-size", .separator = '=', .value = "value" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_m, .description = "Set the maximum size of archivable files to *value* megabytes.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-d", .separator = '\0', .value = NULL }, { .name = "--ignore-default-config", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_d, .description = "Don't use the default options.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-v", .separator = ' ', .value = "value" }, { .name = "--verbose", .separator = '=', .value = "value" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_v, .description = "Set the level of debug information to *value*.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-V", .separator = '\0', .value = NULL }, { .name = "--version", .separator = '\0', .value = NULL }, { .name = "--about", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_V, .description = "Print version, copyright, license and contact, then exit.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-x", .separator = ' ', .value = "file" }, { .name = "--extract", .separator = '=', .value = "file" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_x, .description = "Extract content of the archive *file*, then exit.", .detail = NULL, }, { .class = "Options", .arguments = { { .name = "-h", .separator = '\0', .value = NULL }, { .name = "--help", .separator = '\0', .value = NULL }, { .name = "--usage", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_h, .description = "Print the user manual, then exit.", .detail = NULL, }, END_OF_OPTIONS, }, }; #endif /* CARE_CLI_H */ proot-5.4.0/src/cli/cli.c000066400000000000000000000354071442763353300151570ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* printf(3), */ #include /* bool, true, false, */ #include /* ARG_MAX, PATH_MAX, */ #include /* str*(3), basename(3), */ #include /* talloc*, */ #include /* exit(3), EXIT_*, strtol(3), {g,s}etenv(3), */ #include /* assert(3), */ #include /* getpid(2), */ #include /* getpid(2), */ #include /* errno(3), */ #include /* INT_MAX, */ /* execinfo.h is GNU extension, disable it not using glibc */ #if defined(__GLIBC__) #include /* backtrace_symbols(3), */ #endif #include "cli/cli.h" #include "cli/note.h" #include "extension/care/extract.h" #include "extension/extension.h" #include "tracee/tracee.h" #include "tracee/event.h" #include "path/binding.h" #include "path/canon.h" #include "path/path.h" #include "build.h" /** * Print a (@detailed) usage of PRoot. */ void print_usage(Tracee *tracee, const Cli *cli, bool detailed) { const char *current_class = "none"; const Option *options; size_t i, j; #define DETAIL(a) if (detailed) a DETAIL(printf("%s %s: %s.\n\n", cli->name, cli->version, cli->subtitle)); printf("Usage:\n %s\n", cli->synopsis); DETAIL(printf("\n")); options = cli->options; for (i = 0; options[i].class != NULL; i++) { for (j = 0; ; j++) { const Argument *argument = &(options[i].arguments[j]); if (!argument->name || (!detailed && j != 0)) { DETAIL(printf("\n")); printf("\t%s\n", options[i].description); if (detailed) { if (options[i].detail[0] != '\0') printf("\n%s\n\n", options[i].detail); else printf("\n"); } break; } if (strcmp(options[i].class, current_class) != 0) { current_class = options[i].class; printf("\n%s:\n", current_class); } if (j == 0) printf(" %s", argument->name); else printf(", %s", argument->name); if (argument->separator != '\0') printf("%c*%s*", argument->separator, argument->value); else if (!detailed) printf("\t"); } } notify_extensions(tracee, PRINT_USAGE, detailed, 0); if (detailed) printf("%s\n", cli->colophon); } /** * Print the version of PRoot. */ void print_version(const Cli *cli) { printf("%s %s\n\n", cli->logo, cli->version); printf("built-in accelerators: process_vm = %s, seccomp_filter = %s\n", #if defined(HAVE_PROCESS_VM) "yes", #else "no", #endif #if defined(HAVE_SECCOMP_FILTER) "yes" #else "no" #endif ); } static void print_execve_help(const Tracee *tracee, const char *argv0, int status) { note(tracee, ERROR, SYSTEM, "execve(\"%s\")", argv0); /* Ubuntu kernel bug? */ if (status == -EPERM && getenv("PROOT_NO_SECCOMP") == NULL) { note(tracee, INFO, USER, "It seems your kernel contains this bug: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1202161\n" "To workaround it, set the env. variable PROOT_NO_SECCOMP to 1."); return; } note(tracee, INFO, USER, "possible causes:\n" " * the program is a script but its interpreter (eg. /bin/sh) was not found;\n" " * the program is an ELF but its interpreter (eg. ld-linux.so) was not found;\n" " * the program is a foreign binary but qemu was not specified;\n" " * qemu does not work correctly (if specified);\n" " * the loader was not found or doesn't work."); } static void print_error_separator(const Tracee *tracee, const Argument *argument) { if (argument->separator == '\0') note(tracee, ERROR, USER, "option '%s' expects no value.", argument->name); else note(tracee, ERROR, USER, "option '%s' and its value must be separated by '%c'.", argument->name, argument->separator); } static void print_argv(const Tracee *tracee, const char *prompt, char *const argv[]) { char string[ARG_MAX] = ""; size_t i; if (!argv) return; #define APPEND(post) \ do { \ ssize_t length = sizeof(string) - (strlen(string) + strlen(post)); \ if (length <= 0) \ return; \ strncat(string, post, length); \ } while (0) APPEND(prompt); APPEND(" ="); for (i = 0; argv[i] != NULL; i++) { APPEND(" "); APPEND(argv[i]); } string[sizeof(string) - 1] = '\0'; #undef APPEND note(tracee, INFO, USER, "%s", string); } static void print_config(Tracee *tracee, char *const argv[]) { assert(tracee != NULL); if (tracee->verbose <= 0) return; if (tracee->qemu) note(tracee, INFO, USER, "host rootfs = %s", HOST_ROOTFS); if (tracee->glue) note(tracee, INFO, USER, "glue rootfs = %s", tracee->glue); note(tracee, INFO, USER, "exe = %s", tracee->exe); print_argv(tracee, "argv", argv); print_argv(tracee, "qemu", tracee->qemu); note(tracee, INFO, USER, "initial cwd = %s", tracee->fs->cwd); note(tracee, INFO, USER, "verbose level = %d", tracee->verbose); notify_extensions(tracee, PRINT_CONFIG, 0, 0); } /** * Initialize @tracee's current working directory. This function * returns -1 if an error occurred, otherwise 0. */ static int initialize_cwd(Tracee *tracee) { char path2[PATH_MAX]; char path[PATH_MAX]; int status; /* Compute the base directory. */ if (tracee->fs->cwd[0] != '/') { status = getcwd2(tracee->reconf.tracee, path); if (status < 0) { note(tracee, ERROR, INTERNAL, "getcwd: %s", strerror(-status)); return -1; } } else strcpy(path, "/"); /* The ending "." ensures canonicalize() will report an error * if tracee->fs->cwd does not exist or if it is not a * directory. */ status = join_paths(3, path2, path, tracee->fs->cwd, "."); if (status < 0) { note(tracee, ERROR, INTERNAL, "getcwd: %s", strerror(-status)); return -1; } /* Initiale state for canonicalization. */ strcpy(path, "/"); status = canonicalize(tracee, path2, true, path, 0); if (status < 0) { note(tracee, WARNING, USER, "can't chdir(\"%s\") in the guest rootfs: %s", path2, strerror(-status)); note(tracee, INFO, USER, "default working directory is now \"/\""); strcpy(path, "/"); } chop_finality(path); /* Replace with the canonicalized working directory. */ TALLOC_FREE(tracee->fs->cwd); tracee->fs->cwd = talloc_strdup(tracee->fs, path); if (tracee->fs->cwd == NULL) return -1; talloc_set_name_const(tracee->fs->cwd, "$cwd"); /* Keep this special environment variable consistent. */ setenv("PWD", path, 1); return 0; } /** * Initialize @tracee->exe from @exe, i.e. canonicalize it from a * guest point-of-view. */ static int initialize_exe(Tracee *tracee, const char *exe) { char path[PATH_MAX]; int status; status = which(tracee, tracee->reconf.paths, path, exe ?: "/bin/sh"); if (status < 0) return -1; status = detranslate_path(tracee, path, NULL); if (status < 0) return -1; tracee->exe = talloc_strdup(tracee, path); if (tracee->exe == NULL) return -1; talloc_set_name_const(tracee->exe, "$exe"); return 0; } /** * Configure @tracee according to the command-line arguments stored in * @argv[]. This function returns the index in @argv[] of the command * to launch, otherwise -1 if an error occured. */ static int parse_config(Tracee *tracee, size_t argc, char *const argv[]) { option_handler_t handler = NULL; const Option *options; const Cli *cli = NULL; size_t argc_offset; size_t i, j, k; int status; if (get_care_cli != NULL) { /* Check if it's an self-extracting CARE archive. */ status = extract_archive_from_file("/proc/self/exe"); if (status == 0) { /* Yes it is, nothing more to do. */ exit_failure = 0; return -1; } /* Check if it's a valid CARE tool name. */ if (strncasecmp(basename(argv[0]), "care", strlen("care")) == 0) cli = get_care_cli(tracee->ctx); } /* Unknown tool name? Default to PRoot. */ if (cli == NULL) cli = get_proot_cli(tracee->ctx); tracee->tool_name = cli->name; if (argc == 1) { print_usage(tracee, cli, false); return -1; } for (i = 1; i < argc; i++) { const char *arg = argv[i]; /* The current argument is the value of a short option. */ if (handler != NULL) { status = handler(tracee, cli, arg); if (status < 0) return -1; handler = NULL; continue; } if (arg[0] != '-') break; /* End of PRoot options. */ options = cli->options; for (j = 0; options[j].class != NULL; j++) { const Option *option = &options[j]; /* A given option has several aliases. */ for (k = 0; ; k++) { const Argument *argument; size_t length; argument = &option->arguments[k]; /* End of aliases for this option. */ if (!argument->name) break; length = strlen(argument->name); if (strncmp(arg, argument->name, length) != 0) continue; /* Avoid ambiguities. */ if (strlen(arg) > length && arg[length] != argument->separator) { print_error_separator(tracee, argument); return -1; } /* No option value. */ if (!argument->value) { status = option->handler(tracee, cli, NULL); if (status < 0) return -1; goto known_option; } /* Value coalesced with to its option. */ if (argument->separator == arg[length]) { assert(strlen(arg) >= length); status = option->handler(tracee, cli, &arg[length + 1]); if (status < 0) return -1; goto known_option; } /* Avoid ambiguities. */ if (argument->separator != ' ') { print_error_separator(tracee, argument); return -1; } /* Short option with a separated value. */ handler = option->handler; goto known_option; } } note(tracee, ERROR, USER, "unknown option '%s'.", arg); return -1; known_option: if (handler != NULL && i == argc - 1) { note(tracee, ERROR, USER, "missing value for option '%s'.", arg); return -1; } } argc_offset = i; #define HOOK_CONFIG(callback) \ do { \ if (cli->callback != NULL) { \ status = cli->callback(tracee, cli, argc, argv, i); \ if (status < 0) \ return -1; \ i = status; \ } \ } while (0) HOOK_CONFIG(pre_initialize_bindings); /* The guest rootfs is now known: bindings specified by the * user (tracee->bindings.user) can be canonicalized. */ status = initialize_bindings(tracee); if (status < 0) return -1; HOOK_CONFIG(post_initialize_bindings); HOOK_CONFIG(pre_initialize_cwd); /* Bindings are now installed (tracee->bindings.guest & * tracee->bindings.host): the current working directory can * be canonicalized. */ status = initialize_cwd(tracee); if (status < 0) return -1; HOOK_CONFIG(post_initialize_cwd); HOOK_CONFIG(pre_initialize_exe); /* Bindings are now installed and the current working * directory is canonicalized: resolve path to @tracee->exe * and configure @tracee->cmdline. */ status = initialize_exe(tracee, argv[argc_offset]); if (status < 0) return -1; HOOK_CONFIG(post_initialize_exe); #undef HOOK_CONFIG print_config(tracee, &argv[argc_offset]); return argc_offset; } bool exit_failure = true; int main(int argc, char *const argv[]) { Tracee *tracee; int status; /* Configure the memory allocator. */ talloc_enable_leak_report(); #if defined(TALLOC_VERSION_MAJOR) && TALLOC_VERSION_MAJOR >= 2 talloc_set_log_stderr(); #endif /* Pre-create the first tracee (pid == 0). */ tracee = get_tracee(NULL, 0, true); if (tracee == NULL) goto error; tracee->pid = getpid(); /* Pre-configure the first tracee. */ status = parse_config(tracee, argc, argv); if (status < 0) goto error; /* Start the first tracee. */ status = launch_process(tracee, &argv[status]); if (status < 0) { print_execve_help(tracee, tracee->exe, status); goto error; } /* Start tracing the first tracee and all its children. */ exit(event_loop()); error: TALLOC_FREE(tracee); if (exit_failure) { fprintf(stderr, "fatal error: see `%s --help`.\n", basename(argv[0])); exit(EXIT_FAILURE); } else exit(EXIT_SUCCESS); } /** * Convert @value into an integer, then put the result into * *@variable. This function prints a warning and returns -1 if a * conversion error occured, otherwise it returns 0. */ int parse_integer_option(const Tracee *tracee, int *variable, const char *value, const char *option) { char *end_ptr = NULL; errno = 0; *variable = strtol(value, &end_ptr, 10); if (errno != 0 || end_ptr == value) { note(tracee, ERROR, USER, "option `%s` expects an integer value.", option); return -1; } return 0; } /** * Expand the environment variable in front of @string, if any. For * example, this function can expand "$HOME" or "$HOME/.ICEauthority". */ const char *expand_front_variable(TALLOC_CTX *context, const char *string) { const char *suffix; char *expanded; ptrdiff_t size; if (string[0] != '$') return string; suffix = strchr(string, '/'); if (suffix == NULL) return (getenv(&string[1]) ?: string); size = suffix - string; if (size <= 1) return string; expanded = talloc_strndup(context, &string[1], size - 1); if (expanded == NULL) return string; expanded = getenv(expanded); if (expanded == NULL) return string; expanded = talloc_asprintf(context, "%s%s", expanded, suffix); if (expanded == NULL) return string; return expanded; } /* Here follows the support for GCC function instrumentation. Build * with CFLAGS='-finstrument-functions -O0 -g' and LDFLAGS='-rdynamic' * to enable this mechanism. */ /* since we rely on GLIBC extensions, disable all of this code if * __GLIBC__ is not defined */ #if defined(__GLIBC__) static int indent_level = 0; void __cyg_profile_func_enter(void *this_function, void *call_site) DONT_INSTRUMENT; void __cyg_profile_func_enter(void *this_function, void *call_site) { void *const pointers[] = { this_function, call_site }; char **symbols = NULL; symbols = backtrace_symbols(pointers, 2); if (symbols == NULL) goto end; fprintf(stderr, "%*s from %s\n", (int) strlen(symbols[0]) + indent_level, symbols[0], symbols[1]); end: if (symbols != NULL) free(symbols); if (indent_level < INT_MAX) indent_level++; } void __cyg_profile_func_exit(void *this_function UNUSED, void *call_site UNUSED) DONT_INSTRUMENT; void __cyg_profile_func_exit(void *this_function UNUSED, void *call_site UNUSED) { if (indent_level > 0) indent_level--; } #endif proot-5.4.0/src/cli/cli.h000066400000000000000000000034261442763353300151600ustar00rootroot00000000000000/* This file is automatically generated from the documentation. EDIT AT YOUR OWN RISK. */ #ifndef CLI_H #define CLI_H #include #include "tracee/tracee.h" #include "attribute.h" typedef struct { const char *name; char separator; const char *value; } Argument; struct Cli; typedef int (*option_handler_t)(Tracee *tracee, const struct Cli *cli, const char *value); typedef struct { const char *class; option_handler_t handler; const char *description; const char *detail; Argument arguments[5]; } Option; #define END_OF_OPTIONS { .class = NULL, \ .arguments = {{ .name = NULL, .separator = '\0', .value = NULL }}, \ .handler = NULL, \ .description = NULL, \ .detail = NULL \ } typedef int (*initialization_hook_t)(Tracee *tracee, const struct Cli *cli, size_t argc, char *const argv[], size_t cursor); typedef struct Cli { const char *name; const char *version; const char *subtitle; const char *synopsis; const char *colophon; const char *logo; initialization_hook_t pre_initialize_bindings; initialization_hook_t post_initialize_bindings; initialization_hook_t pre_initialize_cwd; initialization_hook_t post_initialize_cwd; initialization_hook_t pre_initialize_exe; initialization_hook_t post_initialize_exe; void *private; const Option options[]; } Cli; extern const Cli *get_proot_cli(TALLOC_CTX *context); extern const Cli * WEAK get_care_cli(TALLOC_CTX *context); extern void print_usage(Tracee *tracee, const Cli *cli, bool detailed); extern void print_version(const Cli *cli); extern int parse_integer_option(const Tracee *tracee, int *variable, const char *value, const char *option); extern const char *expand_front_variable(TALLOC_CTX *context, const char *string); extern bool exit_failure; #endif /* CLI_H */ proot-5.4.0/src/cli/note.c000066400000000000000000000042751442763353300153540ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* errno, */ #include /* strerror(3), */ #include /* va_*, */ #include /* vfprintf(3), */ #include /* INT_MAX, */ #include "cli/note.h" #include "tracee/tracee.h" int global_verbose_level; const char *global_tool_name; /** * Print @message to the standard error stream according to its * @severity and @origin. */ void note(const Tracee *tracee, Severity severity, Origin origin, const char *message, ...) { const char *tool_name; va_list extra_params; int verbose_level; if (tracee == NULL) { verbose_level = global_verbose_level; tool_name = global_tool_name ?: ""; } else { verbose_level = tracee->verbose; tool_name = tracee->tool_name; } if (verbose_level < 0 && severity != ERROR) return; switch (severity) { case WARNING: fprintf(stderr, "%s warning: ", tool_name); break; case ERROR: fprintf(stderr, "%s error: ", tool_name); break; case INFO: default: fprintf(stderr, "%s info: ", tool_name); break; } if (origin == TALLOC) fprintf(stderr, "talloc: "); va_start(extra_params, message); vfprintf(stderr, message, extra_params); va_end(extra_params); switch (origin) { case SYSTEM: fprintf(stderr, ": "); perror(NULL); break; case TALLOC: break; case INTERNAL: case USER: default: fprintf(stderr, "\n"); break; } return; } proot-5.4.0/src/cli/note.h000066400000000000000000000027731442763353300153620ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef NOTE_H #define NOTE_H #include "tracee/tracee.h" #include "attribute.h" /* Specify where a notice is coming from. */ typedef enum { SYSTEM, INTERNAL, USER, TALLOC, } Origin; /* Specify the severity of a notice. */ typedef enum { ERROR, WARNING, INFO, } Severity; #define VERBOSE(tracee, level, message, args...) do { \ if (tracee == NULL || tracee->verbose >= (level)) \ note(tracee, INFO, INTERNAL, (message), ## args); \ } while (0) extern void note(const Tracee *tracee, Severity severity, Origin origin, const char *message, ...) FORMAT(printf, 4, 5); extern int global_verbose_level; extern const char *global_tool_name; #endif /* NOTE_H */ proot-5.4.0/src/cli/proot.c000066400000000000000000000225721442763353300155520ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* str*(3), */ #include /* assert(3), */ #include /* printf(3), fflush(3), */ #include /* write(2), */ #include "cli/cli.h" #include "cli/note.h" #include "extension/extension.h" #include "path/binding.h" #include "attribute.h" /* These should be included last. */ #include "build.h" #include "cli/proot.h" static int handle_option_r(Tracee *tracee, const Cli *cli UNUSED, const char *value) { Binding *binding; /* ``chroot $PATH`` is semantically equivalent to ``mount * --bind $PATH /``. */ binding = new_binding(tracee, value, "/", true); if (binding == NULL) return -1; return 0; } static int handle_option_b(Tracee *tracee, const Cli *cli UNUSED, const char *value) { char *host; char *guest; host = talloc_strdup(tracee->ctx, value); if (host == NULL) { note(tracee, ERROR, INTERNAL, "can't allocate memory"); return -1; } guest = strchr(host, ':'); if (guest != NULL) { *guest = '\0'; guest++; } new_binding(tracee, host, guest, true); return 0; } static int handle_option_q(Tracee *tracee, const Cli *cli UNUSED, const char *value) { const char *ptr; size_t nb_args; bool last; size_t i; nb_args = 0; ptr = value; while (1) { nb_args++; /* Keep consecutive non-space characters. */ while (*ptr != ' ' && *ptr != '\0') ptr++; /* End-of-string ? */ if (*ptr == '\0') break; /* Skip consecutive space separators. */ while (*ptr == ' ' && *ptr != '\0') ptr++; /* End-of-string ? */ if (*ptr == '\0') break; } tracee->qemu = talloc_zero_array(tracee, char *, nb_args + 1); if (tracee->qemu == NULL) return -1; talloc_set_name_const(tracee->qemu, "@qemu"); i = 0; ptr = value; do { const void *start; const void *end; last = true; /* Keep consecutive non-space characters. */ start = ptr; while (*ptr != ' ' && *ptr != '\0') ptr++; end = ptr; /* End-of-string ? */ if (*ptr == '\0') goto next; /* Remove consecutive space separators. */ while (*ptr == ' ' && *ptr != '\0') ptr++; /* End-of-string ? */ if (*ptr == '\0') goto next; last = false; next: tracee->qemu[i] = talloc_strndup(tracee->qemu, start, end - start); if (tracee->qemu[i] == NULL) return -1; i++; } while (!last); assert(i == nb_args); new_binding(tracee, "/", HOST_ROOTFS, true); new_binding(tracee, "/dev/null", "/etc/ld.so.preload", false); return 0; } static int handle_option_mixed_mode(Tracee *tracee, const Cli *cli UNUSED, const char *value UNUSED) { tracee->mixed_mode = value; return 0; } static int handle_option_w(Tracee *tracee, const Cli *cli UNUSED, const char *value) { tracee->fs->cwd = talloc_strdup(tracee->fs, value); if (tracee->fs->cwd == NULL) return -1; talloc_set_name_const(tracee->fs->cwd, "$cwd"); return 0; } static int handle_option_k(Tracee *tracee, const Cli *cli UNUSED, const char *value) { void *extension; int status; extension = get_extension(tracee, kompat_callback); if (extension != NULL) { note(tracee, WARNING, USER, "option -k was already specified"); note(tracee, INFO, USER, "only the last -k option is enabled"); TALLOC_FREE(extension); } status = initialize_extension(tracee, kompat_callback, value); if (status < 0) note(tracee, WARNING, INTERNAL, "option \"-k %s\" discarded", value); return 0; } static int handle_option_i(Tracee *tracee, const Cli *cli UNUSED, const char *value) { void *extension; extension = get_extension(tracee, fake_id0_callback); if (extension != NULL) { note(tracee, WARNING, USER, "option -i/-0/-S was already specified"); note(tracee, INFO, USER, "only the last -i/-0/-S option is enabled"); TALLOC_FREE(extension); } (void) initialize_extension(tracee, fake_id0_callback, value); return 0; } static int handle_option_0(Tracee *tracee, const Cli *cli, const char *value UNUSED) { return handle_option_i(tracee, cli, "0:0"); } static int handle_option_kill_on_exit(Tracee *tracee, const Cli *cli UNUSED, const char *value UNUSED) { tracee->killall_on_exit = true; return 0; } static int handle_option_v(Tracee *tracee, const Cli *cli UNUSED, const char *value) { int status; status = parse_integer_option(tracee, &tracee->verbose, value, "-v"); if (status < 0) return status; global_verbose_level = tracee->verbose; return 0; } extern unsigned char WEAK _binary_licenses_start; extern unsigned char WEAK _binary_licenses_end; static int handle_option_V(Tracee *tracee UNUSED, const Cli *cli, const char *value UNUSED) { size_t size; print_version(cli); printf("\n%s\n", cli->colophon); fflush(stdout); size = &_binary_licenses_end - &_binary_licenses_start; if (size > 0) write(1, &_binary_licenses_start, size); exit_failure = false; return -1; } static int handle_option_h(Tracee *tracee, const Cli *cli, const char *value UNUSED) { print_usage(tracee, cli, true); exit_failure = false; return -1; } static void new_bindings(Tracee *tracee, const char *bindings[], const char *value) { int i; for (i = 0; bindings[i] != NULL; i++) { const char *path; path = (strcmp(bindings[i], "*path*") != 0 ? expand_front_variable(tracee->ctx, bindings[i]) : value); new_binding(tracee, path, NULL, false); } } static int handle_option_R(Tracee *tracee, const Cli *cli, const char *value) { int status; status = handle_option_r(tracee, cli, value); if (status < 0) return status; new_bindings(tracee, recommended_bindings, value); return 0; } static int handle_option_S(Tracee *tracee, const Cli *cli, const char *value) { int status; status = handle_option_0(tracee, cli, value); if (status < 0) return status; status = handle_option_r(tracee, cli, value); if (status < 0) return status; new_bindings(tracee, recommended_su_bindings, value); return 0; } static int handle_option_p(Tracee *tracee, const Cli *cli UNUSED, const char *value) { int status = 0; char *port_in; char *port_out; port_in = talloc_strdup(tracee->ctx, value); if (port_in == NULL) { note(tracee, ERROR, INTERNAL, "can't allocate memory"); return -1; } port_out = strchr(port_in, ':'); if (port_out != NULL) { *port_out = '\0'; port_out++; } if(global_portmap_extension == NULL) status = initialize_extension(tracee, portmap_callback, value); if(status < 0) return status; status = add_portmap_entry(atoi(port_in), atoi(port_out)); return status; } static int handle_option_n(Tracee *tracee, const Cli *cli UNUSED, const char *value) { int status = 0; if(global_portmap_extension == NULL) status = initialize_extension(tracee, portmap_callback, value); if(status < 0) return status; status = activate_netcoop_mode(); return status; } #ifdef HAVE_PYTHON_EXTENSION static int handle_option_P(Tracee *tracee, const Cli *cli UNUSED, const char *value) { (void) initialize_extension(tracee, python_callback, value); return 0; } #endif static int handle_option_l(Tracee *tracee, const Cli *cli UNUSED, const char *value UNUSED) { return initialize_extension(tracee, link2symlink_callback, NULL); } /** * Initialize @tracee->qemu. */ static int post_initialize_exe(Tracee *tracee, const Cli *cli UNUSED, size_t argc UNUSED, char *const argv[] UNUSED, size_t cursor UNUSED) { char path[PATH_MAX]; int status; /* Nothing else to do ? */ if (tracee->qemu == NULL) return 0; /* Resolve the full guest path to tracee->qemu[0]. */ status = which(tracee->reconf.tracee, tracee->reconf.paths, path, tracee->qemu[0]); if (status < 0) return -1; /* Actually tracee->qemu[0] has to be a host path from the tracee's * point-of-view, not from the PRoot's point-of-view. See * translate_execve() for details. */ if (tracee->reconf.tracee != NULL) { status = detranslate_path(tracee->reconf.tracee, path, NULL); if (status < 0) return -1; } tracee->qemu[0] = talloc_strdup(tracee->qemu, path); if (tracee->qemu[0] == NULL) return -1; return 0; } /** * Initialize @tracee's fields that are mandatory for PRoot but that * are not required on the command line, i.e. "-w" and "-r". */ static int pre_initialize_bindings(Tracee *tracee, const Cli *cli, size_t argc UNUSED, char *const argv[] UNUSED, size_t cursor) { int status; /* Default to "." if no CWD were specified. */ if (tracee->fs->cwd == NULL) { status = handle_option_w(tracee, cli, "."); if (status < 0) return -1; } /* The default guest rootfs is "/" if none was specified. */ if (get_root(tracee) == NULL) { status = handle_option_r(tracee, cli, "/"); if (status < 0) return -1; } return cursor; } const Cli *get_proot_cli(TALLOC_CTX *context UNUSED) { global_tool_name = proot_cli.name; return &proot_cli; } proot-5.4.0/src/cli/proot.h000066400000000000000000000361271442763353300155600ustar00rootroot00000000000000/* This file is automatically generated from the documentation. EDIT AT YOUR OWN RISK. */ #ifndef PROOT_CLI_H #define PROOT_CLI_H #include "cli/cli.h" #ifndef VERSION #define VERSION "5.4.0" #endif static const char *recommended_bindings[] = { "/etc/host.conf", "/etc/hosts", "/etc/hosts.equiv", "/etc/mtab", "/etc/netgroup", "/etc/networks", "/etc/passwd", "/etc/group", "/etc/nsswitch.conf", "/etc/resolv.conf", "/etc/localtime", "/dev/", "/sys/", "/proc/", "/tmp/", "/run/", "/var/run/dbus/system_bus_socket", /* "/var/tmp/kdecache-$LOGNAME", */ "$HOME", "*path*", NULL, }; static const char *recommended_su_bindings[] = { "/etc/host.conf", "/etc/hosts", "/etc/nsswitch.conf", "/etc/resolv.conf", "/dev/", "/sys/", "/proc/", "/tmp/", "/run/shm", "$HOME", "*path*", NULL, }; static int handle_option_r(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_b(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_q(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_mixed_mode(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_w(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_v(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_V(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_h(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_k(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_0(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_i(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_p(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_n(Tracee *tracee, const Cli *cli, const char *value); #ifdef HAVE_PYTHON_EXTENSION static int handle_option_P(Tracee *tracee, const Cli *cli, const char *value); #endif static int handle_option_l(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_R(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_S(Tracee *tracee, const Cli *cli, const char *value); static int handle_option_kill_on_exit(Tracee *tracee, const Cli *cli, const char *value); static int pre_initialize_bindings(Tracee *, const Cli *, size_t, char *const *, size_t); static int post_initialize_exe(Tracee *, const Cli *, size_t, char *const *, size_t); static Cli proot_cli = { .version = VERSION, .name = "proot", .subtitle = "chroot, mount --bind, and binfmt_misc without privilege/setup", .synopsis = "proot [option] ... [command]", .colophon = "Visit https://proot-me.github.io for help, bug reports, suggestions, patches, ...\n\ Copyright (C) 2023 PRoot Developers, licensed under GPL v2 or later.", .logo = "\ _____ _____ ___\n\ | __ \\ __ \\_____ _____| |_\n\ | __/ / _ \\/ _ \\ _|\n\ |__| |__|__\\_____/\\_____/\\____|", .pre_initialize_bindings = pre_initialize_bindings, .post_initialize_exe = post_initialize_exe, .options = { { .class = "Regular options", .arguments = { { .name = "-r", .separator = ' ', .value = "path" }, { .name = "--rootfs", .separator = '=', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_r, .description = "Use *path* as the new guest root file-system, default is /.", .detail = "\tThe specified path typically contains a Linux distribution where\n\ \tall new programs will be confined. The default rootfs is /\n\ \twhen none is specified, this makes sense when the bind mechanism\n\ \tis used to relocate host files and directories, see the -b\n\ \toption and the Examples section for details.\n\ \t\n\ \tIt is recommended to use the -R or -S options instead.", }, { .class = "Regular options", .arguments = { { .name = "-b", .separator = ' ', .value = "path" }, { .name = "--bind", .separator = '=', .value = "path" }, { .name = "-m", .separator = ' ', .value = "path" }, { .name = "--mount", .separator = '=', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_b, .description = "Make the content of *path* accessible in the guest rootfs.", .detail = "\tThis option makes any file or directory of the host rootfs\n\ \taccessible in the confined environment just as if it were part of\n\ \tthe guest rootfs. By default the host path is bound to the same\n\ \tpath in the guest rootfs but users can specify any other location\n\ \twith the syntax: -b *host_path*:*guest_location*. If the\n\ \tguest location is a symbolic link, it is dereferenced to ensure\n\ \tthe new content is accessible through all the symbolic links that\n\ \tpoint to the overlaid content. In most cases this default\n\ \tbehavior shouldn't be a problem, although it is possible to\n\ \texplicitly not dereference the guest location by appending it the\n\ \t! character: -b *host_path*:*guest_location!*.", }, { .class = "Regular options", .arguments = { { .name = "-q", .separator = ' ', .value = "command" }, { .name = "--qemu", .separator = '=', .value = "command" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_q, .description = "Execute guest programs through QEMU as specified by *command*.", .detail = "\tEach time a guest program is going to be executed, PRoot inserts\n\ \tthe QEMU user-mode command in front of the initial request.\n\ \tThat way, guest programs actually run on a virtual guest CPU\n\ \temulated by QEMU user-mode. The native execution of host programs\n\ \tis still effective and the whole host rootfs is bound to\n\ \t/host-rootfs in the guest environment.", }, { .class = "Regular options", .arguments = { { .name = "--mixed-mode", .separator = ' ', .value = "value" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_mixed_mode, .description = "Disable the mixed-execution feature.", .detail = "\tDo not treat ELF executables specially when they appear to be\n\ \tnative executables of the host system.", }, { .class = "Regular options", .arguments = { { .name = "-w", .separator = ' ', .value = "path" }, { .name = "--pwd", .separator = '=', .value = "path" }, { .name = "--cwd", .separator = '=', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_w, .description = "Set the initial working directory to *path*.", .detail = "\tSome programs expect to be launched from a given directory but do\n\ \tnot perform any chdir by themselves. This option avoids the\n\ \tneed for running a shell and then entering the directory manually.", }, { .class = "Regular options", .arguments = { { .name = "--kill-on-exit", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_kill_on_exit, .description = "Kill all processes on command exit.", .detail = "\tWhen the executed command leaves orphean or detached processes\n\ \taround, proot waits until all processes possibly terminate. This option forces\n\ \tthe immediate termination of all tracee processes when the main command exits.", }, { .class = "Regular options", .arguments = { { .name = "-v", .separator = ' ', .value = "value" }, { .name = "--verbose", .separator = '=', .value = "value" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_v, .description = "Set the level of debug information to *value*.", .detail = "\tThe higher the integer value is, the more detailed debug\n\ \tinformation is printed to the standard error stream. A negative\n\ \tvalue makes PRoot quiet except on fatal errors.", }, { .class = "Regular options", .arguments = { { .name = "-V", .separator = '\0', .value = NULL }, { .name = "--version", .separator = '\0', .value = NULL }, { .name = "--about", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_V, .description = "Print version, copyright, license and contact, then exit.", .detail = "", }, { .class = "Regular options", .arguments = { { .name = "-h", .separator = '\0', .value = NULL }, { .name = "--help", .separator = '\0', .value = NULL }, { .name = "--usage", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_h, .description = "Print the version and the command-line usage, then exit.", .detail = "", }, { .class = "Extension options", .arguments = { { .name = "-k", .separator = ' ', .value = "string" }, { .name = "--kernel-release", .separator = '=', .value = "string" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_k, .description = "Make current kernel appear as kernel release *string*.", .detail = "\tIf a program is run on a kernel older than the one expected by its\n\ \tGNU C library, the following error is reported: \"FATAL: kernel too\n\ \told\". To be able to run such programs, PRoot can emulate some of\n\ \tthe features that are available in the kernel release specified by\n\ \t*string* but that are missing in the current kernel.", }, { .class = "Extension options", .arguments = { { .name = "-0", .separator = '\0', .value = NULL }, { .name = "--root-id", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_0, .description = "Make current user appear as \"root\" and fake its privileges.", .detail = "\tSome programs will refuse to work if they are not run with \"root\"\n\ \tprivileges, even if there is no technical reason for that. This\n\ \tis typically the case with package managers. This option allows\n\ \tusers to bypass this kind of limitation by faking the user/group\n\ \tidentity, and by faking the success of some operations like\n\ \tchanging the ownership of files, changing the root directory to\n\ \t/, ... Note that this option is quite limited compared to\n\ \tfakeroot.", }, { .class = "Extension options", .arguments = { { .name = "-i", .separator = ' ', .value = "string" }, { .name = "--change-id", .separator = '=', .value = "string" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_i, .description = "Make current user and group appear as *string* \"uid:gid\".", .detail = "\tThis option makes the current user and group appear as uid and\n\ \tgid. Likewise, files actually owned by the current user and\n\ \tgroup appear as if they were owned by uid and gid instead.\n\ \tNote that the -0 option is the same as -i 0:0.", }, { .class = "Extension options", .arguments = { { .name = "-p", .separator = ' ', .value = "string" }, { .name = "--port", .separator = '=', .value = "string" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_p, .description = "Map ports to others with the syntax as *string* \"port_in:port_out\".", .detail = "\tThis option makes PRoot intercept bind and connect system calls,\n\ \tand change the port they use. The port map is specified\n\ \twith the syntax: -b *port_in*:*port_out*. For example,\n\ \tan application that runs a MySQL server binding to 5432 wants\n\ \tto cohabit with other similar application, but doesn't have an\n\ \toption to change its port. PRoot can be used here to modify\n\ \tthis port: proot -p 5432:5433 myapplication. With this command,\n\ \tthe MySQL server will be bound to the port 5433.\n\ \tThis command can be repeated multiple times to map multiple ports.", }, { .class = "Extension options", .arguments = { { .name = "-n", .separator = '\0', .value = NULL }, { .name = "--netcoop", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_n, .description = "Enable the network cooperation mode.", .detail = "\tThis option makes PRoot intercept bind() system calls and\n\ \tchange the port they are binding to to 0. With this, the system will\n\ \tallocate an available port. Each time this is done, a new entry is added\n\ \tto the port mapping entries, so that corresponding connect() system calls\n\ \tuse the same resulting port. This network \"cooperation\" makes it possible\n\ \tto run multiple instances of a same program without worrying about the same ports\n\ \tbeing used twice.", }, #ifdef HAVE_PYTHON_EXTENSION { .class = "Extension options", .arguments = { { .name = "-P", .separator = ' ', .value = "string" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_P, .description = "Allow to access tracee information from python (experimental).", .detail = "\tThis option allow to launch a python script as an extension (experimental).", }, #endif { .class = "Extension options", .arguments = { { .name = "-l", .separator = '\0', .value = NULL }, { .name = "--link2symlink", .separator = '\0', .value = NULL }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_l, .description = "Enable the link2symlink extension.", .detail = "\tThis extension causes proot to create a symlink when a hardlink\n\ \tshould be created. Some environments don't let the user create a hardlink, this\n\ \toption should be used to fix it.", }, { .class = "Alias options", .arguments = { { .name = "-R", .separator = ' ', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_R, .description = "Alias: -r *path* + a couple of recommended -b.", .detail = "\tPrograms isolated in *path*, a guest rootfs, might still need to\n\ \taccess information about the host system, as it is illustrated in\n\ \tthe Examples section of the manual. These host information\n\ \tare typically: user/group definition, network setup, run-time\n\ \tinformation, users' files, ... On all Linux distributions, they\n\ \tall lie in a couple of host files and directories that are\n\ \tautomatically bound by this option:\n\ \t\n\ \t * /etc/host.conf\n\ \t * /etc/hosts\n\ \t * /etc/hosts.equiv\n\ \t * /etc/mtab\n\ \t * /etc/netgroup\n\ \t * /etc/networks\n\ \t * /etc/passwd\n\ \t * /etc/group\n\ \t * /etc/nsswitch.conf\n\ \t * /etc/resolv.conf\n\ \t * /etc/localtime\n\ \t * /dev/\n\ \t * /sys/\n\ \t * /proc/\n\ \t * /tmp/\n\ \t * /run/\n\ \t * /var/run/dbus/system_bus_socket\n\ \t * $HOME", }, { .class = "Alias options", .arguments = { { .name = "-S", .separator = ' ', .value = "path" }, { .name = NULL, .separator = '\0', .value = NULL } }, .handler = handle_option_S, .description = "Alias: -0 -r *path* + a couple of recommended -b.", .detail = "\tThis option is useful to safely create and install packages into\n\ \tthe guest rootfs. It is similar to the -R option except it\n\ \tenables the -0 option and binds only the following minimal set\n\ \tof paths to avoid unexpected changes on host files:\n\ \t\n\ \t * /etc/host.conf\n\ \t * /etc/hosts\n\ \t * /etc/nsswitch.conf\n\ \t * /etc/resolv.conf\n\ \t * /dev/\n\ \t * /sys/\n\ \t * /proc/\n\ \t * /tmp/\n\ \t * /run/shm\n\ \t * $HOME", }, END_OF_OPTIONS, }, }; #endif /* PROOT_CLI_H */ proot-5.4.0/src/compat.h000066400000000000000000000156201442763353300151240ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef COMPAT_H #define COMPAT_H /* Local definitions for compatibility with old and/or broken distros... */ # ifndef AT_NULL # define AT_NULL 0 # endif # ifndef AT_PHDR # define AT_PHDR 3 # endif # ifndef AT_PHENT # define AT_PHENT 4 # endif # ifndef AT_PHNUM # define AT_PHNUM 5 # endif # ifndef AT_BASE # define AT_BASE 7 # endif # ifndef AT_ENTRY # define AT_ENTRY 9 # endif # ifndef AT_RANDOM # define AT_RANDOM 25 # endif # ifndef AT_EXECFN # define AT_EXECFN 31 # endif # ifndef AT_SYSINFO # define AT_SYSINFO 32 # endif # ifndef AT_SYSINFO_EHDR # define AT_SYSINFO_EHDR 33 # endif # ifndef AT_FDCWD # define AT_FDCWD -100 # endif # ifndef AT_SYMLINK_FOLLOW # define AT_SYMLINK_FOLLOW 0x400 # endif # ifndef AT_REMOVEDIR # define AT_REMOVEDIR 0x200 # endif # ifndef AT_SYMLINK_NOFOLLOW # define AT_SYMLINK_NOFOLLOW 0x100 # endif # ifndef IN_DONT_FOLLOW # define IN_DONT_FOLLOW 0x02000000 # endif # ifndef WIFCONTINUED # define WIFCONTINUED(status) ((status) == 0xffff) # endif # ifndef PTRACE_GETREGS # define PTRACE_GETREGS 12 # endif # ifndef PTRACE_SETREGS # define PTRACE_SETREGS 13 # endif # ifndef PTRACE_GETFPREGS # define PTRACE_GETFPREGS 14 # endif # ifndef PTRACE_SETFPREGS # define PTRACE_SETFPREGS 15 # endif # ifndef PTRACE_GETFPXREGS # define PTRACE_GETFPXREGS 18 # endif # ifndef PTRACE_SETFPXREGS # define PTRACE_SETFPXREGS 19 # endif # ifndef PTRACE_SETOPTIONS # define PTRACE_SETOPTIONS 0x4200 # endif # ifndef PTRACE_GETEVENTMSG # define PTRACE_GETEVENTMSG 0x4201 # endif # ifndef PTRACE_GETREGSET # define PTRACE_GETREGSET 0x4204 # endif # ifndef PTRACE_SETREGSET # define PTRACE_SETREGSET 0x4205 # endif # ifndef PTRACE_SEIZE # define PTRACE_SEIZE 0x4206 # endif # ifndef PTRACE_INTERRUPT # define PTRACE_INTERRUPT 0x4207 # endif # ifndef PTRACE_LISTEN # define PTRACE_LISTEN 0x4208 # endif # ifndef PTRACE_O_TRACESYSGOOD # define PTRACE_O_TRACESYSGOOD 0x00000001 # endif # ifndef PTRACE_O_TRACEFORK # define PTRACE_O_TRACEFORK 0x00000002 # endif # ifndef PTRACE_O_TRACEVFORK # define PTRACE_O_TRACEVFORK 0x00000004 # endif # ifndef PTRACE_O_TRACECLONE # define PTRACE_O_TRACECLONE 0x00000008 # endif # ifndef PTRACE_O_TRACEEXEC # define PTRACE_O_TRACEEXEC 0x00000010 # endif # ifndef PTRACE_O_TRACEVFORKDONE # define PTRACE_O_TRACEVFORKDONE 0x00000020 # endif # ifndef PTRACE_O_TRACEEXIT # define PTRACE_O_TRACEEXIT 0x00000040 # endif # ifndef PTRACE_O_TRACESECCOMP # define PTRACE_O_TRACESECCOMP 0x00000080 # endif # ifndef PTRACE_EVENT_FORK # define PTRACE_EVENT_FORK 1 # endif # ifndef PTRACE_EVENT_VFORK # define PTRACE_EVENT_VFORK 2 # endif # ifndef PTRACE_EVENT_CLONE # define PTRACE_EVENT_CLONE 3 # endif # ifndef PTRACE_EVENT_EXEC # define PTRACE_EVENT_EXEC 4 # endif # ifndef PTRACE_EVENT_VFORK_DONE # define PTRACE_EVENT_VFORK_DONE 5 # endif # ifndef PTRACE_EVENT_EXIT # define PTRACE_EVENT_EXIT 6 # endif # ifndef PTRACE_EVENT_SECCOMP # define PTRACE_EVENT_SECCOMP 7 # endif # ifndef PTRACE_EVENT_SECCOMP2 # if PTRACE_EVENT_SECCOMP == 7 # define PTRACE_EVENT_SECCOMP2 8 # elif PTRACE_EVENT_SECCOMP == 8 # define PTRACE_EVENT_SECCOMP2 7 # else # error "unknown PTRACE_EVENT_SECCOMP value" # endif # endif # ifndef PTRACE_SET_SYSCALL # define PTRACE_SET_SYSCALL 23 # endif # ifndef PTRACE_GET_THREAD_AREA # define PTRACE_GET_THREAD_AREA 25 # endif # ifndef PTRACE_SET_THREAD_AREA # define PTRACE_SET_THREAD_AREA 26 # endif # ifndef PTRACE_GETVFPREGS # define PTRACE_GETVFPREGS 27 # endif # ifndef PTRACE_ARCH_PRCTL # define PTRACE_ARCH_PRCTL 30 # endif # ifndef ARCH_SET_GS # define ARCH_SET_GS 0x1001 # endif # ifndef ARCH_SET_FS # define ARCH_SET_FS 0x1002 # endif # ifndef ARCH_GET_GS # define ARCH_GET_FS 0x1003 # endif # ifndef ARCH_GET_FS # define ARCH_GET_GS 0x1004 # endif # ifndef PTRACE_SINGLEBLOCK # define PTRACE_SINGLEBLOCK 33 # endif # ifndef ADDR_NO_RANDOMIZE # define ADDR_NO_RANDOMIZE 0x0040000 # endif # ifndef SYS_ACCEPT4 # define SYS_ACCEPT4 18 # endif # ifndef TALLOC_FREE # define TALLOC_FREE(ctx) do { talloc_free(ctx); ctx = NULL; } while(0) # endif # ifndef PR_SET_NAME # define PR_SET_NAME 15 # endif # ifndef PR_SET_NO_NEW_PRIVS # define PR_SET_NO_NEW_PRIVS 38 # endif # ifndef PR_SET_SECCOMP # define PR_SET_SECCOMP 22 # endif # ifndef SECCOMP_MODE_FILTER # define SECCOMP_MODE_FILTER 2 # endif # ifndef talloc_get_type_abort # define talloc_get_type_abort talloc_get_type # endif # ifndef FUTEX_PRIVATE_FLAG # define FUTEX_PRIVATE_FLAG 128 # endif # ifndef EFD_SEMAPHORE # define EFD_SEMAPHORE 1 # endif # ifndef F_DUPFD_CLOEXEC # define F_DUPFD_CLOEXEC 1030 # endif # ifndef O_RDONLY # define O_RDONLY 00000000 # endif # ifndef O_CLOEXEC # define O_CLOEXEC 02000000 # endif # ifndef MAP_PRIVATE # define MAP_PRIVATE 0x02 # endif # ifndef MAP_FIXED # define MAP_FIXED 0x10 # endif # ifndef MAP_ANONYMOUS # define MAP_ANONYMOUS 0x20 # endif # ifndef PROT_READ # define PROT_READ 0x1 # endif # ifndef PROT_WRITE # define PROT_WRITE 0x2 # endif # ifndef PROT_EXEC # define PROT_EXEC 0x4 # endif # ifndef PROT_GROWSDOWN # define PROT_GROWSDOWN 0x01000000 # endif # ifndef NT_ARM_SYSTEM_CALL # define NT_ARM_SYSTEM_CALL 0x404 # endif #endif /* COMPAT_H */ proot-5.4.0/src/execve/000077500000000000000000000000001442763353300147435ustar00rootroot00000000000000proot-5.4.0/src/execve/aoxp.c000066400000000000000000000265731442763353300160730ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* ARG_MAX, */ #include /* assert(3), */ #include /* strlen(3), memcmp(3), memcpy(3), */ #include /* bzero(3), */ #include /* bool, true, false, */ #include /* E*, */ #include /* va_*, */ #include /* uint32_t, */ #include /* talloc_*, */ #include "arch.h" #include "tracee/tracee.h" #include "tracee/mem.h" #include "tracee/abi.h" #include "build.h" struct mixed_pointer { /* Pointer -- in tracee's address space -- to the current * object, if local == NULL. */ word_t remote; /* Pointer -- in tracer's address space -- to the current * object, if local != NULL. */ void *local; }; #include "execve/aoxp.h" /** * Read object pointed to by @array[@index] from tracee's memory, then * make @local_pointer points to the locally *cached* version. This * function returns -errno when an error occured, otherwise 0. */ int read_xpointee_as_object(ArrayOfXPointers *array, size_t index, void **local_pointer) { int status; int size; assert(index < array->length); /* Already cached locally? */ if (array->_xpointers[index].local != NULL) goto end; /* Remote NULL is mapped to local NULL. */ if (array->_xpointers[index].remote == 0) { array->_xpointers[index].local = NULL; goto end; } size = sizeof_xpointee(array, index); if (size < 0) return size; array->_xpointers[index].local = talloc_size(array, size); if (array->_xpointers[index].local == NULL) return -ENOMEM; /* Copy locally the remote object. */ status = read_data(TRACEE(array), array->_xpointers[index].local, array->_xpointers[index].remote, size); if (status < 0) { array->_xpointers[index].local = NULL; return status; } end: *local_pointer = array->_xpointers[index].local; return 0; } /** * Read string pointed to by @array[@index] from tracee's memory, then * make @local_pointer points to the locally *cached* version. This * function returns -errno when an error occured, otherwise 0. */ int read_xpointee_as_string(ArrayOfXPointers *array, size_t index, char **local_pointer) { char tmp[ARG_MAX]; int status; assert(index < array->length); /* Already cached locally? */ if (array->_xpointers[index].local != NULL) goto end; /* Remote NULL is mapped to local NULL. */ if (array->_xpointers[index].remote == 0) { array->_xpointers[index].local = NULL; goto end; } /* Copy locally the remote string into a temporary buffer. */ status = read_string(TRACEE(array), tmp, array->_xpointers[index].remote, ARG_MAX); if (status < 0) return status; if (status >= ARG_MAX) return -ENOMEM; /* Save the local string in a "persistent" buffer. */ array->_xpointers[index].local = talloc_strdup(array, tmp); if (array->_xpointers[index].local == NULL) return -ENOMEM; end: *local_pointer = array->_xpointers[index].local; return 0; } /** * This function returns the number of bytes of the string pointed to * by @array[@index], otherwise -errno if an error occured. */ int sizeof_xpointee_as_string(ArrayOfXPointers *array, size_t index) { char *string; int status; assert(index < array->length); status = read_xpointee_as_string(array, index, &string); if (status < 0) return status; if (string == NULL) return 0; return strlen(string) + 1; } /** * Compare object pointed to by @array[@index] with object pointed to * by @local_reference. This function returns 1 if they are * equivalent, 0 otherwise. On error, -errno is returned. */ int compare_xpointee_generic(ArrayOfXPointers *array, size_t index, const void *local_reference) { void *object; int status; assert(index < array->length); status = read_xpointee(array, index, &object); if (status < 0) return status; if (object == NULL && local_reference == NULL) return 1; if (object == NULL && local_reference != NULL) return 0; if (object != NULL && local_reference == NULL) return 0; status = sizeof_xpointee(array, index); if (status < 0) return status; return (int) (memcmp(object, local_reference, status) == 0); } /** * This function returns the index in @array of the first pointee * equivalent to the @local_reference pointee, otherwise it returns * -errno if an error occured. */ int find_xpointee(ArrayOfXPointers *array, const void *local_reference) { size_t i; for (i = 0; i < array->length; i++) { int status; status = compare_xpointee(array, i, local_reference); if (status < 0) return status; if (status != 0) break; } return i; } /** * Make @array[@index] points to a copy of the string pointed to by * @string. This function returns -errno when an error occured, * otherwise 0. */ int write_xpointee_as_string(ArrayOfXPointers *array, size_t index, const char *string) { assert(index < array->length); array->_xpointers[index].local = talloc_strdup(array, string); if (array->_xpointers[index].local == NULL) return -ENOMEM; return 0; } /** * Make @array[@index ... @index + @nb_xpointees] points to a copy of * the variadic arguments. This function returns -errno when an error * occured, otherwise 0. */ int write_xpointees(ArrayOfXPointers *array, size_t index, size_t nb_xpointees, ...) { va_list va_xpointees; int status; size_t i; va_start(va_xpointees, nb_xpointees); for (i = 0; i < nb_xpointees; i++) { void *object = va_arg(va_xpointees, void *); status = write_xpointee(array, index + i, object); if (status < 0) goto end; } status = 0; end: va_end(va_xpointees); return status; } /** * Resize the @array at the given @index by the @delta_nb_entries. * This function returns -errno when an error occured, otherwise 0. */ int resize_array_of_xpointers(ArrayOfXPointers *array, size_t index, ssize_t delta_nb_entries) { size_t nb_moved_entries; size_t new_length; void *tmp; assert(index < array->length); if (delta_nb_entries == 0) return 0; new_length = array->length + delta_nb_entries; nb_moved_entries = array->length - index; if (delta_nb_entries > 0) { tmp = talloc_realloc(array, array->_xpointers, XPointer, new_length); if (tmp == NULL) return -ENOMEM; array->_xpointers = tmp; memmove(array->_xpointers + index + delta_nb_entries, array->_xpointers + index, nb_moved_entries * sizeof(XPointer)); bzero(array->_xpointers + index, delta_nb_entries * sizeof(XPointer)); } else { assert(delta_nb_entries <= 0); assert(index >= (size_t) -delta_nb_entries); memmove(array->_xpointers + index + delta_nb_entries, array->_xpointers + index, nb_moved_entries * sizeof(XPointer)); tmp = talloc_realloc(array, array->_xpointers, XPointer, new_length); if (tmp == NULL) return -ENOMEM; array->_xpointers = tmp; } array->length = new_length; return 0; } /** * Copy into *@array_ the pointer array pointed to by @reg from * @tracee's memory space. Only the first @nb_entries are copied, * unless it is 0 then all the entries up to the NULL pointer are * copied. This function returns -errno when an error occured, * otherwise 0. */ int fetch_array_of_xpointers(Tracee *tracee, ArrayOfXPointers **array_, Reg reg, size_t nb_entries) { word_t pointer = 1; /* ie. != 0 */ word_t address; ArrayOfXPointers *array; size_t i; assert(array_ != NULL); *array_ = talloc_zero(tracee->ctx, ArrayOfXPointers); if (*array_ == NULL) return -ENOMEM; array = *array_; address = peek_reg(tracee, CURRENT, reg); for (i = 0; nb_entries != 0 ? i < nb_entries : pointer != 0; i++) { void *tmp = talloc_realloc(array, array->_xpointers, XPointer, i + 1); if (tmp == NULL) return -ENOMEM; array->_xpointers = tmp; pointer = peek_word(tracee, address + i * sizeof_word(tracee)); if (errno != 0) return -errno; array->_xpointers[i].remote = pointer; array->_xpointers[i].local = NULL; } array->length = i; /* By default, assume it is an array of string pointers. */ array->read_xpointee = (read_xpointee_t) read_xpointee_as_string; array->sizeof_xpointee = sizeof_xpointee_as_string; array->write_xpointee = (write_xpointee_t) write_xpointee_as_string; /* By default, use generic callbacks: they rely on * array->read_xpointee() and array->sizeof_xpointee(). */ array->compare_xpointee = compare_xpointee_generic; return 0; } /** * Copy @array into tracee's memory space, then put in @reg the * address where it was copied. This function returns -errno if an * error occured, otherwise 0. */ int push_array_of_xpointers(ArrayOfXPointers *array, Reg reg) { Tracee *tracee; struct iovec *local; size_t local_count; size_t total_size; word_t *pod_array; word_t tracee_ptr; int status; size_t i; /* Nothing to do, for sure. */ if (array == NULL) return 0; tracee = TRACEE(array); /* The pointer table is a POD array in the tracee's memory. */ pod_array = talloc_zero_size(tracee->ctx, array->length * sizeof_word(tracee)); if (pod_array == NULL) return -ENOMEM; /* There's one vector per modified pointee + one vector for the * pod array. */ local = talloc_zero_array(tracee->ctx, struct iovec, array->length + 1); if (local == NULL) return -ENOMEM; /* The pod array is expected to be at the beginning of the * allocated memory by the caller. */ total_size = array->length * sizeof_word(tracee); local[0].iov_base = pod_array; local[0].iov_len = total_size; local_count = 1; /* Create one vector for each modified pointee. */ for (i = 0; i < array->length; i++) { ssize_t size; if (array->_xpointers[i].local == NULL) continue; /* At this moment, we only know the offsets in the * tracee's memory block. */ array->_xpointers[i].remote = total_size; size = sizeof_xpointee(array, i); if (size < 0) return size; total_size += size; local[local_count].iov_base = array->_xpointers[i].local; local[local_count].iov_len = size; local_count++; } /* Nothing has changed, don't update anything. */ if (local_count == 1) return 0; assert(local_count < array->length + 1); /* Modified pointees and the pod array are stored in a tracee's * memory block. */ tracee_ptr = alloc_mem(tracee, total_size); if (tracee_ptr == 0) return -E2BIG; /* Now, we know the absolute addresses in the tracee's * memory. */ for (i = 0; i < array->length; i++) { if (array->_xpointers[i].local != NULL) array->_xpointers[i].remote += tracee_ptr; if (is_32on64_mode(tracee)) ((uint32_t *) pod_array)[i] = array->_xpointers[i].remote; else pod_array[i] = array->_xpointers[i].remote; } /* Write all the modified pointees and the pod array at once. */ status = writev_data(tracee, tracee_ptr, local, local_count); if (status < 0) return status; poke_reg(tracee, reg, tracee_ptr); return 0; } proot-5.4.0/src/execve/aoxp.h000066400000000000000000000060431442763353300160660ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef AOXP_H #define AOXP_H #include #include "tracee/reg.h" #include "arch.h" typedef struct array_of_xpointers ArrayOfXPointers; typedef int (*read_xpointee_t)(ArrayOfXPointers *array, size_t index, void **object); typedef int (*write_xpointee_t)(ArrayOfXPointers *array, size_t index, const void *object); typedef int (*compare_xpointee_t)(ArrayOfXPointers *array, size_t index, const void *reference); typedef int (*sizeof_xpointee_t)(ArrayOfXPointers *array, size_t index); typedef struct mixed_pointer XPointer; struct array_of_xpointers { XPointer *_xpointers; size_t length; read_xpointee_t read_xpointee; write_xpointee_t write_xpointee; compare_xpointee_t compare_xpointee; sizeof_xpointee_t sizeof_xpointee; }; static inline int read_xpointee(ArrayOfXPointers *array, size_t index, void **object) { return array->read_xpointee(array, index, object); } static inline int write_xpointee(ArrayOfXPointers *array, size_t index, const void *object) { return array->write_xpointee(array, index, object); } static inline int compare_xpointee(ArrayOfXPointers *array, size_t index, const void *reference) { return array->compare_xpointee(array, index, reference); } static inline int sizeof_xpointee(ArrayOfXPointers *array, size_t index) { return array->sizeof_xpointee(array, index); } extern int find_xpointee(ArrayOfXPointers *array, const void *reference); extern int resize_array_of_xpointers(ArrayOfXPointers *array, size_t index, ssize_t nb_delta_entries); extern int fetch_array_of_xpointers(Tracee *tracee, ArrayOfXPointers **array, Reg reg, size_t nb_entries); extern int push_array_of_xpointers(ArrayOfXPointers *array, Reg reg); extern int read_xpointee_as_object(ArrayOfXPointers *array, size_t index, void **object); extern int read_xpointee_as_string(ArrayOfXPointers *array, size_t index, char **string); extern int write_xpointee_as_string(ArrayOfXPointers *array, size_t index, const char *string); extern int write_xpointees(ArrayOfXPointers *array, size_t index, size_t nb_xpointees, ...); extern int compare_xpointee_generic(ArrayOfXPointers *array, size_t index, const void *reference); extern int sizeof_xpointee_as_string(ArrayOfXPointers *array, size_t index); #endif /* AOXP_H */ proot-5.4.0/src/execve/auxv.c000066400000000000000000000113251442763353300160740ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* AT_*, */ #include /* assert(3), */ #include /* E*, */ #include /* write(3), close(3), */ #include /* open(2), */ #include /* open(2), */ #include /* open(2), */ #include "execve/auxv.h" #include "syscall/sysnum.h" #include "tracee/tracee.h" #include "tracee/mem.h" #include "tracee/reg.h" #include "tracee/abi.h" #include "arch.h" /** * Add the given vector [@type, @value] to @vectors. This function * returns -errno if an error occurred, otherwise 0. */ int add_elf_aux_vector(ElfAuxVector **vectors, word_t type, word_t value) { ElfAuxVector *tmp; size_t nb_vectors; assert(*vectors != NULL); nb_vectors = talloc_array_length(*vectors); /* Sanity checks. */ assert(nb_vectors > 0); assert((*vectors)[nb_vectors - 1].type == AT_NULL); tmp = talloc_realloc(talloc_parent(*vectors), *vectors, ElfAuxVector, nb_vectors + 1); if (tmp == NULL) return -ENOMEM; *vectors = tmp; /* Replace the sentinel with the new vector. */ (*vectors)[nb_vectors - 1].type = type; (*vectors)[nb_vectors - 1].value = value; /* Restore the sentinel. */ (*vectors)[nb_vectors].type = AT_NULL; (*vectors)[nb_vectors].value = 0; return 0; } /** * Get the address of the the ELF auxiliary vectors table for the * given @tracee. This function returns 0 if an error occurred. */ word_t get_elf_aux_vectors_address(const Tracee *tracee) { word_t address; word_t data; /* Sanity check: this works only in execve sysexit. */ assert(IS_IN_SYSEXIT2(tracee, PR_execve)); /* Right after execve, the stack layout is: * * argc, argv[0], ..., 0, envp[0], ..., 0, auxv[0].type, auxv[0].value, ..., 0, 0 */ address = peek_reg(tracee, CURRENT, STACK_POINTER); /* Read: argc */ data = peek_word(tracee, address); if (errno != 0) return 0; /* Skip: argc, argv, 0 */ address += (1 + data + 1) * sizeof_word(tracee); /* Skip: envp, 0 */ do { data = peek_word(tracee, address); if (errno != 0) return 0; address += sizeof_word(tracee); } while (data != 0); return address; } /** * Fetch ELF auxiliary vectors stored at the given @address in * @tracee's memory. This function returns NULL if an error occurred, * otherwise it returns a pointer to the new vectors, in an ABI * independent form (the Talloc parent of this pointer is * @tracee->ctx). */ ElfAuxVector *fetch_elf_aux_vectors(const Tracee *tracee, word_t address) { ElfAuxVector *vectors = NULL; ElfAuxVector vector; int status; /* It is assumed the sentinel always exists. */ vectors = talloc_array(tracee->ctx, ElfAuxVector, 1); if (vectors == NULL) return NULL; vectors[0].type = AT_NULL; vectors[0].value = 0; while (1) { vector.type = peek_word(tracee, address); if (errno != 0) return NULL; address += sizeof_word(tracee); if (vector.type == AT_NULL) break; /* Already added. */ vector.value = peek_word(tracee, address); if (errno != 0) return NULL; address += sizeof_word(tracee); status = add_elf_aux_vector(&vectors, vector.type, vector.value); if (status < 0) return NULL; } return vectors; } /** * Push ELF auxiliary @vectors to the given @address in @tracee's * memory. This function returns -errno if an error occurred, * otherwise 0. */ int push_elf_aux_vectors(const Tracee* tracee, ElfAuxVector *vectors, word_t address) { size_t i; for (i = 0; vectors[i].type != AT_NULL; i++) { poke_word(tracee, address, vectors[i].type); if (errno != 0) return -errno; address += sizeof_word(tracee); poke_word(tracee, address, vectors[i].value); if (errno != 0) return -errno; address += sizeof_word(tracee); } poke_word(tracee, address, AT_NULL); if (errno != 0) return -errno; address += sizeof_word(tracee); poke_word(tracee, address, 0); if (errno != 0) return -errno; address += sizeof_word(tracee); return 0; } proot-5.4.0/src/execve/auxv.h000066400000000000000000000025041442763353300161000ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef AUXV #define AUXV #include "tracee/tracee.h" #include "arch.h" typedef struct elf_aux_vector { word_t type; word_t value; } ElfAuxVector; extern word_t get_elf_aux_vectors_address(const Tracee *tracee); extern ElfAuxVector *fetch_elf_aux_vectors(const Tracee *tracee, word_t address); extern int add_elf_aux_vector(ElfAuxVector **vectors, word_t type, word_t value); extern int push_elf_aux_vectors(const Tracee* tracee, ElfAuxVector *vectors, word_t address); #endif /* AUXV */ proot-5.4.0/src/execve/elf.c000066400000000000000000000107331442763353300156610ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* open(2), */ #include /* read(2), close(2), */ #include /* EACCES, ENOTSUP, */ #include /* UINT64_MAX, */ #include /* PATH_MAX, */ #include /* str*(3), memcpy(3), */ #include /* assert(3), */ #include /* talloc_*, */ #include /* bool, true, false, */ #include "execve/elf.h" #include "tracee/tracee.h" #include "cli/note.h" #include "arch.h" #include "compat.h" /** * Open the ELF file @t_path and extract its header into @elf_header. * This function returns -errno if an error occured, otherwise the * file descriptor for @t_path. */ int open_elf(const char *t_path, ElfHeader *elf_header) { int fd; int status; /* * Read the ELF header. */ fd = open(t_path, O_RDONLY); if (fd < 0) return -errno; /* Check if it is an ELF file. */ status = read(fd, elf_header, sizeof(ElfHeader)); if (status < 0) { status = -errno; goto end; } if ((size_t) status < sizeof(ElfHeader) || ELF_IDENT(*elf_header, 0) != 0x7f || ELF_IDENT(*elf_header, 1) != 'E' || ELF_IDENT(*elf_header, 2) != 'L' || ELF_IDENT(*elf_header, 3) != 'F') { status = -ENOEXEC; goto end; } /* Check if it is a known class (32-bit or 64-bit). */ if ( !IS_CLASS32(*elf_header) && !IS_CLASS64(*elf_header)) { status = -ENOEXEC; goto end; } status = 0; end: /* Delayed error handling. */ if (status < 0) { close(fd); return status; } return fd; } /** * Invoke @callback(..., @data) for each program headers from the * specified ELF file (referenced by @fd, with the given @elf_header). * This function returns -errno if an error occured, or it returns * immediately the value != 0 returned by @callback, otherwise 0. */ int iterate_program_headers(const Tracee *tracee, int fd, const ElfHeader *elf_header, program_headers_iterator_t callback, void *data) { ProgramHeader program_header; uint64_t elf_phoff; uint16_t elf_phentsize; uint16_t elf_phnum; int status; int i; /* Get class-specific fields. */ elf_phnum = ELF_FIELD(*elf_header, phnum); elf_phentsize = ELF_FIELD(*elf_header, phentsize); elf_phoff = ELF_FIELD(*elf_header, phoff); /* * Some sanity checks regarding the current * support of the ELF specification in PRoot. */ if (elf_phnum >= 0xffff) { note(tracee, WARNING, INTERNAL, "%d: big PH tables are not yet supported.", fd); return -ENOTSUP; } if (!KNOWN_PHENTSIZE(*elf_header, elf_phentsize)) { note(tracee, WARNING, INTERNAL, "%d: unsupported size of program header.", fd); return -ENOTSUP; } status = (int) lseek(fd, elf_phoff, SEEK_SET); if (status < 0) return -errno; for (i = 0; i < elf_phnum; i++) { status = read(fd, &program_header, elf_phentsize); if (status != elf_phentsize) return (status < 0 ? -errno : -ENOTSUP); status = callback(elf_header, &program_header, data); if (status != 0) return status; } return 0; } /** * Check if @host_path is an ELF file for the host architecture. */ bool is_host_elf(const Tracee *tracee, const char *host_path) { int host_elf_machine[] = HOST_ELF_MACHINE; static int force_foreign = -1; ElfHeader elf_header; uint16_t elf_machine; int fd; int i; if (force_foreign < 0) force_foreign = (getenv("PROOT_FORCE_FOREIGN_BINARY") != NULL); if (force_foreign > 0 || !tracee->qemu) return false; fd = open_elf(host_path, &elf_header); if (fd < 0) return false; close(fd); elf_machine = ELF_FIELD(elf_header, machine); for (i = 0; host_elf_machine[i] != 0; i++) { if (host_elf_machine[i] == elf_machine) { VERBOSE(tracee, 1, "'%s' is a host ELF", host_path); return true; } } return false; } proot-5.4.0/src/execve/elf.h000066400000000000000000000106511442763353300156650ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef ELF_H #define ELF_H #define EI_NIDENT 16 #include #include typedef struct { unsigned char e_ident[EI_NIDENT]; uint16_t e_type; uint16_t e_machine; uint32_t e_version; uint32_t e_entry; uint32_t e_phoff; uint32_t e_shoff; uint32_t e_flags; uint16_t e_ehsize; uint16_t e_phentsize; uint16_t e_phnum; uint16_t e_shentsize; uint16_t e_shnum; uint16_t e_shstrndx; } ElfHeader32; typedef struct { unsigned char e_ident[EI_NIDENT]; uint16_t e_type; uint16_t e_machine; uint32_t e_version; uint64_t e_entry; uint64_t e_phoff; uint64_t e_shoff; uint32_t e_flags; uint16_t e_ehsize; uint16_t e_phentsize; uint16_t e_phnum; uint16_t e_shentsize; uint16_t e_shnum; uint16_t e_shstrndx; } ElfHeader64; typedef union { ElfHeader32 class32; ElfHeader64 class64; } ElfHeader; typedef struct { uint32_t p_type; uint32_t p_offset; uint32_t p_vaddr; uint32_t p_paddr; uint32_t p_filesz; uint32_t p_memsz; uint32_t p_flags; uint32_t p_align; } ProgramHeader32; typedef struct { uint32_t p_type; uint32_t p_flags; uint64_t p_offset; uint64_t p_vaddr; uint64_t p_paddr; uint64_t p_filesz; uint64_t p_memsz; uint64_t p_align; } ProgramHeader64; typedef union { ProgramHeader32 class32; ProgramHeader64 class64; } ProgramHeader; /* Object type: */ #define ET_REL 1 #define ET_EXEC 2 #define ET_DYN 3 #define ET_CORE 4 /* Segment flags: */ #define PF_X 1 #define PF_W 2 #define PF_R 4 typedef enum { PT_LOAD = 1, PT_DYNAMIC = 2, PT_INTERP = 3, PT_GNU_STACK = 0x6474e551, } SegmentType; typedef struct { int32_t d_tag; uint32_t d_val; } DynamicEntry32; typedef struct { int64_t d_tag; uint64_t d_val; } DynamicEntry64; typedef union { DynamicEntry32 class32; DynamicEntry64 class64; } DynamicEntry; typedef enum { DT_STRTAB = 5, DT_RPATH = 15, DT_RUNPATH = 29 } DynamicType; /* The following macros are also compatible with ELF 64-bit. */ #define ELF_IDENT(header, index) (header).class32.e_ident[(index)] #define ELF_CLASS(header) ELF_IDENT(header, 4) #define IS_CLASS32(header) (ELF_CLASS(header) == 1) #define IS_CLASS64(header) (ELF_CLASS(header) == 2) /* Helper to access a @field of the structure ElfHeaderXX. */ #define ELF_FIELD(header, field) \ (IS_CLASS64(header) \ ? (header).class64. e_ ## field \ : (header).class32. e_ ## field) /* Helper to access a @field of the structure ProgramHeaderXX */ #define PROGRAM_FIELD(ehdr, phdr, field) \ (IS_CLASS64(ehdr) \ ? (phdr).class64. p_ ## field \ : (phdr).class32. p_ ## field) /* Helper to access a @field of the structure DynamicEntryXX */ #define DYNAMIC_FIELD(ehdr, dynent, field) \ (IS_CLASS64(ehdr) \ ? (dynent).class64. d_ ## field \ : (dynent).class32. d_ ## field) #define KNOWN_PHENTSIZE(header, size) \ ( (IS_CLASS32(header) && (size) == sizeof(ProgramHeader32)) \ || (IS_CLASS64(header) && (size) == sizeof(ProgramHeader64))) #define IS_POSITION_INDENPENDANT(elf_header) \ (ELF_FIELD((elf_header), type) == ET_DYN) #include "tracee/tracee.h" extern int open_elf(const char *t_path, ElfHeader *elf_header); extern bool is_host_elf(const Tracee *tracee, const char *t_path); typedef int (* program_headers_iterator_t)(const ElfHeader *elf_header, const ProgramHeader *program_header, void *data); extern int iterate_program_headers(const Tracee *tracee, int fd, const ElfHeader *elf_header, program_headers_iterator_t callback, void *data); #endif /* ELF_H */ proot-5.4.0/src/execve/enter.c000066400000000000000000000452061442763353300162330ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* lstat(2), lseek(2), */ #include /* lstat(2), lseek(2), fchmod(2), */ #include /* access(2), lstat(2), close(2), read(2), */ #include /* E*, */ #include /* assert(3), */ #include /* talloc*, */ #include /* PROT_*, */ #include /* strlen(3), strcpy(3), */ #include /* getenv(3), */ #include /* fwrite(3), */ #include /* assert(3), */ #include "execve/execve.h" #include "execve/shebang.h" #include "execve/aoxp.h" #include "execve/ldso.h" #include "execve/elf.h" #include "path/path.h" #include "path/temp.h" #include "path/binding.h" #include "tracee/tracee.h" #include "syscall/syscall.h" #include "syscall/sysnum.h" #include "arch.h" #include "cli/note.h" #define P(a) PROGRAM_FIELD(load_info->elf_header, *program_header, a) /** * Add @program_header (type PT_LOAD) to @load_info->mappings. This * function returns -errno if an error occured, otherwise it returns * 0. */ static int add_mapping(const Tracee *tracee UNUSED, LoadInfo *load_info, const ProgramHeader *program_header) { size_t index; word_t start_address; word_t end_address; static word_t page_size = 0; static word_t page_mask = 0; if (page_size == 0) { page_size = sysconf(_SC_PAGE_SIZE); if ((int) page_size <= 0) page_size = 0x1000; page_mask = ~(page_size - 1); } if (load_info->mappings == NULL) index = 0; else index = talloc_array_length(load_info->mappings); load_info->mappings = talloc_realloc(load_info, load_info->mappings, Mapping, index + 1); if (load_info->mappings == NULL) return -ENOMEM; start_address = P(vaddr) & page_mask; end_address = (P(vaddr) + P(filesz) + page_size) & page_mask; load_info->mappings[index].fd = -1; /* Unknown yet. */ load_info->mappings[index].offset = P(offset) & page_mask; load_info->mappings[index].addr = start_address; load_info->mappings[index].length = end_address - start_address; load_info->mappings[index].flags = MAP_PRIVATE | MAP_FIXED; load_info->mappings[index].prot = ( (P(flags) & PF_R ? PROT_READ : 0) | (P(flags) & PF_W ? PROT_WRITE : 0) | (P(flags) & PF_X ? PROT_EXEC : 0)); /* "If the segment's memory size p_memsz is larger than the * file size p_filesz, the "extra" bytes are defined to hold * the value 0 and to follow the segment's initialized area." * -- man 7 elf. */ if (P(memsz) > P(filesz)) { /* How many extra bytes in the current page? */ load_info->mappings[index].clear_length = end_address - P(vaddr) - P(filesz); /* Create new pages for the remaining extra bytes. */ start_address = end_address; end_address = (P(vaddr) + P(memsz) + page_size) & page_mask; if (end_address > start_address) { index++; load_info->mappings = talloc_realloc(load_info, load_info->mappings, Mapping, index + 1); if (load_info->mappings == NULL) return -ENOMEM; load_info->mappings[index].fd = -1; /* Anonymous. */ load_info->mappings[index].offset = 0; load_info->mappings[index].addr = start_address; load_info->mappings[index].length = end_address - start_address; load_info->mappings[index].clear_length = 0; load_info->mappings[index].flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED; load_info->mappings[index].prot = load_info->mappings[index - 1].prot; } } else load_info->mappings[index].clear_length = 0; return 0; } /** * Translate @user_path into @host_path and check if this latter exists, is * executable and is a regular file. This function returns -errno if * an error occured, 0 otherwise. */ int translate_and_check_exec(Tracee *tracee, char host_path[PATH_MAX], const char *user_path) { struct stat statl; int status; if (user_path[0] == '\0') return -ENOEXEC; status = translate_path(tracee, host_path, AT_FDCWD, user_path, true); if (status < 0) return status; status = access(host_path, F_OK); if (status < 0) return -ENOENT; status = access(host_path, X_OK); if (status < 0) return -EACCES; status = lstat(host_path, &statl); if (status < 0) return -EPERM; return 0; } /** * Add @program_header (type PT_INTERP) to @load_info->interp. This * function returns -errno if an error occured, otherwise it returns * 0. */ static int add_interp(Tracee *tracee, int fd, LoadInfo *load_info, const ProgramHeader *program_header) { char host_path[PATH_MAX]; char *user_path; int status; /* Only one PT_INTERP segment is allowed. */ if (load_info->interp != NULL) return -EINVAL; load_info->interp = talloc_zero(load_info, LoadInfo); if (load_info->interp == NULL) return -ENOMEM; user_path = talloc_size(tracee->ctx, P(filesz) + 1); if (user_path == NULL) return -ENOMEM; /* Remember pread(2) doesn't change the * current position in the file. */ status = pread(fd, user_path, P(filesz), P(offset)); if ((size_t) status != P(filesz)) /* Unexpected size. */ status = -EACCES; if (status < 0) return status; user_path[P(filesz)] = '\0'; /* When a QEMU command was specified: * * - if it's a foreign binary we are reading the ELF * interpreter of QEMU instead. * * - if it's a host binary, we are reading its ELF * interpreter. * * In both case, it lies in "/host-rootfs" from a guest * point-of-view. */ if (tracee->qemu != NULL && user_path[0] == '/') { user_path = talloc_asprintf(tracee->ctx, "%s%s", HOST_ROOTFS, user_path); if (user_path == NULL) return -ENOMEM; } status = translate_and_check_exec(tracee, host_path, user_path); if (status < 0) return status; load_info->interp->host_path = talloc_strdup(load_info->interp, host_path); if (load_info->interp->host_path == NULL) return -ENOMEM; load_info->interp->user_path = talloc_strdup(load_info->interp, user_path); if (load_info->interp->user_path == NULL) return -ENOMEM; return 0; } #undef P struct add_load_info_data { LoadInfo *load_info; Tracee *tracee; int fd; }; /** * This function is a program header iterator. It invokes * add_mapping() or add_interp(), according to the type of * @program_header. This function returns -errno if an error * occurred, otherwise 0. */ static int add_load_info(const ElfHeader *elf_header, const ProgramHeader *program_header, void *data_) { struct add_load_info_data *data = data_; int status; switch (PROGRAM_FIELD(*elf_header, *program_header, type)) { case PT_LOAD: status = add_mapping(data->tracee, data->load_info, program_header); if (status < 0) return status; break; case PT_INTERP: status = add_interp(data->tracee, data->fd, data->load_info, program_header); if (status < 0) return status; break; case PT_GNU_STACK: data->load_info->needs_executable_stack |= ((PROGRAM_FIELD(*elf_header, *program_header, flags) & PF_X) != 0); break; default: break; } return 0; } /** * Extract the load info from @load->host_path. This function returns * -errno if an error occured, otherwise it returns 0. */ static int extract_load_info(Tracee *tracee, LoadInfo *load_info) { struct add_load_info_data data; int fd = -1; int status; assert(load_info != NULL); assert(load_info->host_path != NULL); fd = open_elf(load_info->host_path, &load_info->elf_header); if (fd < 0) return fd; /* Sanity check. */ switch (ELF_FIELD(load_info->elf_header, type)) { case ET_EXEC: case ET_DYN: break; default: status = -EINVAL; goto end; } data.load_info = load_info; data.tracee = tracee; data.fd = fd; status = iterate_program_headers(tracee, fd, &load_info->elf_header, add_load_info, &data); end: if (fd >= 0) close(fd); return status; } /** * Add @load_base to each adresses of @load_info. */ static void add_load_base(LoadInfo *load_info, word_t load_base) { size_t nb_mappings; size_t i; nb_mappings = talloc_array_length(load_info->mappings); for (i = 0; i < nb_mappings; i++) load_info->mappings[i].addr += load_base; if (IS_CLASS64(load_info->elf_header)) load_info->elf_header.class64.e_entry += load_base; else load_info->elf_header.class32.e_entry += load_base; } /** * Compute the final load address for each position independant * objects of @tracee. * * TODO: support for ASLR. */ static void compute_load_addresses(Tracee *tracee) { if (IS_POSITION_INDENPENDANT(tracee->load_info->elf_header) && tracee->load_info->mappings[0].addr == 0) { #if defined(HAS_LOADER_32BIT) if (IS_CLASS32(tracee->load_info->elf_header)) add_load_base(tracee->load_info, EXEC_PIC_ADDRESS_32); else #endif add_load_base(tracee->load_info, EXEC_PIC_ADDRESS); } /* Nothing more to do? */ if (tracee->load_info->interp == NULL) return; if (IS_POSITION_INDENPENDANT(tracee->load_info->interp->elf_header) && tracee->load_info->interp->mappings[0].addr == 0) { #if defined(HAS_LOADER_32BIT) if (IS_CLASS32(tracee->load_info->elf_header)) add_load_base(tracee->load_info->interp, INTERP_PIC_ADDRESS_32); else #endif add_load_base(tracee->load_info->interp, INTERP_PIC_ADDRESS); } } /** * Expand in argv[] and envp[] the runner for @user_path, if needed. * This function returns -errno if an error occurred, otherwise 0. On * success, both @host_path and @user_path point to the program to * execute (respectively from host and guest point-of-views), and both * @tracee's argv[] (pointed to by SYSARG_2) @tracee's envp[] (pointed * to by SYSARG_3) are correctly updated. */ static int expand_runner(Tracee* tracee, char host_path[PATH_MAX], char user_path[PATH_MAX]) { ArrayOfXPointers *envp; char *argv0; int status; /* Execution of host programs when QEMU is in use relies on * LD_ environment variables. */ status = fetch_array_of_xpointers(tracee, &envp, SYSARG_3, 0); if (status < 0) return status; /* Environment variables should be compared with the "name" * part of the "name=value" string format. */ envp->compare_xpointee = (compare_xpointee_t) compare_xpointee_env; /* No need to adjust argv[] if it's a host binary (a.k.a * mixed-mode). */ if (tracee->mixed_mode || !is_host_elf(tracee, host_path)) { ArrayOfXPointers *argv; size_t nb_qemu_args; size_t i; status = fetch_array_of_xpointers(tracee, &argv, SYSARG_2, 0); if (status < 0) return status; status = read_xpointee_as_string(argv, 0, &argv0); if (status < 0) return status; /* Assuming PRoot was invoked this way: * * proot -q 'qemu-arm -cpu cortex-a9' ... * * a call to: * * execve("/bin/true", { "true", NULL }, ...) * * becomes: * * execve("/usr/bin/qemu", * { "qemu", "-cpu", "cortex-a9", "-0", "true", "/bin/true", NULL }, ...) */ nb_qemu_args = talloc_array_length(tracee->qemu) - 1; status = resize_array_of_xpointers(argv, 1, nb_qemu_args + 2); if (status < 0) return status; for (i = 0; i < nb_qemu_args; i++) { status = write_xpointee(argv, i, tracee->qemu[i]); if (status < 0) return status; } status = write_xpointees(argv, i, 3, "-0", argv0, user_path); if (status < 0) return status; /* Ensure LD_ features should not be applied to QEMU * iteself. */ status = ldso_env_passthru(tracee, envp, argv, "-E", "-U", i); if (status < 0) return status; status = push_array_of_xpointers(argv, SYSARG_2); if (status < 0) return status; /* Launch the runner in lieu of the initial * program. */ assert(strlen(tracee->qemu[0]) + strlen(HOST_ROOTFS) < PATH_MAX); assert(tracee->qemu[0][0] == '/'); strcpy(host_path, tracee->qemu[0]); strcpy(user_path, HOST_ROOTFS); strcat(user_path, host_path); } /* Provide information to the host dynamic linker to find host * libraries (remember the guest root file-system contains * libraries for the guest architecture only). */ status = rebuild_host_ldso_paths(tracee, host_path, envp); if (status < 0) return status; status = push_array_of_xpointers(envp, SYSARG_3); if (status < 0) return status; return 0; } extern unsigned char _binary_loader_elf_start[]; extern unsigned char _binary_loader_elf_end[]; extern unsigned char WEAK _binary_loader_m32_elf_start[]; extern unsigned char WEAK _binary_loader_m32_elf_end[]; /** * Extract the built-in loader. This function returns NULL if an * error occurred, otherwise it returns the path to the extracted * loader. Note: @tracee is only used for notification purpose. */ static char *extract_loader(const Tracee *tracee, bool wants_32bit_version) { char path[PATH_MAX]; size_t status2; void *start; size_t size; int status; int fd; char *loader_path = NULL; FILE *file = NULL; file = open_temp_file(NULL, "prooted"); if (file == NULL) goto end; fd = fileno(file); if (wants_32bit_version) { start = (void *) _binary_loader_m32_elf_start; size = (size_t)(_binary_loader_m32_elf_end-_binary_loader_m32_elf_start); } else { start = (void *) _binary_loader_elf_start; size = (size_t) (_binary_loader_elf_end-_binary_loader_elf_start); } status2 = write(fd, start, size); if (status2 != size) { note(tracee, ERROR, SYSTEM, "can't write the loader"); goto end; } status = fchmod(fd, S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); if (status < 0) { note(tracee, ERROR, SYSTEM, "can't change loader permissions (u+rx)"); goto end; } status = readlink_proc_pid_fd(getpid(), fd, path); if (status < 0) { note(tracee, ERROR, INTERNAL, "can't retrieve loader path (/proc/self/fd/)"); goto end; } status = access(path, X_OK); if (status < 0) { note(tracee, ERROR, INTERNAL, "it seems the current temporary directory (%s) " "is mounted with no execution permission.", get_temp_directory()); note(tracee, INFO, USER, "Please set PROOT_TMP_DIR env. variable to an alternate " "location ('%s/tmp' for example).", get_root(tracee)); goto end; } loader_path = talloc_strdup(talloc_autofree_context(), path); if (loader_path == NULL) { note(tracee, ERROR, INTERNAL, "can't allocate memory"); goto end; } if (tracee->verbose >= 2) note(tracee, INFO, INTERNAL, "loader: %s", loader_path); end: if (file != NULL) { status = fclose(file); if (status < 0) note(tracee, WARNING, SYSTEM, "can't close loader file"); } return loader_path; } /** * Get the path to the loader for the given @tracee. This function * returns NULL if an error occurred. */ static inline const char *get_loader_path(const Tracee *tracee) { static char *loader_path = NULL; #if defined(HAS_LOADER_32BIT) static char *loader32_path = NULL; if (IS_CLASS32(tracee->load_info->elf_header)) { loader32_path = loader32_path ?: getenv("PROOT_LOADER_32") ?: extract_loader(tracee, true); return loader32_path; } else #endif { loader_path = loader_path ?: getenv("PROOT_LOADER") ?: extract_loader(tracee, false); return loader_path; } } /** * Extract all the information that will be required by * translate_load_*(). This function returns -errno if an error * occured, otherwise 0. */ int translate_execve_enter(Tracee *tracee) { char user_path[PATH_MAX]; char host_path[PATH_MAX]; char new_exe[PATH_MAX]; char *raw_path; const char *loader_path; int status; if (IS_NOTIFICATION_PTRACED_LOAD_DONE(tracee)) { /* Syscalls can now be reported to its ptracer. */ tracee->as_ptracee.ignore_loader_syscalls = false; /* Cancel this spurious execve, it was only used as a * notification. */ set_sysnum(tracee, PR_void); return 0; } status = get_sysarg_path(tracee, user_path, SYSARG_1); if (status < 0) return status; /* Remember the user path before it is overwritten by * expand_shebang(). This "raw" path is useful to fix the * value of AT_EXECFN and /proc/{@tracee->pid}/comm. */ raw_path = talloc_strdup(tracee->ctx, user_path); if (raw_path == NULL) return -ENOMEM; status = expand_shebang(tracee, host_path, user_path); if (status < 0) /* The Linux kernel actually returns -EACCES when * trying to execute a directory. */ return status == -EISDIR ? -EACCES : status; /* user_path is modified only if there's an interpreter * (ie. for a script or with qemu). */ if (status == 0 && tracee->qemu == NULL) TALLOC_FREE(raw_path); /* Remember the new value for "/proc/self/exe". It points to * a canonicalized guest path, hence detranslate_path() * instead of using user_path directly. */ strcpy(new_exe, host_path); status = detranslate_path(tracee, new_exe, NULL); if (status >= 0) { talloc_unlink(tracee, tracee->new_exe); tracee->new_exe = talloc_strdup(tracee, new_exe); } else tracee->new_exe = NULL; if (tracee->qemu != NULL) { status = expand_runner(tracee, host_path, user_path); if (status < 0) return status; } TALLOC_FREE(tracee->load_info); tracee->load_info = talloc_zero(tracee, LoadInfo); if (tracee->load_info == NULL) return -ENOMEM; tracee->load_info->host_path = talloc_strdup(tracee->load_info, host_path); if (tracee->load_info->host_path == NULL) return -ENOMEM; tracee->load_info->user_path = talloc_strdup(tracee->load_info, user_path); if (tracee->load_info->user_path == NULL) return -ENOMEM; tracee->load_info->raw_path = (raw_path != NULL ? talloc_reparent(tracee->ctx, tracee->load_info, raw_path) : talloc_reference(tracee->load_info, tracee->load_info->user_path)); if (tracee->load_info->raw_path == NULL) return -ENOMEM; status = extract_load_info(tracee, tracee->load_info); if (status < 0) return status; if (tracee->load_info->interp != NULL) { status = extract_load_info(tracee, tracee->load_info->interp); if (status < 0) return status; /* An ELF interpreter is supposed to be * standalone. */ if (tracee->load_info->interp->interp != NULL) return -EINVAL; } compute_load_addresses(tracee); /* Execute the loader instead of the program. */ loader_path = get_loader_path(tracee); if (loader_path == NULL) return -ENOENT; status = set_sysarg_path(tracee, loader_path, SYSARG_1); if (status < 0) return status; /* Mask to its ptracer syscalls performed by the loader. */ tracee->as_ptracee.ignore_loader_syscalls = true; return 0; } proot-5.4.0/src/execve/execve.h000066400000000000000000000035701442763353300164000ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef EXECVE_H #define EXECVE_H #include /* PATH_MAX, */ #include "tracee/tracee.h" #include "execve/elf.h" #include "arch.h" extern int translate_execve_enter(Tracee *tracee); extern void translate_execve_exit(Tracee *tracee); extern int translate_and_check_exec(Tracee *tracee, char host_path[PATH_MAX], const char *user_path); typedef struct mapping { word_t addr; word_t length; word_t clear_length; word_t prot; word_t flags; word_t fd; word_t offset; } Mapping; typedef struct load_info { char *host_path; char *user_path; char *raw_path; Mapping *mappings; ElfHeader elf_header; bool needs_executable_stack; struct load_info *interp; } LoadInfo; #define IS_NOTIFICATION_PTRACED_LOAD_DONE(tracee) ( \ (tracee)->as_ptracee.ptracer != NULL \ && peek_reg((tracee), ORIGINAL, SYSARG_1) == (word_t) 1 \ && peek_reg((tracee), ORIGINAL, SYSARG_4) == (word_t) 2 \ && peek_reg((tracee), ORIGINAL, SYSARG_5) == (word_t) 3 \ && peek_reg((tracee), ORIGINAL, SYSARG_6) == (word_t) 4) #endif /* EXECVE_H */ proot-5.4.0/src/execve/exit.c000066400000000000000000000346041442763353300160670ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* AT_*, */ #include /* talloc*, */ #include /* MAP_*, */ #include /* assert(3), */ #include /* strlen(3), strerror(3), */ #include /* bzero(3), */ #include /* kill(2), SIG*, */ #include /* write(2), */ #include /* E*, */ #include "execve/execve.h" #include "execve/elf.h" #include "loader/script.h" #include "tracee/reg.h" #include "tracee/abi.h" #include "tracee/mem.h" #include "syscall/sysnum.h" #include "execve/auxv.h" #include "path/binding.h" #include "path/temp.h" #include "cli/note.h" /** * Fill @path with the content of @vectors, formatted according to * @ptracee's current ABI. */ static int fill_file_with_auxv(const Tracee *ptracee, const char *path, const ElfAuxVector *vectors) { const ssize_t current_sizeof_word = sizeof_word(ptracee); ssize_t status; int fd = -1; int i; fd = open(path, O_WRONLY); if (fd < 0) return -1; i = 0; do { status = write(fd, &vectors[i].type, current_sizeof_word); if (status < current_sizeof_word) { status = -1; goto end; } status = write(fd, &vectors[i].value, current_sizeof_word); if (status < current_sizeof_word) { status = -1; goto end; } } while (vectors[i++].type != AT_NULL); status = 0; end: if (fd >= 0) (void) close(fd); return status; } /** * Bind content of @vectors over /proc/{@ptracee->pid}/auxv. This * function returns -1 if an error occurred, otherwise 0. */ static int bind_proc_pid_auxv(const Tracee *ptracee) { word_t vectors_address; ElfAuxVector *vectors; const char *guest_path; const char *host_path; Binding *binding; int status; vectors_address = get_elf_aux_vectors_address(ptracee); if (vectors_address == 0) return -1; vectors = fetch_elf_aux_vectors(ptracee, vectors_address); if (vectors == NULL) return -1; /* Path to these ELF auxiliary vectors. */ guest_path = talloc_asprintf(ptracee->ctx, "/proc/%d/auxv", ptracee->pid); if (guest_path == NULL) return -1; /* Remove binding to this path, if any. It contains ELF * auxiliary vectors of the previous execve(2). */ binding = get_binding(ptracee, GUEST, guest_path); if (binding != NULL && compare_paths(binding->guest.path, guest_path) == PATHS_ARE_EQUAL) { remove_binding_from_all_lists(ptracee, binding); TALLOC_FREE(binding); } host_path = create_temp_file(ptracee->ctx, "auxv"); if (host_path == NULL) return -1; status = fill_file_with_auxv(ptracee, host_path, vectors); if (status < 0) return -1; /* Note: this binding will be removed once ptracee gets freed. */ binding = insort_binding3(ptracee, ptracee->life_context, host_path, guest_path); if (binding == NULL) return -1; /* This temporary file (host_path) will be removed once the * binding is freed. */ talloc_reparent(ptracee->ctx, binding, host_path); return 0; } /** * Convert @mappings into load @script statements at the given @cursor * position. This function returns the new cursor position. */ static void *transcript_mappings(void *cursor, const Mapping *mappings) { size_t nb_mappings; size_t i; nb_mappings = talloc_array_length(mappings); for (i = 0; i < nb_mappings; i++) { LoadStatement *statement = cursor; if ((mappings[i].flags & MAP_ANONYMOUS) != 0) statement->action = LOAD_ACTION_MMAP_ANON; else statement->action = LOAD_ACTION_MMAP_FILE; statement->mmap.addr = mappings[i].addr; statement->mmap.length = mappings[i].length; statement->mmap.prot = mappings[i].prot; statement->mmap.offset = mappings[i].offset; statement->mmap.clear_length = mappings[i].clear_length; cursor += LOAD_STATEMENT_SIZE(*statement, mmap); } return cursor; } /** * Convert @tracee->load_info into a load script, then transfer this * latter into @tracee's memory. */ static int transfer_load_script(Tracee *tracee) { const word_t stack_pointer = peek_reg(tracee, CURRENT, STACK_POINTER); static word_t page_size = 0; static word_t page_mask = 0; word_t entry_point; size_t script_size; size_t strings_size; size_t string1_size; size_t string2_size; size_t string3_size; size_t padding_size; word_t string1_address; word_t string2_address; word_t string3_address; void *buffer; size_t buffer_size; bool needs_executable_stack; LoadStatement *statement; void *cursor; int status; if (page_size == 0) { page_size = sysconf(_SC_PAGE_SIZE); if ((int) page_size <= 0) page_size = 0x1000; page_mask = ~(page_size - 1); } needs_executable_stack = (tracee->load_info->needs_executable_stack || ( tracee->load_info->interp != NULL && tracee->load_info->interp->needs_executable_stack)); /* Strings addresses are required to generate the load script, * for "open" actions. Since I want to generate it in one * pass, these strings will be put right below the current * stack pointer -- the only known adresses so far -- in the * "strings area". */ string1_size = strlen(tracee->load_info->user_path) + 1; string2_size = (tracee->load_info->interp == NULL ? 0 : strlen(tracee->load_info->interp->user_path) + 1); string3_size = (tracee->load_info->raw_path == tracee->load_info->user_path ? 0 : strlen(tracee->load_info->raw_path) + 1); /* A padding will be appended at the end of the load script * (a.k.a "strings area") to ensure this latter is aligned properly. */ padding_size = (stack_pointer - string1_size - string2_size - string3_size) % STACK_ALIGNMENT; strings_size = string1_size + string2_size + string3_size + padding_size; string1_address = stack_pointer - strings_size; string2_address = stack_pointer - strings_size + string1_size; string3_address = (string3_size == 0 ? string1_address : stack_pointer - strings_size + string1_size + string2_size); /* Compute the size of the load script. */ script_size = LOAD_STATEMENT_SIZE(*statement, open) + (LOAD_STATEMENT_SIZE(*statement, mmap) * talloc_array_length(tracee->load_info->mappings)) + (tracee->load_info->interp == NULL ? 0 : LOAD_STATEMENT_SIZE(*statement, open) + (LOAD_STATEMENT_SIZE(*statement, mmap) * talloc_array_length(tracee->load_info->interp->mappings))) + (needs_executable_stack ? LOAD_STATEMENT_SIZE(*statement, make_stack_exec) : 0) + LOAD_STATEMENT_SIZE(*statement, start); /* Allocate enough room for both the load script and the * strings area. */ buffer_size = script_size + strings_size; buffer = talloc_zero_size(tracee->ctx, buffer_size); if (buffer == NULL) return -ENOMEM; cursor = buffer; /* Load script statement: open. */ statement = cursor; statement->action = LOAD_ACTION_OPEN; statement->open.string_address = string1_address; cursor += LOAD_STATEMENT_SIZE(*statement, open); /* Load script statements: mmap. */ cursor = transcript_mappings(cursor, tracee->load_info->mappings); if (tracee->load_info->interp != NULL) { /* Load script statement: open. */ statement = cursor; statement->action = LOAD_ACTION_OPEN_NEXT; statement->open.string_address = string2_address; cursor += LOAD_STATEMENT_SIZE(*statement, open); /* Load script statements: mmap. */ cursor = transcript_mappings(cursor, tracee->load_info->interp->mappings); entry_point = ELF_FIELD(tracee->load_info->interp->elf_header, entry); } else entry_point = ELF_FIELD(tracee->load_info->elf_header, entry); if (needs_executable_stack) { /* Load script statement: stack_exec. */ statement = cursor; statement->action = LOAD_ACTION_MAKE_STACK_EXEC; statement->make_stack_exec.start = stack_pointer & page_mask; cursor += LOAD_STATEMENT_SIZE(*statement, make_stack_exec); } /* Load script statement: start. */ statement = cursor; /* Start of the program slightly differs when ptraced. */ if (tracee->as_ptracee.ptracer != NULL) statement->action = LOAD_ACTION_START_TRACED; else statement->action = LOAD_ACTION_START; statement->start.stack_pointer = stack_pointer; statement->start.entry_point = entry_point; statement->start.at_phent = ELF_FIELD(tracee->load_info->elf_header, phentsize); statement->start.at_phnum = ELF_FIELD(tracee->load_info->elf_header, phnum); statement->start.at_entry = ELF_FIELD(tracee->load_info->elf_header, entry); statement->start.at_phdr = ELF_FIELD(tracee->load_info->elf_header, phoff) + tracee->load_info->mappings[0].addr; statement->start.at_execfn = string3_address; cursor += LOAD_STATEMENT_SIZE(*statement, start); /* Sanity check. */ assert((uintptr_t) cursor - (uintptr_t) buffer == script_size); /* Convert the load script to the expected format. */ if (is_32on64_mode(tracee)) { int i; for (i = 0; buffer + i * sizeof(uint64_t) < cursor; i++) ((uint32_t *) buffer)[i] = ((uint64_t *) buffer)[i]; } /* Concatenate the load script and the strings. */ memcpy(cursor, tracee->load_info->user_path, string1_size); cursor += string1_size; if (string2_size != 0) { memcpy(cursor, tracee->load_info->interp->user_path, string2_size); cursor += string2_size; } if (string3_size != 0) { memcpy(cursor, tracee->load_info->raw_path, string3_size); cursor += string3_size; } /* Sanity check. */ cursor += padding_size; assert((uintptr_t) cursor - (uintptr_t) buffer == buffer_size); /* Allocate enough room in tracee's memory for the load * script, and make the first user argument points to this * location. Note that it is safe to update the stack pointer * manually since we are in execve sysexit. However it should * be done before transfering data since the kernel might not * allow page faults below the stack pointer. */ poke_reg(tracee, STACK_POINTER, stack_pointer - buffer_size); poke_reg(tracee, USERARG_1, stack_pointer - buffer_size); /* Copy everything in the tracee's memory at once. */ status = write_data(tracee, stack_pointer - buffer_size, buffer, buffer_size); if (status < 0) return status; /* Tracee's stack content is now as follow: * * +------------+ <- initial stack pointer (higher address) * | padding | * +------------+ * | string3 | * +------------+ * | string2 | * +------------+ * | string1 | * +------------+ * | start | * +------------+ * | mmap anon | * +------------+ * | mmap file | * +------------+ * | open next | * +------------+ * | mmap anon. | * +------------+ * | mmap file | * +------------+ * | open | * +------------+ <- stack pointer, userarg1 (word aligned) */ /* Remember we are in the sysexit stage, so be sure the * current register values will be used as-is at the end. */ save_current_regs(tracee, ORIGINAL); tracee->_regs_were_changed = true; return 0; } /** * Start the loading of @tracee. This function returns no error since * it's either too late to do anything useful (the calling process is * already replaced) or the error reported by the kernel * (syscall_result < 0) will be propagated as-is. */ void translate_execve_exit(Tracee *tracee) { word_t syscall_result; int status; if (IS_NOTIFICATION_PTRACED_LOAD_DONE(tracee)) { /* Be sure not to confuse the ptracer with an * unexpected syscall/returned value. */ poke_reg(tracee, SYSARG_RESULT, 0); set_sysnum(tracee, PR_execve); /* According to most ABIs, all registers have * undefined values at program startup except: * * - the stack pointer * - the instruction pointer * - the rtld_fini pointer * - the state flags */ poke_reg(tracee, STACK_POINTER, peek_reg(tracee, ORIGINAL, SYSARG_2)); poke_reg(tracee, INSTR_POINTER, peek_reg(tracee, ORIGINAL, SYSARG_3)); poke_reg(tracee, RTLD_FINI, 0); poke_reg(tracee, STATE_FLAGS, 0); /* Restore registers to their current values. */ save_current_regs(tracee, ORIGINAL); tracee->_regs_were_changed = true; /* This is is required to make GDB work correctly * under PRoot, however it deserves to be used * unconditionally. */ (void) bind_proc_pid_auxv(tracee); /* If the PTRACE_O_TRACEEXEC option is *not* in effect * for the execing tracee, the kernel delivers an * extra SIGTRAP to the tracee after execve(2) * *returns*. This is an ordinary signal (similar to * one which can be generated by "kill -TRAP"), not a * special kind of ptrace-stop. Employing * PTRACE_GETSIGINFO for this signal returns si_code * set to 0 (SI_USER). This signal may be blocked by * signal mask, and thus may be delivered (much) * later. -- man 2 ptrace * * This signal is delayed so far since the program was * not fully loaded yet; GDB would get "invalid * adress" errors otherwise. */ if ((tracee->as_ptracee.options & PTRACE_O_TRACEEXEC) == 0) kill(tracee->pid, SIGTRAP); return; } syscall_result = peek_reg(tracee, CURRENT, SYSARG_RESULT); if ((int) syscall_result < 0) return; /* Execve happened; commit the new "/proc/self/exe". */ if (tracee->new_exe != NULL) { (void) talloc_unlink(tracee, tracee->exe); tracee->exe = talloc_reference(tracee, tracee->new_exe); talloc_set_name_const(tracee->exe, "$exe"); } /* New processes have no heap. The process could've been cloned with * CLONE_VM so it has been sharing the heap with its parent. execve() * discards the VM so make sure to reallocate new heap. */ if (talloc_reference_count(tracee->heap) > 0) { talloc_unlink(tracee, tracee->heap); tracee->heap = talloc_zero(tracee, Heap); if (!tracee->heap) note(tracee, ERROR, INTERNAL, "can't allocate heap"); } else { bzero(tracee->heap, sizeof(Heap)); } /* Transfer the load script to the loader. */ status = transfer_load_script(tracee); if (status < 0) note(tracee, ERROR, INTERNAL, "can't transfer load script: %s", strerror(-status)); return; } proot-5.4.0/src/execve/ldso.c000066400000000000000000000370231442763353300160550ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* bool, true, false, */ #include /* strlen(3), strcpy(3), strcat(3), strcmp(3), */ #include /* getenv(3), */ #include /* assert(3), */ #include /* ENOMEM, */ #include /* close(2), */ #include /* PATH_MAX, ARG_MAX, */ #include "execve/ldso.h" #include "execve/elf.h" #include "execve/aoxp.h" #include "tracee/tracee.h" #include "cli/note.h" /** * Check if the environment @variable has the given @name. */ bool is_env_name(const char *variable, const char *name) { size_t length = strlen(name); return (variable[0] == name[0] && length < strlen(variable) && variable[length] == '=' && strncmp(variable, name, length) == 0); } /** * This function returns 1 or 0 depending on the equivalence of the * @reference environment variable and the one pointed to by the entry * in @envp at the given @index, otherwise it returns -errno when an * error occured. */ int compare_xpointee_env(ArrayOfXPointers *envp, size_t index, const char *reference) { char *value; int status; assert(index < envp->length); status = read_xpointee_as_string(envp, index, &value); if (status < 0) return status; if (value == NULL) return 0; return (int)is_env_name(value, reference); } /** * This function ensures that environment variables related to the * dynamic linker are applied to the emulated program, not to QEMU * itself. For instance, let's say the user has entered the * command-line below: * * env LD_TRACE_LOADED_OBJECTS=1 /bin/ls * * It should be converted to: * * qemu -E LD_TRACE_LOADED_OBJECTS=1 /bin/ls * * instead of: * * env LD_TRACE_LOADED_OBJECTS=1 qemu /bin/ls * * Note that the LD_LIBRARY_PATH variable is always required to run * QEMU (a host binary): * * env LD_LIBRARY_PATH=... qemu -U LD_LIBRARY_PATH /bin/ls * * or when LD_LIBRARY_PATH was also specified by the user: * * env LD_LIBRARY_PATH=... qemu -E LD_LIBRARY_PATH=... /bin/ls * * This funtion returns -errno if an error occured, otherwise 0. */ int ldso_env_passthru(const Tracee *tracee, ArrayOfXPointers *envp, ArrayOfXPointers *argv, const char *define, const char *undefine, size_t offset) { bool has_seen_library_path = false; int status; size_t i; for (i = 0; i < envp->length; i++) { bool is_known = false; char *env; status = read_xpointee_as_string(envp, i, &env); if (status < 0) return status; /* Skip variables that do not start with "LD_". */ if (env == NULL || strncmp(env, "LD_", sizeof("LD_") - 1) != 0) continue; /* When a host program executes a guest program, use * the value of LD_LIBRARY_PATH as it was before being * swapped by the mixed-mode support. */ if ( tracee->host_ldso_paths != NULL && tracee->guest_ldso_paths != NULL && is_env_name(env, "LD_LIBRARY_PATH") && strcmp(env, tracee->host_ldso_paths) == 0) env = (char *) tracee->guest_ldso_paths; #define PASSTHRU(check, name) \ if (is_env_name(env, name)) { \ check |= true; \ /* Errors are not fatal here. */ \ status = resize_array_of_xpointers(argv, offset, 2); \ if (status >= 0) { \ status = write_xpointees(argv, offset, 2, define, env); \ if (status < 0) \ return status; \ } \ write_xpointee(envp, i, ""); \ continue; \ } \ PASSTHRU(has_seen_library_path, "LD_LIBRARY_PATH"); PASSTHRU(is_known, "LD_PRELOAD"); PASSTHRU(is_known, "LD_BIND_NOW"); PASSTHRU(is_known, "LD_TRACE_LOADED_OBJECTS"); PASSTHRU(is_known, "LD_AOUT_LIBRARY_PATH"); PASSTHRU(is_known, "LD_AOUT_PRELOAD"); PASSTHRU(is_known, "LD_AUDIT"); PASSTHRU(is_known, "LD_BIND_NOT"); PASSTHRU(is_known, "LD_DEBUG"); PASSTHRU(is_known, "LD_DEBUG_OUTPUT"); PASSTHRU(is_known, "LD_DYNAMIC_WEAK"); PASSTHRU(is_known, "LD_HWCAP_MASK"); PASSTHRU(is_known, "LD_KEEPDIR"); PASSTHRU(is_known, "LD_NOWARN"); PASSTHRU(is_known, "LD_ORIGIN_PATH"); PASSTHRU(is_known, "LD_POINTER_GUARD"); PASSTHRU(is_known, "LD_PROFILE"); PASSTHRU(is_known, "LD_PROFILE_OUTPUT"); PASSTHRU(is_known, "LD_SHOW_AUXV"); PASSTHRU(is_known, "LD_USE_LOAD_BIAS"); PASSTHRU(is_known, "LD_VERBOSE"); PASSTHRU(is_known, "LD_WARN"); } if (!has_seen_library_path) { /* Errors are not fatal here. */ status = resize_array_of_xpointers(argv, offset, 2); if (status >= 0) { status = write_xpointees(argv, offset, 2, undefine, "LD_LIBRARY_PATH"); if (status < 0) return status; } } return 0; } /** * Add to @host_ldso_paths the list of @paths prefixed with the path * to the host rootfs. */ static int add_host_ldso_paths(char host_ldso_paths[ARG_MAX], const char *paths) { char *cursor1; const char *cursor2; cursor1 = host_ldso_paths + strlen(host_ldso_paths); cursor2 = paths; do { bool is_absolute; size_t length1; size_t length2 = strcspn(cursor2, ":"); is_absolute = (*cursor2 == '/'); length1 = 1 + length2; if (is_absolute) length1 += strlen(HOST_ROOTFS); /* Check there's enough room. */ if (cursor1 + length1 >= host_ldso_paths + ARG_MAX) return -ENOEXEC; if (cursor1 != host_ldso_paths) { strcpy(cursor1, ":"); cursor1++; } /* Since we are executing a host binary under a * QEMUlated environment, we have to access its * library paths through the "host-rootfs" binding. * Technically it means a path like "/lib" is accessed * as "${HOST_ROOTFS}/lib" to avoid conflict with the * guest "/lib". */ if (is_absolute) { strcpy(cursor1, HOST_ROOTFS); cursor1 += strlen(HOST_ROOTFS); } strncpy(cursor1, cursor2, length2); cursor1 += length2; cursor2 += length2 + 1; } while (*(cursor2 - 1) != '\0'); *cursor1 = '\0'; return 0; } struct find_program_header_data { ProgramHeader *program_header; SegmentType type; uint64_t address; }; /** * This function is a program header iterator. It stops the iteration * (by returning 1) once it has found a program header that matches * @data. This function returns -errno if an error occurred, * otherwise 0 or 1. */ static int find_program_header(const ElfHeader *elf_header, const ProgramHeader *program_header, void *data_) { struct find_program_header_data *data = data_; if (PROGRAM_FIELD(*elf_header, *program_header, type) == data->type) { uint64_t start; uint64_t end; memcpy(data->program_header, program_header, sizeof(ProgramHeader)); if (data->address == (uint64_t) -1) return 1; start = PROGRAM_FIELD(*elf_header, *program_header, vaddr); end = start + PROGRAM_FIELD(*elf_header, *program_header, memsz); if (start < end && data->address >= start && data->address <= end) return 1; } return 0; } /** * Add to @xpaths the paths (':'-separated list) from the file * referenced by @fd at the given @offset. This function returns * -errno if an error occured, otherwise 0. */ static int add_xpaths(const Tracee *tracee, int fd, uint64_t offset, char **xpaths) { char *paths = NULL; char *tmp; size_t length; size_t size; int status; status = (int) lseek(fd, offset, SEEK_SET); if (status < 0) return -errno; /* Read the complete list of paths. */ length = 0; paths = NULL; do { size = length + 1024; tmp = talloc_realloc(tracee->ctx, paths, char, size); if (!tmp) return -ENOMEM; paths = tmp; status = read(fd, paths + length, 1024); if (status < 0) return status; length += strnlen(paths + length, 1024); } while (length == size); /* Concatene this list of paths to xpaths. */ if (!*xpaths) { *xpaths = talloc_array(tracee->ctx, char, length + 1); if (!*xpaths) return -ENOMEM; strcpy(*xpaths, paths); } else { length += strlen(*xpaths); length++; /* ":" separator */ tmp = talloc_realloc(tracee->ctx, *xpaths, char, length + 1); if (!tmp) return -ENOMEM; *xpaths = tmp; strcat(*xpaths, ":"); strcat(*xpaths, paths); } /* I don't know if DT_R*PATH entries are unique. In * doubt I support multiple entries. */ return 0; } /** * Put the RPATH and RUNPATH dynamic entries from the file referenced * by @fd -- which has the provided @elf_header -- in @rpaths and * @runpaths respectively. This function returns -errno if an error * occured, otherwise 0. */ static int read_ldso_rpaths(const Tracee* tracee, int fd, const ElfHeader *elf_header, char **rpaths, char **runpaths) { ProgramHeader dynamic_segment; ProgramHeader strtab_segment; struct find_program_header_data data; uint64_t strtab_address = (uint64_t) -1; off_t strtab_offset; int status; size_t i; uint64_t offsetof_dynamic_segment; uint64_t sizeof_dynamic_segment; size_t sizeof_dynamic_entry; data.program_header = &dynamic_segment; data.type = PT_DYNAMIC; data.address = (uint64_t) -1; status = iterate_program_headers(tracee, fd, elf_header, find_program_header, &data); if (status <= 0) return status; offsetof_dynamic_segment = PROGRAM_FIELD(*elf_header, dynamic_segment, offset); sizeof_dynamic_segment = PROGRAM_FIELD(*elf_header, dynamic_segment, filesz); if (IS_CLASS32(*elf_header)) sizeof_dynamic_entry = sizeof(DynamicEntry32); else sizeof_dynamic_entry = sizeof(DynamicEntry64); if (sizeof_dynamic_segment % sizeof_dynamic_entry != 0) return -ENOEXEC; /** * Invoke @embedded_code on each dynamic entry of the given @type. */ #define FOREACH_DYNAMIC_ENTRY(type, embedded_code) \ for (i = 0; i < sizeof_dynamic_segment / sizeof_dynamic_entry; i++) { \ DynamicEntry dynamic_entry; \ uint64_t value; \ \ /* embedded_code may change the file offset. */ \ status = (int) lseek(fd, offsetof_dynamic_segment + i * sizeof_dynamic_entry, \ SEEK_SET); \ if (status < 0) \ return -errno; \ \ status = read(fd, &dynamic_entry, sizeof_dynamic_entry); \ if (status < 0) \ return status; \ \ if (DYNAMIC_FIELD(*elf_header, dynamic_entry, tag) != type) \ continue; \ \ value = DYNAMIC_FIELD(*elf_header, dynamic_entry, val); \ \ embedded_code \ } /* Get the address of the *first* string table. The ELF * specification doesn't mention if it may have several string * table references. */ FOREACH_DYNAMIC_ENTRY(DT_STRTAB, { strtab_address = value; break; }) if (strtab_address == (uint64_t) -1) return 0; data.program_header = &strtab_segment; data.type = PT_LOAD; data.address = strtab_address; /* Search the program header that contains the given string table. */ status = iterate_program_headers(tracee, fd, elf_header, find_program_header, &data); if (status < 0) return status; strtab_offset = PROGRAM_FIELD(*elf_header, strtab_segment, offset) + (strtab_address - PROGRAM_FIELD(*elf_header, strtab_segment, vaddr)); FOREACH_DYNAMIC_ENTRY(DT_RPATH, { if (strtab_offset < 0 || (uint64_t) strtab_offset > UINT64_MAX - value) return -ENOEXEC; status = add_xpaths(tracee, fd, strtab_offset + value, rpaths); if (status < 0) return status; }) FOREACH_DYNAMIC_ENTRY(DT_RUNPATH, { if (strtab_offset < 0 || (uint64_t) strtab_offset > UINT64_MAX - value) return -ENOEXEC; status = add_xpaths(tracee, fd, strtab_offset + value, runpaths); if (status < 0) return status; }) #undef FOREACH_DYNAMIC_ENTRY return 0; } /** * Rebuild the variable LD_LIBRARY_PATH in @envp for the program * @host_path according to its RPATH, RUNPATH, and the initial * LD_LIBRARY_PATH. This function returns -errno if an error occured, * 1 if RPATH/RUNPATH entries were found, 0 otherwise. */ int rebuild_host_ldso_paths(Tracee *tracee, const char host_path[PATH_MAX], ArrayOfXPointers *envp) { static char *initial_ldso_paths = NULL; ElfHeader elf_header; char host_ldso_paths[ARG_MAX] = ""; bool rpath_found = false; char *rpaths = NULL; char *runpaths = NULL; size_t length1; size_t length2; size_t index; int status; int fd; fd = open_elf(host_path, &elf_header); if (fd < 0) return fd; status = read_ldso_rpaths(tracee, fd, &elf_header, &rpaths, &runpaths); close(fd); if (status < 0) return status; /* 1. DT_RPATH */ if (rpaths && !runpaths) { status = add_host_ldso_paths(host_ldso_paths, rpaths); if (status < 0) return 0; /* Not fatal. */ rpath_found = true; } /* 2. LD_LIBRARY_PATH */ if (initial_ldso_paths == NULL) initial_ldso_paths = strdup(getenv("LD_LIBRARY_PATH") ?: "/"); if (initial_ldso_paths != NULL && initial_ldso_paths[0] != '\0') { status = add_host_ldso_paths(host_ldso_paths, initial_ldso_paths); if (status < 0) return 0; /* Not fatal. */ } /* 3. DT_RUNPATH */ if (runpaths) { status = add_host_ldso_paths(host_ldso_paths, runpaths); if (status < 0) return 0; /* Not fatal. */ rpath_found = true; } /* 4. /etc/ld.so.cache NYI. */ /* 5. /lib[32|64], /usr/lib[32|64] + /usr/local/lib[32|64] */ /* 6. /lib, /usr/lib + /usr/local/lib */ if (IS_CLASS32(elf_header)) status = add_host_ldso_paths(host_ldso_paths, #if defined(ARCH_X86) || defined(ARCH_X86_64) "/lib/i386-linux-gnu:/usr/lib/i386-linux-gnu:" #endif "/lib32:/usr/lib32:/usr/local/lib32" ":/lib:/usr/lib:/usr/local/lib"); else status = add_host_ldso_paths(host_ldso_paths, #if defined(ARCH_X86_64) "/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:" #elif defined(ARCH_ARM64) "/lib/aarch64-linux-gnu:/usr/lib/aarch64-linux-gnu:" #endif "/lib64:/usr/lib64:/usr/local/lib64" ":/lib:/usr/lib:/usr/local/lib"); if (status < 0) return 0; /* Not fatal. */ status = find_xpointee(envp, "LD_LIBRARY_PATH"); if (status < 0) return 0; /* Not fatal. */ index = (size_t) status; if (index == envp->length) { /* Allocate a new entry at the end of envp[] when * LD_LIBRARY_PATH was not found. */ index = (envp->length > 0 ? envp->length - 1 : 0); status = resize_array_of_xpointers(envp, index, 1); if (status < 0) return 0; /* Not fatal. */ } else if (tracee->guest_ldso_paths == NULL) { /* Remember guest LD_LIBRARY_PATH in order to restore * it when a host program will execute a guest * program. */ char *env; /* Errors are not fatal here. */ status = read_xpointee_as_string(envp, index, &env); if (status >= 0) tracee->guest_ldso_paths = talloc_strdup(tracee, env); } /* Forge the new LD_LIBRARY_PATH variable from * host_ldso_paths. */ length1 = strlen("LD_LIBRARY_PATH="); length2 = strlen(host_ldso_paths); if (ARG_MAX - length2 - 1 < length1) return 0; /* Not fatal. */ memmove(host_ldso_paths + length1, host_ldso_paths, length2 + 1); memcpy(host_ldso_paths, "LD_LIBRARY_PATH=" , length1); write_xpointee(envp, index, host_ldso_paths); /* The guest LD_LIBRARY_PATH will be restored only if the host * program didn't change it explicitly, so remember its * initial value. */ if (tracee->host_ldso_paths == NULL) tracee->host_ldso_paths = talloc_strdup(tracee, host_ldso_paths); return (int) rpath_found; } proot-5.4.0/src/execve/ldso.h000066400000000000000000000026321442763353300160600ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef LDSO_H #define LDSO_H #include #include #include "execve/aoxp.h" #include "execve/elf.h" extern int ldso_env_passthru(const Tracee *tracee, ArrayOfXPointers *envp, ArrayOfXPointers *argv, const char *define, const char *undefine, size_t offset); extern int rebuild_host_ldso_paths(Tracee *tracee, const char t_program[PATH_MAX], ArrayOfXPointers *envp); extern int compare_xpointee_env(ArrayOfXPointers *envp, size_t index, const char *name); extern bool is_env_name(const char *variable, const char *name); #endif /* LDSO_H */ proot-5.4.0/src/execve/shebang.c000066400000000000000000000172331442763353300165240ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* open(2), */ #include /* open(2), */ #include /* open(2), */ #include /* PATH_MAX, */ #include /* BINPRM_BUF_SIZE, */ #include /* read(2), close(2), */ #include /* -E*, */ #include /* MAXSYMLINKS, */ #include /* bool, */ #include /* assert(3), */ #include "execve/shebang.h" #include "execve/execve.h" #include "execve/aoxp.h" #include "tracee/tracee.h" #include "attribute.h" /** * Extract into @user_path and @argument the shebang from @host_path. * This function returns -errno if an error occured, 1 if a shebang * was found and extracted, otherwise 0. * * Extract from "man 2 execve": * * On Linux, the entire string following the interpreter name is * passed as a *single* argument to the interpreter, and this * string can include white space. */ static int extract_shebang(const Tracee *tracee UNUSED, const char *host_path, char user_path[PATH_MAX], char argument[BINPRM_BUF_SIZE]) { char tmp2[2]; char tmp; size_t current_length; size_t i; int status; int fd; /* Assumption. */ assert(BINPRM_BUF_SIZE < PATH_MAX); argument[0] = '\0'; /* Inspect the executable. */ fd = open(host_path, O_RDONLY); if (fd < 0) return -errno; status = read(fd, tmp2, 2 * sizeof(char)); if (status < 0) { status = -errno; goto end; } if ((size_t) status < 2 * sizeof(char)) { /* EOF */ status = 0; goto end; } /* Check if it really is a script text. */ if (tmp2[0] != '#' || tmp2[1] != '!') { status = 0; goto end; } current_length = 2; user_path[0] = '\0'; /* Skip leading spaces. */ do { status = read(fd, &tmp, sizeof(char)); if (status < 0) { status = -errno; goto end; } if ((size_t) status < sizeof(char)) { /* EOF */ status = -ENOEXEC; goto end; } current_length++; } while ((tmp == ' ' || tmp == '\t') && current_length < BINPRM_BUF_SIZE); /* Slurp the interpreter path until the first space or end-of-line. */ for (i = 0; current_length < BINPRM_BUF_SIZE; current_length++, i++) { switch (tmp) { case ' ': case '\t': /* Remove spaces in between the interpreter * and the hypothetical argument. */ user_path[i] = '\0'; break; case '\n': case '\r': /* There is no argument. */ user_path[i] = '\0'; argument[0] = '\0'; status = 1; goto end; default: /* There is an argument if the previous * character in user_path[] is '\0'. */ if (i > 1 && user_path[i - 1] == '\0') goto argument; else user_path[i] = tmp; break; } status = read(fd, &tmp, sizeof(char)); if (status < 0) { status = -errno; goto end; } if ((size_t) status < sizeof(char)) { /* EOF */ user_path[i] = '\0'; argument[0] = '\0'; status = 1; goto end; } } /* The interpreter path is too long, truncate it. */ user_path[i] = '\0'; argument[0] = '\0'; status = 1; goto end; argument: /* Slurp the argument until the end-of-line. */ for (i = 0; current_length < BINPRM_BUF_SIZE; current_length++, i++) { switch (tmp) { case '\n': case '\r': argument[i] = '\0'; /* Remove trailing spaces. */ for (i--; i > 0 && (argument[i] == ' ' || argument[i] == '\t'); i--) argument[i] = '\0'; status = 1; goto end; default: argument[i] = tmp; break; } status = read(fd, &tmp, sizeof(char)); if (status < 0) { status = -errno; goto end; } if ((size_t) status < sizeof(char)) { /* EOF */ argument[0] = '\0'; status = 1; goto end; } } /* The argument is too long, truncate it. */ argument[i] = '\0'; status = 1; end: close(fd); /* Did an error occur or isn't a script? */ if (status <= 0) return status; return 1; } /** * Expand in argv[] the shebang of @user_path, if any. This function * returns -errno if an error occurred, 1 if a shebang was found and * extracted, otherwise 0. On success, both @host_path and @user_path * point to the program to execute (respectively from host * point-of-view and as-is), and @tracee's argv[] (pointed to by * SYSARG_2) is correctly updated. */ int expand_shebang(Tracee *tracee, char host_path[PATH_MAX], char user_path[PATH_MAX]) { ArrayOfXPointers *argv = NULL; bool has_shebang = false; char argument[BINPRM_BUF_SIZE]; int status; size_t i; /* "The interpreter must be a valid pathname for an executable * which is not itself a script [1]. If the filename * argument of execve() specifies an interpreter script, then * interpreter will be invoked with the following arguments: * * interpreter [optional-arg] filename arg... * * where arg... is the series of words pointed to by the argv * argument of execve()." -- man 2 execve * * [1]: as of this writing (3.10.17) this is true only for the * ELF interpreter; ie. a script can use a script as * interpreter. */ for (i = 0; i < MAXSYMLINKS; i++) { char *old_user_path; /* Translate this path (user -> host), then check it is executable. */ status = translate_and_check_exec(tracee, host_path, user_path); if (status < 0) return status; /* Remember the initial user path. */ old_user_path = talloc_strdup(tracee->ctx, user_path); if (old_user_path == NULL) return -ENOMEM; /* Extract into user_path and argument the shebang from host_path. */ status = extract_shebang(tracee, host_path, user_path, argument); if (status < 0) return status; /* No more shebang. */ if (status == 0) break; has_shebang = true; /* Translate new path (user -> host), then check it is executable. */ status = translate_and_check_exec(tracee, host_path, user_path); if (status < 0) return status; /* Fetch argv[] only on demand. */ if (argv == NULL) { status = fetch_array_of_xpointers(tracee, &argv, SYSARG_2, 0); if (status < 0) return status; } /* Assuming the shebang of "script" is "#!/bin/sh -x", * a call to: * * execve("./script", { "script.sh", NULL }, ...) * * becomes: * * execve("/bin/sh", { "/bin/sh", "-x", "./script", NULL }, ...) * * See commit 8c8fbe85 about "argv->length == 1". */ if (argument[0] != '\0') { status = resize_array_of_xpointers(argv, 0, 2 + (argv->length == 1)); if (status < 0) return status; status = write_xpointees(argv, 0, 3, user_path, argument, old_user_path); if (status < 0) return status; } else { status = resize_array_of_xpointers(argv, 0, 1 + (argv->length == 1)); if (status < 0) return status; status = write_xpointees(argv, 0, 2, user_path, old_user_path); if (status < 0) return status; } } if (i == MAXSYMLINKS) return -ELOOP; /* Push argv[] only on demand. */ if (argv != NULL) { status = push_array_of_xpointers(argv, SYSARG_2); if (status < 0) return status; } return (has_shebang ? 1 : 0); } proot-5.4.0/src/execve/shebang.h000066400000000000000000000021041442763353300165200ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef SHEBANG_H #define SHEBANG_H #include /* PATH_MAX, ARG_MAX, */ #include "tracee/tracee.h" extern int expand_shebang(Tracee *tracee, char host_path[PATH_MAX], char user_path[PATH_MAX]); #endif /* SHEBANG_H */ proot-5.4.0/src/extension/000077500000000000000000000000001442763353300155005ustar00rootroot00000000000000proot-5.4.0/src/extension/care/000077500000000000000000000000001442763353300164125ustar00rootroot00000000000000proot-5.4.0/src/extension/care/archive.c000066400000000000000000000361661442763353300202130ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* open(2), lseek(2), */ #include /* open(2), */ #include /* open(2), */ #include /* read(2), readlink(2), close(2), lseek(2), */ #include /* errno, EACCES, */ #include /* assert(3), */ #include /* PATH_MAX, */ #include /* strlen(3), strcmp(3), */ #include /* bool, true, false, */ #include /* talloc(3), */ #include /* archive_*(3), */ #include /* archive_entry*(3), */ #include "extension/care/archive.h" #include "tracee/tracee.h" #include "cli/note.h" typedef struct { int (*set_format)(struct archive *); int (*add_filter)(struct archive *); int hardlink_resolver_strategy; const char *options; enum { NOT_SPECIAL = 0, SELF_EXTRACTING, RAW } special; } Format; /** * Move *@cursor backward -- within in the given @string -- if it * reads @suffix once moved. */ static bool slurp_suffix(const char *string, const char **cursor, const char *suffix) { size_t length; length = strlen(suffix); if (*cursor - length < string || strncmp(*cursor - length, suffix, length) != 0) return false; *cursor -= length; return true; } /** * Detect the expected format for the given @string. This function * returns -1 if an error occurred, otherwise it returns 0 and updates * the @format structure and @suffix_length with the number of * characters that describes the parsed format. */ static int parse_suffix(const Tracee* tracee, Format *format, const char *string, size_t *suffix_length) { const char *cursor; bool found; bool no_wrapper_found = false; bool no_filter_found = false; bool no_format_found = false; cursor = string + strlen(string); bzero(format, sizeof(Format)); /* parse_special: */ found = slurp_suffix(string, &cursor, "/"); if (found) goto end; found = slurp_suffix(string, &cursor, ".raw"); if (found) { format->special = RAW; goto parse_filter; } found = slurp_suffix(string, &cursor, ".bin"); if (found) { #if defined(CARE_BINARY_IS_PORTABLE) format->special = SELF_EXTRACTING; goto parse_filter; #else note(tracee, ERROR, USER, "This version of CARE was built " "without self-extracting (.bin) support"); return -1; #endif } no_wrapper_found = true; parse_filter: found = slurp_suffix(string, &cursor, ".gz"); if (found) { format->add_filter = archive_write_add_filter_gzip; format->options = "gzip:compression-level=1"; goto parse_format; } found = slurp_suffix(string, &cursor, ".lzo"); if (found) { format->add_filter = archive_write_add_filter_lzop; format->options = "lzop:compression-level=1"; goto parse_format; } found = slurp_suffix(string, &cursor, ".tgz"); if (found) { format->add_filter = archive_write_add_filter_gzip; format->options = "gzip:compression-level=1"; format->set_format = archive_write_set_format_gnutar; format->hardlink_resolver_strategy = ARCHIVE_FORMAT_TAR_GNUTAR; goto sanity_checks; } found = slurp_suffix(string, &cursor, ".tzo"); if (found) { format->add_filter = archive_write_add_filter_lzop; format->options = "lzop:compression-level=1"; format->set_format = archive_write_set_format_gnutar; format->hardlink_resolver_strategy = ARCHIVE_FORMAT_TAR_GNUTAR; goto sanity_checks; } no_filter_found = true; parse_format: found = slurp_suffix(string, &cursor, ".cpio"); if (found) { format->set_format = archive_write_set_format_cpio; format->hardlink_resolver_strategy = ARCHIVE_FORMAT_CPIO_POSIX; goto sanity_checks; } found = slurp_suffix(string, &cursor, ".tar"); if (found) { format->set_format = archive_write_set_format_gnutar; format->hardlink_resolver_strategy = ARCHIVE_FORMAT_TAR_GNUTAR; goto sanity_checks; } no_format_found = true; sanity_checks: if (no_filter_found && no_format_found) { format->add_filter = archive_write_add_filter_lzop; format->options = "lzop:compression-level=1"; format->set_format = archive_write_set_format_gnutar; format->hardlink_resolver_strategy = ARCHIVE_FORMAT_TAR_GNUTAR; if (no_wrapper_found) { #if defined(CARE_BINARY_IS_PORTABLE) format->special = SELF_EXTRACTING; note(tracee, WARNING, USER, "unknown suffix, assuming self-extracting format."); #else format->special = RAW; note(tracee, WARNING, USER, "unknown suffix, assuming raw format."); #endif } no_wrapper_found = false; no_filter_found = false; no_format_found = false; } if (no_format_found) { note(tracee, WARNING, USER, "unknown format, assuming tar format."); format->set_format = archive_write_set_format_gnutar; format->hardlink_resolver_strategy = ARCHIVE_FORMAT_TAR_GNUTAR; no_format_found = false; } end: *suffix_length = strlen(cursor); return 0; } /** * Copy "/proc/self/exe" into @destination. This function returns -1 * if an error occured, otherwise the file descriptor of the * destination. */ static int copy_self_exe(const Tracee *tracee, const char *destination) { int output_fd; int input_fd; int status; input_fd = open("/proc/self/exe", O_RDONLY); if (input_fd < 0) { note(tracee, ERROR, SYSTEM, "can't open '/proc/self/exe'"); return -1; } output_fd = open(destination, O_RDWR|O_CREAT|O_TRUNC, S_IRWXU|S_IRGRP|S_IXGRP); if (output_fd < 0) { note(tracee, ERROR, SYSTEM, "can't open/create '%s'", destination); status = -1; goto end; } while (1) { uint8_t buffer[4 * 1024]; ssize_t size; status = read(input_fd, buffer, sizeof(buffer)); if (status < 0) { note(tracee, ERROR, SYSTEM, "can't read '/proc/self/exe'"); goto end; } if (status == 0) break; size = status; status = write(output_fd, buffer, size); if (status < 0) { note(tracee, ERROR, SYSTEM, "can't write '%s'", destination); goto end; } if (status != size) note(tracee, WARNING, INTERNAL, "wrote %zd bytes instead of %zd", (size_t) status, size); } end: (void) close(input_fd); if (status < 0) { (void) close(output_fd); return -1; } return output_fd; } /** * Create a new archive structure (memory allocation attached to * @context) for the given @output file. This function returns NULL * on error, otherwise the newly allocated archive structure. See * parse_suffix() for the meaning of @suffix_length. */ Archive *new_archive(TALLOC_CTX *context, const Tracee* tracee, const char *output, size_t *suffix_length) { Format format; Archive *archive; int status; assert(output != NULL); status = parse_suffix(tracee, &format, output, suffix_length); if (status < 0) return NULL; archive = talloc_zero(context, Archive); if (archive == NULL) { note(tracee, ERROR, INTERNAL, "can't allocate archive structure"); return NULL; } archive->fd = -1; /* No format was set, content will be copied into a directory * instead of being archived. */ if (format.set_format == NULL) { int flags = ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_XATTR | (geteuid() == 0 ? ARCHIVE_EXTRACT_OWNER : 0); archive->handle = archive_write_disk_new(); if (archive->handle == NULL) { note(tracee, WARNING, INTERNAL, "can't initialize archive structure"); return NULL; } status = archive_write_disk_set_options(archive->handle, flags); if (status == ARCHIVE_WARN) { note(tracee, WARNING, INTERNAL, "set archive options: %s", archive_error_string(archive->handle)); } else if (status != ARCHIVE_OK) { note(tracee, ERROR, INTERNAL, "can't set archive options: %s", archive_error_string(archive->handle)); return NULL; } status = archive_write_disk_set_standard_lookup(archive->handle); if (status == ARCHIVE_WARN) { note(tracee, WARNING, INTERNAL, "set archive lookup: %s", archive_error_string(archive->handle)); } else if (status != ARCHIVE_OK) { note(tracee, ERROR, INTERNAL, "can't set archive lookup: %s", archive_error_string(archive->handle)); return NULL; } archive->hardlink_resolver = archive_entry_linkresolver_new(); if (archive->hardlink_resolver != NULL) archive_entry_linkresolver_set_strategy(archive->hardlink_resolver, ARCHIVE_FORMAT_TAR); return archive; } archive->handle = archive_write_new(); if (archive->handle == NULL) { note(tracee, WARNING, INTERNAL, "can't initialize archive structure"); return NULL; } assert(format.set_format != NULL); status = format.set_format(archive->handle); if (status == ARCHIVE_WARN) { note(tracee, WARNING, INTERNAL, "set archive format: %s", archive_error_string(archive->handle)); } else if (status != ARCHIVE_OK) { note(tracee, ERROR, INTERNAL, "can't set archive format: %s", archive_error_string(archive->handle)); return NULL; } if (format.hardlink_resolver_strategy != 0) { archive->hardlink_resolver = archive_entry_linkresolver_new(); if (archive->hardlink_resolver != NULL) archive_entry_linkresolver_set_strategy(archive->hardlink_resolver, format.hardlink_resolver_strategy); } if (format.add_filter != NULL) { status = format.add_filter(archive->handle); if (status == ARCHIVE_WARN) { note(tracee, WARNING, INTERNAL, "add archive filter: %s", archive_error_string(archive->handle)); } else if (status != ARCHIVE_OK) { note(tracee, ERROR, INTERNAL, "can't add archive filter: %s", archive_error_string(archive->handle)); return NULL; } } if (format.options != NULL) { status = archive_write_set_options(archive->handle, format.options); if (status == ARCHIVE_WARN) { note(tracee, WARNING, INTERNAL, "set archive options: %s", archive_error_string(archive->handle)); } else if (status != ARCHIVE_OK) { note(tracee, ERROR, INTERNAL, "can't set archive options: %s", archive_error_string(archive->handle)); return NULL; } } switch (format.special) { case SELF_EXTRACTING: archive->fd = copy_self_exe(tracee, output); if (archive->fd < 0) return NULL; /* Remember where the CARE binary ends. */ archive->offset = lseek(archive->fd, 0, SEEK_CUR); status = archive_write_open_fd(archive->handle, archive->fd); break; case RAW: archive->fd = open(output, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP); if (archive->fd < 0) { note(tracee, ERROR, SYSTEM, "can't open/create '%s'", output); return NULL; } status = write(archive->fd, "RAW", strlen("RAW")); if (status != strlen("RAW")) { note(tracee, ERROR, SYSTEM, "can't write '%s'", output); (void) close(archive->fd); return NULL; } /* Remember where the "RAW" string ends. */ archive->offset = lseek(archive->fd, 0, SEEK_CUR); status = archive_write_open_fd(archive->handle, archive->fd); break; default: status = archive_write_open_filename(archive->handle, output); break; } if (status == ARCHIVE_WARN) { note(tracee, WARNING, INTERNAL, "open archive '%s': %s", output, archive_error_string(archive->handle)); } else if (status != ARCHIVE_OK) { note(tracee, ERROR, INTERNAL, "can't open archive '%s': %s", output, archive_error_string(archive->handle)); return NULL; } return archive; } /** * Finalize the given @archive. This function returns -1 if an error * occurred, otherwise 0. */ int finalize_archive(Archive *archive) { int status; if (archive == NULL || archive->handle == NULL) return -1; if (archive->hardlink_resolver != NULL) archive_entry_linkresolver_free(archive->hardlink_resolver); status = archive_write_close(archive->handle); if (status != ARCHIVE_OK && status != ARCHIVE_WARN) return -1; status = archive_write_free(archive->handle); if (status != ARCHIVE_OK && status != ARCHIVE_WARN) return -1; return 0; } /** * Put the content of @path into @archive, with the specified @statl * status, at the given @alternate_path (NULL if unchanged). This * function returns -1 if an error occurred, otherwise 0. Note: this * function can be called with @tracee == NULL. */ int archive(const Tracee* tracee, Archive *archive, const char *path, const char *alternate_path, const struct stat *statl) { struct archive_entry *entry = NULL; ssize_t status; mode_t type; size_t size; int fd = -1; if (archive == NULL || archive->handle == NULL) return -1; entry = archive_entry_new(); if (entry == NULL) { note(tracee, WARNING, INTERNAL, "can't create archive entry for '%s': %s", path, archive_error_string(archive->handle)); status = -1; goto end; } archive_entry_set_pathname(entry, alternate_path ?: path); archive_entry_copy_stat(entry, statl); if (archive->hardlink_resolver != NULL) { struct archive_entry *unused; archive_entry_linkify(archive->hardlink_resolver, &entry, &unused); } /* Get status only once hardlinks were resolved. */ size = archive_entry_size(entry); type = archive_entry_filetype(entry); if (type == AE_IFLNK) { char target[PATH_MAX]; status = readlink(path, target, PATH_MAX); if (status >= PATH_MAX) { status = -1; errno = ENAMETOOLONG; } if (status < 0) { note(tracee, WARNING, SYSTEM, "can't readlink '%s'", path); status = -1; goto end; } target[status] = '\0'; /* Must be done before archive_write_header(). */ archive_entry_set_symlink(entry, target); } status = archive_write_header(archive->handle, entry); if (status == ARCHIVE_WARN) { note(tracee, WARNING, INTERNAL, "write header for '%s': %s", path, archive_error_string(archive->handle)); } else if (status != ARCHIVE_OK) { note(tracee, ERROR, INTERNAL, "can't write header for '%s': %s", path, archive_error_string(archive->handle)); status = -1; goto end; } /* No content to archive? */ if (type != AE_IFREG || size == 0) { status = 0; goto end; } fd = open(path, O_RDONLY); if (fd < 0) { if (errno != EACCES) note(tracee, WARNING, SYSTEM, "can't open '%s'", path); status = -1; goto end; } /* Copy the content from the file into the archive. */ do { uint8_t buffer[4096]; status = read(fd, buffer, sizeof(buffer)); if (status < 0) { note(tracee, WARNING, SYSTEM, "can't read '%s'", path); status = -1; goto end; } size = archive_write_data(archive->handle, buffer, status); if ((size_t) status != size) { note(tracee, WARNING, INTERNAL, "can't archive '%s' content: %s", path, archive_error_string(archive->handle)); status = -1; goto end; } } while (status > 0); status = 0; end: if (fd >= 0) (void) close(fd); if (entry != NULL) archive_entry_free(entry); return status; } proot-5.4.0/src/extension/care/archive.h000066400000000000000000000027431442763353300202120ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef ARCHIVE_H #define ARCHIVE_H #include #include #include #include "tracee/tracee.h" typedef struct { struct archive *handle; struct archive_entry_linkresolver *hardlink_resolver; /* Information used to create an self-extracting archive. */ off_t offset; int fd; } Archive; extern Archive *new_archive(TALLOC_CTX *context, const Tracee* tracee, const char *output, size_t *prefix_length); extern int finalize_archive(Archive *archive); extern int archive(const Tracee* tracee, Archive *archive, const char *path, const char *alternate_path, const struct stat *statl); #endif /* ARCHIVE_H */ proot-5.4.0/src/extension/care/care.c000066400000000000000000000360631442763353300175000ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* struct stat, */ #include /* struct stat, */ #include /* lstat(2), */ #include /* PATH_MAX, */ #include /* strlen(3), */ #include /* assert(3), */ #include /* time(2), localtime(3), */ #include /* offsetof(3), */ #include /* talloc*, */ #include /* STAILQ_*, */ #include /* PRI*, */ #include /* AT_*, */ #include "uthash.h" /* ut*, UT*, HASH*, */ #include "extension/care/care.h" #include "extension/care/final.h" #include "extension/care/archive.h" #include "extension/extension.h" #include "tracee/tracee.h" #include "tracee/mem.h" #include "execve/auxv.h" #include "path/canon.h" #include "path/path.h" #include "path/binding.h" #include "cli/note.h" /* Make uthash use talloc. */ #undef uthash_malloc #undef uthash_free #define uthash_malloc(size) talloc_size(care, size) #define uthash_free(pointer, size) TALLOC_FREE(pointer) /* Hash entry. */ typedef struct Entry { UT_hash_handle hh; char *path; } Entry; /** * Add a copy of @value at the end if the given @list. All the newly * talloc'ed elements (duplicated value, item, list head) are attached * to the given @context. This function returns NULL if an error * occurred, otherwise the newly talloc'ed item. */ Item *queue_item(TALLOC_CTX *context, List **list, const char *value) { Item *item; if (*list == NULL) { *list = talloc_zero(context, List); if (*list == NULL) return NULL; STAILQ_INIT(*list); } item = talloc_zero(*list, Item); if (item == NULL) return NULL; item->load = talloc_strdup(item, value); if (item->load == NULL) return NULL; STAILQ_INSERT_TAIL(*list, item, link); return item; } /** * Generate a valid archive @care->output from @care. */ static void generate_output_name(const Tracee *tracee, Care *care) { struct tm *splitted_time; time_t flat_time; flat_time = time(NULL); splitted_time = localtime(&flat_time); if (splitted_time == NULL) { note(tracee, ERROR, INTERNAL, "can't generate a valid output name from the current time, " "please specify an ouput name explicitly"); return; } care->output = talloc_asprintf(care, "care-%02d%02d%02d%02d%02d%02d.%s", splitted_time->tm_year - 100, splitted_time->tm_mon + 1, splitted_time->tm_mday, splitted_time->tm_hour, splitted_time->tm_min, splitted_time->tm_sec, #if defined(CARE_BINARY_IS_PORTABLE) "bin" #else "raw" #endif ); if (care->output == NULL) { note(tracee, ERROR, INTERNAL, "can't generate a valid output name from the current time, " "please specify an ouput name explicitly"); return; } } /** * Genereate @extension->config from @options. This function returns * -1 if an error ocurred, otherwise 0. */ static int generate_care(Extension *extension, const Options *options) { size_t suffix_length; const char *cursor; Tracee *tracee; Item *item2; Item *item; Care *care; tracee = TRACEE(extension); extension->config = talloc_zero(extension, Care); if (extension->config == NULL) return -1; care = extension->config; care->command = options->command; care->ipc_are_volatile = !options->ignore_default_config; if (options->output != NULL) care->output = talloc_strdup(care, options->output); else generate_output_name(tracee, care); if (care->output == NULL) { note(tracee, WARNING, INTERNAL, "can't get output name"); return -1; } care->initial_cwd = talloc_strdup(care, tracee->fs->cwd); if (care->initial_cwd == NULL) { note(tracee, WARNING, INTERNAL, "can't allocate cwd"); return -1; } care->archive = new_archive(care, tracee, care->output, &suffix_length); if (care->archive == NULL) return -1; cursor = strrchr(care->output, '/'); if (cursor == NULL || strlen(cursor) == 1) cursor = care->output; else cursor++; care->prefix = talloc_strndup(care, cursor, strlen(cursor) - suffix_length); if (care->prefix == NULL) { note(tracee, WARNING, INTERNAL, "can't allocate archive prefix"); return -1; } /* Copy & canonicalize volatile paths. */ if (options->volatile_paths != NULL) { char path[PATH_MAX]; int status; STAILQ_FOREACH(item, options->volatile_paths, link) { /* Initial state before canonicalization. */ strcpy(path, "/"); status = canonicalize(tracee, (const char *) item->load, false, path, 0); if (status < 0) continue; /* Sanity check. */ if (strcmp(path, "/") == 0) { const char *string; const char *name; name = talloc_get_name(item); string = name == NULL || name[0] != '$' ? talloc_asprintf(tracee->ctx, "'%s'", (const char *) item->load) : talloc_asprintf(tracee->ctx, "'%s' (%s)", (const char *) item->load, name); note(tracee, WARNING, USER, "path %s was declared volatile but it leads to '/', " "as a consequence it will *not* be considered volatile.", string); continue; } item2 = queue_item(care, &care->volatile_paths, path); if (item2 == NULL) continue; /* Preserve the non expanded form. */ talloc_set_name_const(item2, talloc_get_name(item)); VERBOSE(tracee, 1, "volatile path: %s", (const char *) item2->load); } } /* Copy volatile env. variables. */ if (options->volatile_envars != NULL) { STAILQ_FOREACH(item, options->volatile_envars, link) { item2 = queue_item(care, &care->volatile_envars, item->load); if (item2 == NULL) continue; VERBOSE(tracee, 1, "volatile envar: %s", (const char *) item2->load); } } /* Convert the limit from megabytes to bytes, as expected by * handle_host_path(). */ care->max_size = options->max_size * 1024 * 1024; /* handle_host_path() can now be safely used. */ care->is_ready = true; talloc_set_destructor(care, finalize_care); return 0; } /** * Add @path_ to the list of @care->concealed_accesses. This function * does *not* check for duplicated entries. */ static void register_concealed_access(const Tracee *tracee, Care *care, const char *path_) { char path[PATH_MAX]; size_t length; int status; length = strlen(path_); if (length >= PATH_MAX) return; memcpy(path, path_, length + 1); /* It was a concealed access if, and only if, the path was * part of a asymmetric binding. */ status = substitute_binding(tracee, HOST, path); if (status != 1) return; /* Do not register accesses that would not succeed even if the * path was revealed, i.e. the path does not exist at all. */ status = access(path, F_OK); if (status < 0) return; queue_item(care, &care->concealed_accesses, path); VERBOSE(tracee, 1, "concealed: %s", path); } /** * Archive @path if needed. */ static void handle_host_path(Extension *extension, const char *path) { struct stat statl; bool as_dentries; char *location; Tracee *tracee; Entry *entry; Care *care; int status; care = talloc_get_type_abort(extension->config, Care); tracee = TRACEE(extension); if (!care->is_ready) return; /* Don't archive if the path was already seen before. * This ensures the rootfs is re-created as it was * before any file creation or modification. */ HASH_FIND_STR(care->entries, path, entry); if (entry != NULL) return; switch (get_sysnum(tracee, ORIGINAL)) { case PR_getdents: case PR_getdents64: /* Don't archive if the dentry was already seen * before, it would be useless. */ HASH_FIND_STR(care->dentries, path, entry); if (entry != NULL) return; as_dentries = true; break; default: as_dentries = false; break; } entry = talloc_zero(care, Entry); if (entry == NULL) { note(tracee, WARNING, INTERNAL, "can't allocate entry for '%s'", path); return; } entry->path = talloc_strdup(entry, path); if (entry->path == NULL) { note(tracee, WARNING, INTERNAL, "can't allocate name for '%s'", path); return; } /* Remember this new entry. */ if (as_dentries) HASH_ADD_KEYPTR(hh, care->dentries, entry->path, strlen(entry->path), entry); else HASH_ADD_KEYPTR(hh, care->entries, entry->path, strlen(entry->path), entry); /* Don't use faccessat(2) here since it would require Linux >= * 2.6.16 and Glibc >= 2.4, whereas CARE is supposed to work * on any Linux 2.6 systems. */ status = lstat(path, &statl); if (status < 0) { register_concealed_access(tracee, care, path); return; } /* FIFOs and Unix domain sockets should be volatile. */ if (S_ISFIFO(statl.st_mode) || S_ISSOCK(statl.st_mode)) { if (care->ipc_are_volatile) { Item *item = queue_item(care, &care->volatile_paths, path); if (item != NULL) VERBOSE(tracee, 0, "volatile path: %s", path); else note(tracee, WARNING, USER, "can't declare '%s' (fifo or socket) as volatile", path); return; } else note(tracee, WARNING, USER, "'%1$s' might be explicitely declared volatile (-p %1$s)", path); } /* Don't archive the content of dentries, this save a lot of * space! */ if (as_dentries) statl.st_size = 0; if (care->volatile_paths != NULL) { Item *item; STAILQ_FOREACH(item, care->volatile_paths, link) { switch (compare_paths(item->load, path)) { case PATHS_ARE_EQUAL: /* It's a volatile path, archive it as * empty to preserve its dentry. */ statl.st_size = 0; break; case PATH1_IS_PREFIX: /* Don't archive it's a sub-part of a * volatile path. */ return; default: continue; } break; } } if (care->max_size >= 0 && statl.st_size > care->max_size) { note(tracee, WARNING, USER, "file '%s' is archived with a null size since it is bigger than %" PRIi64 "MB, you can specify an alternate limit with the option -m.", path, care->max_size / 1024 / 1024); statl.st_size = 0; } /* Format the location within the archive. */ location = NULL; assert(path[0] == '/'); location = talloc_asprintf(tracee->ctx, "%s/rootfs%s", care->prefix, path); if (location == NULL) { note(tracee, WARNING, INTERNAL, "can't allocate location for '%s'", path); return; } status = archive(tracee, care->archive, path, location, &statl); if (status == 0) VERBOSE(tracee, 1, "archived: %s", path); } typedef struct { uint32_t d_ino; uint32_t next; uint16_t size; char name[]; } Dirent32; typedef struct { uint64_t d_ino; uint64_t next; uint16_t size; char name[]; } Dirent64; typedef struct { uint64_t inode; int64_t next; uint16_t size; uint8_t type; char name[]; } NewDirent; /** * Archive all the entries returned by getdents syscalls. */ static void handle_getdents(Tracee *tracee, bool is_new_getdents) { char component[PATH_MAX]; char path[PATH_MAX]; uint64_t offset; int status; word_t result; word_t buffer; word_t fd; Dirent32 dirent32; Dirent64 dirent64; NewDirent new_dirent; result = peek_reg(tracee, CURRENT, SYSARG_RESULT); if ((int) result < 0) return; fd = peek_reg(tracee, ORIGINAL, SYSARG_1); buffer = peek_reg(tracee, ORIGINAL, SYSARG_2); offset = 0; while (offset < result) { word_t name_offset; word_t address; size_t size; address = buffer + offset; if (!is_new_getdents) { #if defined(ARCH_X86_64) const bool is_32bit = is_32on64_mode(tracee); #else const bool is_32bit = true; #endif if (is_32bit) { name_offset = offsetof(Dirent32, name); status = read_data(tracee, &dirent32, address, sizeof(dirent32)); size = dirent32.size; } else { name_offset = offsetof(Dirent64, name); status = read_data(tracee, &dirent64, address, sizeof(dirent64)); size = dirent64.size; } } else { name_offset = offsetof(NewDirent, name); status = read_data(tracee, &new_dirent, address, sizeof(new_dirent)); size = new_dirent.size; } if (status < 0) { note(tracee, WARNING, INTERNAL, "can't read dentry"); break; } status = read_string(tracee, component, address + name_offset, PATH_MAX); if (status < 0 || status >= PATH_MAX) { note(tracee, WARNING, INTERNAL, "can't read dentry" ); goto next; } /* Archive through the host_path notification. */ strcpy(path, "/"); translate_path(tracee, path, fd, component, false); next: offset += size; } if (offset != result) note(tracee, WARNING, INTERNAL, "dentry table out of sync."); } /** * Set AT_HWCAP to 0 to ensure no processor specific extensions will * be used, for the sake of reproducibility across different CPUs. * This function assumes the "argv, envp, auxv" stuff is pointed to by * @tracee's stack pointer, as expected right after a successful call * to execve(2). */ static int adjust_elf_auxv(Tracee *tracee) { ElfAuxVector *vectors; ElfAuxVector *vector; word_t vectors_address; vectors_address = get_elf_aux_vectors_address(tracee); if (vectors_address == 0) return 0; vectors = fetch_elf_aux_vectors(tracee, vectors_address); if (vectors == NULL) return 0; for (vector = vectors; vector->type != AT_NULL; vector++) { if (vector->type == AT_HWCAP) vector->value = 0; } push_elf_aux_vectors(tracee, vectors, vectors_address); return 0; } /* List of syscalls handled by this extensions. */ static FilteredSysnum filtered_sysnums[] = { { PR_getdents, FILTER_SYSEXIT }, { PR_getdents64, FILTER_SYSEXIT }, FILTERED_SYSNUM_END, }; /** * Handler for this @extension. It is triggered each time an @event * occurred. See ExtensionEvent for the meaning of @data1 and @data2. */ int care_callback(Extension *extension, ExtensionEvent event, intptr_t data1, intptr_t data2 UNUSED) { Tracee *tracee; switch (event) { case INITIALIZATION: extension->filtered_sysnums = filtered_sysnums; return generate_care(extension, (Options *) data1); case NEW_STATUS: { int status = (int) data1; if (WIFEXITED(status)) { Care *care = talloc_get_type_abort(extension->config, Care); care->last_exit_status = WEXITSTATUS(status); } return 0; } case HOST_PATH: handle_host_path(extension, (const char *) data1); return 0; case SYSCALL_EXIT_START: tracee = TRACEE(extension); switch (get_sysnum(tracee, ORIGINAL)) { case PR_getdents: handle_getdents(tracee, false); break; case PR_getdents64: handle_getdents(tracee, true); break; case PR_execve: { word_t result = peek_reg(tracee, CURRENT, SYSARG_RESULT); /* Note: this can be done only before PRoot pushes the * load script into tracee's stack. */ if ((int) result >= 0) adjust_elf_auxv(tracee); break; } default: break; } return 0; default: return 0; } } proot-5.4.0/src/extension/care/care.h000066400000000000000000000035111442763353300174750ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef CARE_H #define CARE_H #include #include /* STAILQ_*, */ #include "extension/care/archive.h" /* Generic item for a STAILQ list. */ typedef struct item { const void *load; STAILQ_ENTRY(item) link; } Item; typedef STAILQ_HEAD(list, item) List; /* CARE CLI configuration. */ typedef struct { const char *output; char *const *command; List *concealed_paths; List *revealed_paths; List *volatile_paths; List *volatile_envars; bool ignore_default_config; int max_size; } Options; /* CARE internal configuration. */ typedef struct { struct Entry *entries; struct Entry *dentries; char *const *command; List *volatile_paths; List *volatile_envars; List *concealed_accesses; const char *prefix; const char *output; const char *initial_cwd; bool ipc_are_volatile; Archive *archive; int64_t max_size; int last_exit_status; bool is_ready; } Care; extern Item *queue_item(TALLOC_CTX *context, List **list, const char *value); #endif /* CARE_H */ proot-5.4.0/src/extension/care/extract.c000066400000000000000000000236771442763353300202470ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* open(2), fstat(2), lseek(2), */ #include /* open(2), fstat(2), */ #include /* open(2), */ #include /* open(2), */ #include /* *int*_t, *INT*_MAX, */ #include /* fstat(2), read(2), lseek(2), */ #include /* mmap(2), MAP_*, */ #include /* bool, true, false, */ #include /* assert(3), */ #include /* errno(3), */ #include /* strerror(3), */ #include /* PRI*, */ #include /* be64toh(3), */ #include /* archive_*(3), */ #include /* archive_entry*(3), */ #include "extension/care/extract.h" #include "cli/note.h" /** * Extract the given @archive into the current working directory. * This function returns -1 if an error occured, otherwise 0. */ static int extract_archive(struct archive *archive) { struct archive_entry *entry; int result = 0; int status; int flags = ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_XATTR; /* Avoid spurious warnings. One should test for the CAP_CHOWN * capability instead but libarchive only does this test: */ if (geteuid() == 0) flags |= ARCHIVE_EXTRACT_OWNER; while (archive_read_next_header(archive, &entry) == ARCHIVE_OK) { status = archive_read_extract(archive, entry, flags); switch (status) { case ARCHIVE_WARN: note(NULL, WARNING, INTERNAL, "%s: %s", archive_error_string(archive), strerror(archive_errno(archive))); /* FALLTHROUGH */ case ARCHIVE_OK: note(NULL, INFO, USER, "extracted: %s", archive_entry_pathname(entry)); break; default: result = -1; note(NULL, ERROR, INTERNAL, "%s: %s", archive_error_string(archive), strerror(archive_errno(archive))); break; } } return result; } /* Data used by archive_[open/read/close] callbacks. */ typedef struct { uint8_t buffer[4096]; const char *path; size_t size_remaining; int fd; } CallbackData; /** * This callback is invoked by archive_open(). It returns ARCHIVE_OK * if the underlying file or data source is successfully opened. If * the open fails, it calls archive_set_error() to register an error * code and message and returns ARCHIVE_FATAL. * * -- man 3 archive_read_open. */ static int open_callback(struct archive *archive, void *data_) { CallbackData *data = talloc_get_type_abort(data_, CallbackData); AutoExtractInfo info; struct stat statf; off_t offset; int status; /* Note: data->fd will be closed by close_callback(). */ data->fd = open(data->path, O_RDONLY); if (data->fd < 0) { archive_set_error(archive, errno, "can't open archive"); return ARCHIVE_FATAL; } status = fstat(data->fd, &statf); if (status < 0) { archive_set_error(archive, errno, "can't stat archive"); return ARCHIVE_FATAL; } /* Assume it's a regular archive if it physically can't be a * self-extracting one. */ if (statf.st_size < (off_t) sizeof(AutoExtractInfo)) return ARCHIVE_OK; offset = lseek(data->fd, statf.st_size - sizeof(AutoExtractInfo), SEEK_SET); if (offset == (off_t) -1) { archive_set_error(archive, errno, "can't seek in archive"); return ARCHIVE_FATAL; } status = read(data->fd, &info, sizeof(AutoExtractInfo)); if (status < 0) { archive_set_error(archive, errno, "can't read archive"); return ARCHIVE_FATAL; } if ( status == sizeof(AutoExtractInfo) && strcmp(info.signature, AUTOEXTRACT_SIGNATURE) == 0) { /* This is a self-extracting archive, retrieve it's * offset and size. */ data->size_remaining = be64toh(info.size); offset = statf.st_size - data->size_remaining - sizeof(AutoExtractInfo); note(NULL, INFO, USER, "archive found: offset = %" PRIu64 ", size = %" PRIu64 "", (uint64_t) offset, data->size_remaining); } else { /* This is not a self-extracting archive, assume it's * a regular one... */ offset = 0; data->size_remaining = SIZE_MAX; /* ... unless a self-extracting archive really was * expected. */ if (strcmp(data->path, "/proc/self/exe") == 0) return ARCHIVE_FATAL; } offset = lseek(data->fd, offset, SEEK_SET); if (offset == (off_t) -1) { archive_set_error(archive, errno, "can't seek in archive"); return ARCHIVE_FATAL; } return ARCHIVE_OK; } /** * This callback is invoked whenever the library requires raw bytes * from the archive. The read callback reads data into a buffer, set * the @buffer argument to point to the available data, and return a * count of the number of bytes available. The library will invoke * the read callback again only after it has consumed this data. The * library imposes no constraints on the size of the data blocks * returned. On end-of-file, the read callback returns zero. On * error, the read callback should invoke archive_set_error() to * register an error code and message and returns -1. * * -- man 3 archive_read_open. */ static ssize_t read_callback(struct archive *archive, void *data_, const void **buffer) { CallbackData *data = talloc_get_type_abort(data_, CallbackData); ssize_t size = sizeof(data->buffer); if (sizeof(data->buffer) > data->size_remaining) { size = data->size_remaining; if (size == 0) { return 0; } } size = read(data->fd, data->buffer, size); if (size < 0) { archive_set_error(archive, errno, "can't read archive"); return -1; } assert(size <= data->size_remaining); data->size_remaining -= size; *buffer = data->buffer; return size; } /** * This callback is invoked by archive_close() when the archive * processing is complete. The callback returns ARCHIVE_OK on * success. On failure, the callback invokes archive_set_error() to * register an error code and message and returns ARCHIVE_FATAL. * * -- man 3 archive_read_open */ static int close_callback(struct archive *archive, void *data_) { CallbackData *data = talloc_get_type_abort(data_, CallbackData); int status; status = close(data->fd); if (status < 0) { archive_set_error(archive, errno, "can't close archive"); return ARCHIVE_WARN; } return ARCHIVE_OK; } /** * Extract the archive stored at the given @path. This function * returns -1 if an error occurred, otherwise 0. */ int extract_archive_from_file(const char *path) { struct archive *archive = NULL; CallbackData *data = NULL; int status2; int status; archive = archive_read_new(); if (archive == NULL) { note(NULL, ERROR, INTERNAL, "can't initialize archive structure"); status = -1; goto end; } status = archive_read_support_format_cpio(archive); if (status == ARCHIVE_WARN) { note(NULL, WARNING, INTERNAL, "set archive format: %s", archive_error_string(archive)); } else if (status != ARCHIVE_OK) { note(NULL, ERROR, INTERNAL, "can't set archive format: %s", archive_error_string(archive)); status = -1; goto end; } status = archive_read_support_format_gnutar(archive); if (status == ARCHIVE_WARN) { note(NULL, WARNING, INTERNAL, "set archive format: %s", archive_error_string(archive)); } else if (status != ARCHIVE_OK) { note(NULL, ERROR, INTERNAL, "can't set archive format: %s", archive_error_string(archive)); status = -1; goto end; } status = archive_read_support_filter_gzip(archive); if (status == ARCHIVE_WARN) { note(NULL, WARNING, INTERNAL, "add archive filter: %s", archive_error_string(archive)); } else if (status != ARCHIVE_OK) { note(NULL, ERROR, INTERNAL, "can't add archive filter: %s", archive_error_string(archive)); status = -1; goto end; } status = archive_read_support_filter_lzop(archive); if (status == ARCHIVE_WARN) { note(NULL, WARNING, INTERNAL, "add archive filter: %s", archive_error_string(archive)); } else if (status != ARCHIVE_OK) { note(NULL, ERROR, INTERNAL, "can't add archive filter: %s", archive_error_string(archive)); status = -1; goto end; } data = talloc_zero(NULL, CallbackData); if (data == NULL) { note(NULL, ERROR, INTERNAL, "can't allocate callback data"); status = -1; goto end; } data->path = talloc_strdup(data, path); if (data->path == NULL) { note(NULL, ERROR, INTERNAL, "can't allocate callback data path"); status = -1; goto end; } status = archive_read_open(archive, data, open_callback, read_callback, close_callback); if (status == ARCHIVE_WARN) { if (archive_error_string(archive) != NULL) note(NULL, WARNING, INTERNAL, "read archive: %s", archive_error_string(archive)); } else if (status != ARCHIVE_OK) { /* Don't complain if no error message were registered, * ie. when testing for a self-extracting archive. */ if (archive_error_string(archive) != NULL) note(NULL, ERROR, INTERNAL, "can't read archive: %s", archive_error_string(archive)); status = -1; goto end; } status = extract_archive(archive); end: if (archive != NULL) { status2 = archive_read_close(archive); if (status2 != ARCHIVE_OK) { note(NULL, WARNING, INTERNAL, "can't close archive: %s", archive_error_string(archive)); } status2 = archive_read_free(archive); if (status2 != ARCHIVE_OK) { note(NULL, WARNING, INTERNAL, "can't free archive: %s", archive_error_string(archive)); } } TALLOC_FREE(data); return status; } proot-5.4.0/src/extension/care/extract.h000066400000000000000000000022271442763353300202400ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef EXTRACT_H #define EXTRACT_H #include #include "attribute.h" #define AUTOEXTRACT_SIGNATURE "I_LOVE_PIZZA" typedef struct { char signature[sizeof(AUTOEXTRACT_SIGNATURE)]; uint64_t size; } PACKED AutoExtractInfo; extern int WEAK extract_archive_from_file(const char *path); #endif /* EXTRACT_H */ proot-5.4.0/src/extension/care/final.c000066400000000000000000000331211442763353300176470ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* lstat(2), readlink(2), getpid(2), wirte(2), lseek(2), get*id(2), */ #include /* get*id(2), */ #include /* struct stat, fchmod(2), */ #include /* PATH_MAX, */ #include /* uname(2), */ #include /* fprintf(3), fclose(3), */ #include /* errno, ENAMETOOLONG, */ #include /* strcpy(3), */ #include /* htobe64(3), */ #include /* assert(3), */ #include "extension/care/final.h" #include "extension/care/care.h" #include "extension/care/extract.h" #include "execve/ldso.h" #include "path/path.h" #include "path/temp.h" #include "cli/note.h" /** * Find in @care->volatile_envars the given @envar (format * "name=value"). This function returns the name of the variable if * found (format "name"), NULL otherwise. */ static const char *find_volatile_envar(const Care *care, const char *envar) { const Item *volatile_envar; if (care->volatile_envars == NULL) return NULL; STAILQ_FOREACH(volatile_envar, care->volatile_envars, link) { if (is_env_name(envar, volatile_envar->load)) return volatile_envar->load; } return NULL; } extern char **environ; /** * Archive in @care->archive the content of @file with the given * @name, then close it. This function returns < 0 if an error * occured, otherwise 0. Note: this function is called in @care's * destructor. */ static int archive_close_file(const Care *care, FILE *file, const char *name) { char path[PATH_MAX]; struct stat statl; char *location; int status; int fd; /* Ensure everything is written into the file before archiving * it. */ fflush(file); fd = fileno(file); status = fstat(fd, &statl); if (status < 0) { note(NULL, ERROR, SYSTEM, "can't get '%s' status", name); goto end; } location = talloc_asprintf(care, "%s/%s", care->prefix, name); if (location == NULL) { note(NULL, ERROR, INTERNAL, "can't allocate location for '%s'", name); status = -1; goto end; } status = readlink_proc_pid_fd(getpid(), fd, path); if (status < 0) { note(NULL, ERROR, INTERNAL, "can't readlink(/proc/self/fd/%d)", fd); goto end; } status = archive(NULL, care->archive, path, location, &statl); end: (void) fclose(file); return status; } /** * Return a copy -- attached to @context -- of @input with all ' * (single quote) characters escaped. */ static const char *escape_quote(TALLOC_CTX *context, const char *input) { char *output; size_t length; size_t i; output = talloc_strdup(context, ""); if (output == NULL) return NULL; length = strlen(input); for (i = 0; i < length; i++) { char buffer[2] = { input[i], '\0' }; if (buffer[0] == '\'') output = talloc_strdup_append_buffer(output, "'\\''"); else output = talloc_strdup_append_buffer(output, buffer); if (output == NULL) return NULL; } return output; } /* Helpers for archive_* functions. */ #define N(format, ...) \ do { \ if (fprintf(file, format "\n", ##__VA_ARGS__) < 0) { \ note(NULL, ERROR, INTERNAL, "can't write file"); \ (void) fclose(file); \ return -1; \ } \ } while (0) #define C(format, ...) N(format " \\", ##__VA_ARGS__) /** * Archive the "re-execute.sh" file, according to the given @care. * This function returns < 0 if an error occured, 0 otherwise. Note: * this function is called in @care's destructor. */ static int archive_re_execute_sh(Care *care) { struct utsname utsname; const Item *item; FILE *file; int status; int i; file = open_temp_file(NULL, "care"); if (file == NULL) { note(NULL, ERROR, INTERNAL, "can't create temporary file for 're-execute.sh'"); return -1; } status = fchmod(fileno(file), 0755); if (status < 0) note(NULL, WARNING, SYSTEM, "can't make 're-execute.sh' executable"); N("#! /bin/sh"); N(""); N("export XAUTHORITY=\"${XAUTHORITY:-$HOME/.Xauthority}\""); N("export ICEAUTHORITY=\"${ICEAUTHORITY:-$HOME/.ICEauthority}\""); N(""); N("nbargs=$#"); C("[ $nbargs -ne 0 ] || set --"); for (i = 0; care->command != NULL && care->command[i] != NULL; i++) C("'%s'", care->command[i]); N(""); N("PROOT=\"${PROOT-$(dirname $0)/proot}\""); N(""); N("if [ ! -e ${PROOT} ]; then"); N(" PROOT=$(which proot)"); N("fi"); N(""); N("if [ -z ${PROOT} ]; then"); N(" echo '**********************************************************************'"); N(" echo '\"proot\" command not found, please get it from https://proot-me.github.io'"); N(" echo '**********************************************************************'"); N(" exit 1"); N("fi"); N(""); N("if [ x$PROOT_NO_SECCOMP != x ]; then"); N(" PROOT_NO_SECCOMP=\"PROOT_NO_SECCOMP=$PROOT_NO_SECCOMP\""); N("fi"); N(""); C("env --ignore-environment"); C("PROOT_IGNORE_MISSING_BINDINGS=1"); C("$PROOT_NO_SECCOMP"); for (i = 0; environ[i] != NULL; i++) { const char *volatile_envar; volatile_envar = find_volatile_envar(care, environ[i]); if (volatile_envar != NULL) C("'%1$s'=\"$%1$s\" ", volatile_envar); else { const char *string = escape_quote(care, environ[i]); C("'%s' ", string ?: environ[i]); } } C("\"${PROOT-$(dirname $0)/proot}\""); if (care->volatile_paths != NULL) { /* If a volatile path is relative to $HOME, use an * asymmetric binding. For instance: * * -b $HOME/.Xauthority:/home/user/.Xauthority * * where "/home/user" was the $HOME during the * original execution. */ STAILQ_FOREACH(item, care->volatile_paths, link) { const char *name = talloc_get_name(item); if (name[0] == '$') C("-b \"%s:%s\" ", name, (char *) item->load); else C("-b \"%s\" ", (char *) item->load); } } status = uname(&utsname); if (status < 0) { note(NULL, WARNING, SYSTEM, "can't get kernel release"); C("-k 3.17.0"); } else { C("-k '\\%s\\%s\\%s\\%s\\%s\\%s\\0\\' ", utsname.sysname, utsname.nodename, utsname.release, utsname.version, utsname.machine, utsname.domainname); } C("-i %d:%d", getuid(), getgid()); C("-w '%s' ", care->initial_cwd); C("-r \"$(dirname $0)/rootfs\""); /* In case the program retrieves its DSOs from /proc/self/maps * (eg. VLC). */ C("-b \"$(dirname $0)/rootfs\""); N("${1+\"$@\"}"); N(""); N("status=$?"); N("if [ $status -ne %d ] && [ $nbargs -eq 0 ]; then", care->last_exit_status); N("echo \"care: The reproduced execution didn't return the same exit status as the\""); N("echo \"care: original execution. If it is unexpected, please report this bug\""); N("echo \"care: to CARE/PRoot developers:\""); N("echo \"care: * mailing list: reproducible@googlegroups.com; or\""); N("echo \"care: * forum: https://groups.google.com/forum/?fromgroups#!forum/reproducible; or\""); N("echo \"care: * issue tracker: https://github.com/cedric-vincent/PRoot/issues/\""); N("fi"); N(""); N("exit $status"); return archive_close_file(care, file, "re-execute.sh"); } /** * Archive the "concealed-accesses.txt" file in @care->archive, * according to the content of @care->concealed_accesses. This * function returns < 0 if an error occured, 0 otherwise. Note: this * function is called in @care's destructor. */ static int archive_concealed_accesses_txt(const Care *care) { const Item *item; FILE *file; if (care->concealed_accesses == NULL) return 0; file = open_temp_file(NULL, "care"); if (file == NULL) { note(NULL, WARNING, INTERNAL, "can't create temporary file for 'concealed-accesses.txt'"); return -1; } STAILQ_FOREACH(item, care->concealed_accesses, link) N("%s", (char *) item->load); return archive_close_file(care, file, "concealed-accesses.txt"); } /** * Archive the "README.txt" file in @care->archive. This function * returns < 0 if an error occured, 0 otherwise. Note: this function * is called in @care's destructor. */ static int archive_readme_txt(const Care *care) { FILE *file; file = open_temp_file(NULL, "care"); if (file == NULL) { note(NULL, WARNING, INTERNAL, "can't create temporary file for 'README.txt'"); return -1; } N("This archive was created with CARE: https://proot-me.github.io. It contains:"); N(""); N("re-execute.sh"); N(" start the re-execution of the initial command as originally"); N(" specified. It is also possible to specify an alternate command."); N(" For example, assuming gcc was archived, it can be re-invoked"); N(" differently:"); N(""); N(" $ ./re-execute.sh gcc --version"); N(" gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"); N(""); N(" $ echo 'int main(void) { return puts(\"OK\"); }' > rootfs/foo.c"); N(" $ ./re-execute.sh gcc -Wall /foo.c"); N(" $ foo.c: In function \"main\":"); N(" $ foo.c:1:1: warning: implicit declaration of function \"puts\""); N(""); N("rootfs/"); N(" directory where all the files used during the original execution"); N(" were archived, they will be required for the reproduced execution."); N(""); N("proot"); N(" virtualization tool invoked by re-execute.sh to confine the"); N(" reproduced execution into the rootfs. It also emulates the"); N(" missing kernel features if needed."); N(""); N("concealed-accesses.txt"); N(" list of accessed paths that were concealed during the original"); N(" execution. Its main purpose is to know what are the paths that"); N(" should be revealed if the the original execution didn't go as"); N(" expected. It is absolutely useless for the reproduced execution."); N(""); return archive_close_file(care, file, "README.txt"); } #undef N #undef C #if !defined(CARE_BINARY_IS_PORTABLE) static int archive_myself(const Care *care) UNUSED; #endif /** * Archive the content pointed to by "/proc/self/exe" in * "@care->archive:@care->prefix/proot". Note: this function is * called in @care's destructor. */ static int archive_myself(const Care *care) { char path[PATH_MAX]; struct stat statl; char *location; int status; status = readlink("/proc/self/exe", path, PATH_MAX); if (status >= PATH_MAX) { status = -1; errno = ENAMETOOLONG; } if (status < 0) { note(NULL, ERROR, SYSTEM, "can't readlink '/proc/self/exe'"); return status; } path[status] = '\0'; status = lstat(path, &statl); if (status < 0) { note(NULL, ERROR, INTERNAL, "can't lstat '%s'", path); return status; } location = talloc_asprintf(care, "%s/proot", care->prefix); if (location == NULL) { note(NULL, ERROR, INTERNAL, "can't allocate location for 'proot'"); return -1; } return archive(NULL, care->archive, path, location, &statl); } /** * Archive "re-execute.sh" & "proot" from @care. This function * always returns 0. Note: this is a Talloc destructor. */ int finalize_care(Care *care) { char *extractor; int status; /* Generate & archive the "re-execute.sh" script. */ status = archive_re_execute_sh(care); if (status < 0) note(NULL, WARNING, INTERNAL, "can't archive 're-execute.sh'"); /* Generate & archive the "concealed-accesses.txt" file. */ status = archive_concealed_accesses_txt(care); if (status < 0) note(NULL, WARNING, INTERNAL, "can't archive 'concealed-accesses.txt'"); /* Generate & archive the "README.txt" file. */ status = archive_readme_txt(care); if (status < 0) note(NULL, WARNING, INTERNAL, "can't archive 'README.txt'"); #if defined(CARE_BINARY_IS_PORTABLE) /* Archive "care" as "proot", these are the same binary. */ status = archive_myself(care); if (status < 0) note(NULL, WARNING, INTERNAL, "can't archive 'proot'"); #endif finalize_archive(care->archive); /* Append self/raw extracting information if needed. */ if (care->archive->fd >= 0 && care->archive->offset > 0) { AutoExtractInfo info; off_t position; strcpy(info.signature, AUTOEXTRACT_SIGNATURE); /* Compute the size of the archive. */ position = lseek(care->archive->fd, 0, SEEK_CUR); assert(position > care->archive->offset); info.size = htobe64(position - care->archive->offset); status = write(care->archive->fd, &info, sizeof(info)); if (status != sizeof(info)) note(NULL, WARNING, SYSTEM, "can't write extracting information"); (void) close(care->archive->fd); care->archive->fd = -1; if (care->archive->offset == strlen("RAW")) extractor = talloc_asprintf(care, "`care -x %s`", care->output); else extractor = talloc_asprintf(care, "`%2$s%1$s` or `care -x %1$s`", care->output, care->output[0] == '/' ? "" : "./"); } else if (care->output[strlen(care->output) - 1] != '/') extractor = talloc_asprintf(care, "`care -x %s`", care->output); else extractor = NULL; note(NULL, INFO, USER, "----------------------------------------------------------------------"); note(NULL, INFO, USER, "Hints:"); note(NULL, INFO, USER, " - search for \"conceal\" in `care -h` if the execution didn't go as expected."); if (extractor != NULL) note(NULL, INFO, USER, " - run %s to extract the output archive correctly.", extractor); return 0; } proot-5.4.0/src/extension/care/final.h000066400000000000000000000017261442763353300176620ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef CARE_FINAL_H #define CARE_FINAL_H #include "care.h" extern int finalize_care(Care *care); #endif /* CARE_FINAL_H */ proot-5.4.0/src/extension/extension.c000066400000000000000000000110531442763353300176600ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* assert(3), */ #include /* talloc_*, */ #include /* LIST_*, */ #include /* bzero(3), */ #include "extension/extension.h" #include "cli/note.h" #include "build.h" #include "compat.h" #include "extension/portmap/portmap.h" /** * Remove an @extension from its tracee's list, then send it the * "REMOVED" event. * * Note: this is a Talloc destructor. */ static int remove_extension(Extension *extension) { LIST_REMOVE(extension, link); extension->callback(extension, REMOVED, 0, 0); bzero(extension, sizeof(Extension)); return 0; } /** * Allocate a new extension for the given @callback then attach it to * its @tracee. This function returns NULL on error, otherwise the * new extension. */ static Extension *new_extension(Tracee *tracee, extension_callback_t callback) { Extension *extension; /* Lazy allocation of the list head. */ if (tracee->extensions == NULL) { tracee->extensions = talloc_zero(tracee, Extensions); if (tracee->extensions == NULL) return NULL; } /* Allocate a new extension. */ extension = talloc_zero(tracee->extensions, Extension); if (extension == NULL) return NULL; extension->callback = callback; /* Attach it to its tracee. */ LIST_INSERT_HEAD(tracee->extensions, extension, link); talloc_set_destructor(extension, remove_extension); return extension; } /** * Retrieve from @tracee->extensions the extension for the given * @callback. */ Extension *get_extension(Tracee *tracee, extension_callback_t callback) { Extension *extension; if (tracee->extensions == NULL) return NULL; LIST_FOREACH(extension, tracee->extensions, link) { if (extension->callback == callback) return extension; } return NULL; } /** * Initialize a new extension for the given @callback then attach it * to its @tracee. The parameter @cli is its argument that was passed * to the command-line interface. This function return -1 if an error * occurred, otherwise 0. */ int initialize_extension(Tracee *tracee, extension_callback_t callback, const char *cli) { Extension *extension; int status; extension = new_extension(tracee, callback); if (extension == NULL) { note(tracee, WARNING, INTERNAL, "can't create a new extension"); return -1; } /* Remove the new extension if its initialized has failed. */ status = extension->callback(extension, INITIALIZATION, (intptr_t) cli, 0); if (status < 0) { TALLOC_FREE(extension); return status; } return 0; } /** * Rebuild a new list of extensions for this @child from its @parent. * The inheritance model is controlled by the @parent. */ void inherit_extensions(Tracee *child, Tracee *parent, word_t clone_flags) { Extension *parent_extension; Extension *child_extension; int status; if (parent->extensions == NULL) return; /* Sanity check. */ assert(child->extensions == NULL || clone_flags == CLONE_RECONF); LIST_FOREACH(parent_extension, parent->extensions, link) { /* Ask the parent how this extension is * inheritable. */ status = parent_extension->callback(parent_extension, INHERIT_PARENT, (intptr_t)child, clone_flags); /* Not inheritable. */ if (status < 0) continue; /* Inheritable... */ child_extension = new_extension(child, parent_extension->callback); if (child_extension == NULL) { note(parent, WARNING, INTERNAL, "can't create a new extension for pid %d", child->pid); continue; } if (status == 0) { /* ... with a shared config or ... */ child_extension->config = talloc_reference(child_extension, parent_extension->config); } else { /* ... with another inheritance model. */ child_extension->callback(child_extension, INHERIT_CHILD, (intptr_t)parent_extension, clone_flags); } } } proot-5.4.0/src/extension/extension.h000066400000000000000000000167701442763353300177000ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef EXTENSION_H #define EXTENSION_H #include /* LIST_, */ #include /* intptr_t, */ #include "tracee/tracee.h" #include "syscall/seccomp.h" #include "extension/portmap/portmap.h" /* List of possible events. */ typedef enum { /* A guest path passed as an argument of the current syscall * is about to be translated: "(char *) data1" is the base for * "(char *) data2" -- the guest path -- if this latter is * relative. If the extension returns > 0, then PRoot skips * its own handling. If the extension returns < 0, then PRoot * reports this errno as-is. */ GUEST_PATH, /* A canonicalized host path is being accessed during the * translation of a guest path: "(char *) data1" is the * canonicalized host path and "(bool) data2" is true if it is * the last iteration. Note that several host paths are accessed * for a given guest path since PRoot has to walk along all * parent directories and symlinks in order to translate it. * If the extension returns < 0, then PRoot reports this errno * as-is. */ HOST_PATH, /* The tracee enters a syscall, and PRoot hasn't do anything * yet. If the extension returns > 0, then PRoot skips its * own handling. If the extension returns < 0, then PRoot * cancels the syscall and reports this errno to the * tracee. */ SYSCALL_ENTER_START, /* The tracee enters a syscall, and PRoot has already handled * it: "(int) data1" is the current status, it is < 0 when * something went wrong. If the extension returns < 0, then * PRoot cancels the syscall and reports this errno to the * tracee. */ SYSCALL_ENTER_END, /* The tracee exits a syscall, and PRoot hasn't do anything * yet. If the extension returns > 0, then PRoot skips its * own handling. If the extension returns < 0, then PRoot * reports this errno to the tracee. */ SYSCALL_EXIT_START, /* The tracee exits a syscall, and PRoot has already handled * it. If the extension returns < 0, then PRoot reports this * errno to the tracee. */ SYSCALL_EXIT_END, /* The canonicalization succeeds: "(char *) data1" is the * translated path from the host point-of-view. It can be * substituted by the extension. If the extension returns < * 0, then PRoot reports this errno as-is. */ TRANSLATED_PATH, /* The tracee is stopped either because of a syscall or a * signal: "(int) data1" is its new status as reported by * waitpid(2). If the extension returns != 0, then PRoot * skips its own handling. */ NEW_STATUS, /* Ask how this extension is inheritable: "(Tracee *) data1" * is the child tracee and "(bool) data2" is the clone(2) * flags (CLONE_RECONF for sub-reconfiguration). The meaning * of the returned value is: * * < 0 : not inheritable * == 0 : inheritable + shared configuration. * > 0 : inheritable + call INHERIT_CHILD. */ INHERIT_PARENT, /* Control the inheritance: "(Extension *) data1" is the * extension of the parent and "(word_t) data2" is the clone(2) * flags (CLONE_RECONF for sub-reconfiguration). For instance * the extension for the child could use a configuration * different from the parent's configuration. */ INHERIT_CHILD, /* The tracee enters a "chained" syscall, that is, an * unrequested syscall inserted by PRoot after an actual * syscall. If the extension returns < 0, then PRoot cancels * the syscall and reports this errno to the tracee. */ SYSCALL_CHAINED_ENTER, /* The tracee exists a "chained" syscall, that is, an * unrequested syscall inserted by PRoot after an actual * syscall. */ SYSCALL_CHAINED_EXIT, /* Initialize the extension: "(const char *) data1" is its * argument that was passed to the command-line interface. If * the extension returns < 0, then PRoot removed it. */ INITIALIZATION, /* The extension is not attached to its tracee anymore * (destructor). */ REMOVED, /* Print the current configuration of the extension. See * print_config() as an example. */ PRINT_CONFIG, /* Print the usage of the extension: "(bool) data1" is true * for a detailed usage. See print_usage() as an example. */ PRINT_USAGE, /* Called for every already opened file descriptor: * "(const char *)" data1" is the path, "(int) data2" is the file descriptor" */ ALREADY_OPENED_FD, } ExtensionEvent; #define CLONE_RECONF ((word_t) -1) struct extension; typedef int (*extension_callback_t)(struct extension *extension, ExtensionEvent event, intptr_t data1, intptr_t data2); typedef struct extension { /* Function to be called when any event occured. */ extension_callback_t callback; /* A chunk of memory allocated by any talloc functions. * Mainly useful to store a configuration. */ TALLOC_CTX *config; /* List of sysnum handled by this extension. */ const FilteredSysnum *filtered_sysnums; /* Link to the next and previous extensions. Note the order * is *never* garantee. */ LIST_ENTRY(extension) link; } Extension; typedef LIST_HEAD(extensions, extension) Extensions; extern int initialize_extension(Tracee *tracee, extension_callback_t callback, const char *cli); extern void inherit_extensions(Tracee *child, Tracee *parent, word_t clone_flags); extern Extension *get_extension(Tracee *tracee, extension_callback_t callback); /** * Notify all extensions of @tracee that the given @event occured. * See ExtensionEvent for the meaning of @data1 and @data2. */ static inline int notify_extensions(Tracee *tracee, ExtensionEvent event, intptr_t data1, intptr_t data2) { Extension *extension; if (tracee->extensions == NULL) return 0; LIST_FOREACH(extension, tracee->extensions, link) { int status = extension->callback(extension, event, data1, data2); if (status != 0) return status; } return 0; } /* Built-in extensions. */ extern int kompat_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2); extern int fake_id0_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2); extern int care_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2); extern int python_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2); extern int link2symlink_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2); /* Added extensions. */ /** * We use a global variable in order to support multiple port mapping options, * otherwise we would have a different extension instance for each (port_in, port_out) pair, * which would be a waste of memory and performance. * This variable is modified only once, in the INITIALIZATION event. */ extern Extension *global_portmap_extension; extern int portmap_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2); #endif /* EXTENSION_H */ proot-5.4.0/src/extension/fake_id0/000077500000000000000000000000001442763353300171425ustar00rootroot00000000000000proot-5.4.0/src/extension/fake_id0/fake_id0.c000066400000000000000000000567451442763353300207710ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* assert(3), */ #include /* intptr_t, */ #include /* E*, */ #include /* chmod(2), stat(2) */ #include /* uid_t, gid_t, get*id(2), */ #include /* get*id(2), */ #include /* linux.git:c0a3a20b */ #include /* AUDIT_ARCH_*, */ #include /* memcpy(3), */ #include /* strtol(3), */ #include /* AT_, */ #include "extension/extension.h" #include "syscall/syscall.h" #include "syscall/sysnum.h" #include "syscall/seccomp.h" #include "execve/execve.h" #include "tracee/tracee.h" #include "tracee/abi.h" #include "tracee/mem.h" #include "execve/auxv.h" #include "path/binding.h" #include "arch.h" typedef struct { uid_t ruid; uid_t euid; uid_t suid; uid_t fsuid; gid_t rgid; gid_t egid; gid_t sgid; gid_t fsgid; } Config; typedef struct { char *path; mode_t mode; } ModifiedNode; /* List of syscalls handled by this extensions. */ static FilteredSysnum filtered_sysnums[] = { { PR_capset, FILTER_SYSEXIT }, { PR_chmod, FILTER_SYSEXIT }, { PR_chown, FILTER_SYSEXIT }, { PR_chown32, FILTER_SYSEXIT }, { PR_chroot, FILTER_SYSEXIT }, { PR_execve, FILTER_SYSEXIT }, { PR_fchmod, FILTER_SYSEXIT }, { PR_fchmodat, FILTER_SYSEXIT }, { PR_fchown, FILTER_SYSEXIT }, { PR_fchown32, FILTER_SYSEXIT }, { PR_fchownat, FILTER_SYSEXIT }, { PR_fstat, FILTER_SYSEXIT }, { PR_fstat, FILTER_SYSEXIT }, { PR_fstat64, FILTER_SYSEXIT }, { PR_fstatat64, FILTER_SYSEXIT }, { PR_getegid, FILTER_SYSEXIT }, { PR_getegid32, FILTER_SYSEXIT }, { PR_geteuid, FILTER_SYSEXIT }, { PR_geteuid32, FILTER_SYSEXIT }, { PR_getgid, FILTER_SYSEXIT }, { PR_getgid32, FILTER_SYSEXIT }, { PR_getgroups, FILTER_SYSEXIT }, { PR_getgroups32, FILTER_SYSEXIT }, { PR_getresgid, FILTER_SYSEXIT }, { PR_getresgid32, FILTER_SYSEXIT }, { PR_getresuid, FILTER_SYSEXIT }, { PR_getresuid32, FILTER_SYSEXIT }, { PR_getuid, FILTER_SYSEXIT }, { PR_getuid32, FILTER_SYSEXIT }, { PR_lchown, FILTER_SYSEXIT }, { PR_lchown32, FILTER_SYSEXIT }, { PR_lstat, FILTER_SYSEXIT }, { PR_lstat64, FILTER_SYSEXIT }, { PR_mknod, FILTER_SYSEXIT }, { PR_mknodat, FILTER_SYSEXIT }, { PR_newfstatat, FILTER_SYSEXIT }, { PR_oldlstat, FILTER_SYSEXIT }, { PR_oldstat, FILTER_SYSEXIT }, { PR_setfsgid, FILTER_SYSEXIT }, { PR_setfsgid32, FILTER_SYSEXIT }, { PR_setfsuid, FILTER_SYSEXIT }, { PR_setfsuid32, FILTER_SYSEXIT }, { PR_setgid, FILTER_SYSEXIT }, { PR_setgid32, FILTER_SYSEXIT }, { PR_setgroups, FILTER_SYSEXIT }, { PR_setgroups32, FILTER_SYSEXIT }, { PR_setregid, FILTER_SYSEXIT }, { PR_setregid32, FILTER_SYSEXIT }, { PR_setreuid, FILTER_SYSEXIT }, { PR_setreuid32, FILTER_SYSEXIT }, { PR_setresgid, FILTER_SYSEXIT }, { PR_setresgid32, FILTER_SYSEXIT }, { PR_setresuid, FILTER_SYSEXIT }, { PR_setresuid32, FILTER_SYSEXIT }, { PR_setuid, FILTER_SYSEXIT }, { PR_setuid32, FILTER_SYSEXIT }, { PR_setxattr, FILTER_SYSEXIT }, { PR_setdomainname, FILTER_SYSEXIT }, { PR_sethostname, FILTER_SYSEXIT }, { PR_lsetxattr, FILTER_SYSEXIT }, { PR_fsetxattr, FILTER_SYSEXIT }, { PR_stat, FILTER_SYSEXIT }, { PR_statx, FILTER_SYSEXIT }, { PR_stat64, FILTER_SYSEXIT }, { PR_statfs, FILTER_SYSEXIT }, { PR_statfs64, FILTER_SYSEXIT }, FILTERED_SYSNUM_END, }; /** * Restore the @node->mode for the given @node->path. * * Note: this is a Talloc destructor. */ static int restore_mode(ModifiedNode *node) { (void) chmod(node->path, node->mode); return 0; } /** * Force permissions of @path to "rwx" during the path translation of * current @tracee's syscall, in order to simulate CAP_DAC_OVERRIDE. * The original permissions are restored through talloc destructors. * See canonicalize() for the meaning of @is_final. */ static void override_permissions(const Tracee *tracee, const char *path, bool is_final) { ModifiedNode *node; struct stat perms; mode_t new_mode; int status; /* Get the meta-data */ status = stat(path, &perms); if (status < 0) return; /* Copy the current permissions */ new_mode = perms.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); /* Add read and write permissions to everything. */ new_mode |= (S_IRUSR | S_IWUSR); /* Always add 'x' bit to directories */ if (S_ISDIR(perms.st_mode)) new_mode |= S_IXUSR; /* Patch the permissions only if needed. */ if (new_mode == (perms.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) return; node = talloc_zero(tracee->ctx, ModifiedNode); if (node == NULL) return; if (!is_final) { /* Restore the previous mode of any non final components. */ node->mode = perms.st_mode; } else { switch (get_sysnum(tracee, ORIGINAL)) { /* For chmod syscalls: restore the new mode of the final component. */ case PR_chmod: node->mode = peek_reg(tracee, ORIGINAL, SYSARG_2); break; case PR_fchmodat: node->mode = peek_reg(tracee, ORIGINAL, SYSARG_3); break; /* For stat syscalls: don't touch the mode of the final component. */ case PR_fstatat64: case PR_lstat: case PR_lstat64: case PR_newfstatat: case PR_oldlstat: case PR_oldstat: case PR_stat: case PR_statx: case PR_stat64: case PR_statfs: case PR_statfs64: return; /* Otherwise: restore the previous mode of the final component. */ default: node->mode = perms.st_mode; break; } } node->path = talloc_strdup(node, path); if (node->path == NULL) { /* Keep only consistent nodes. */ TALLOC_FREE(node); return; } /* The mode restoration works because Talloc destructors are * called in reverse order. */ talloc_set_destructor(node, restore_mode); (void) chmod(path, new_mode); return; } /** * Adjust current @tracee's syscall parameters according to @config. * This function always returns 0. */ static int handle_sysenter_end(Tracee *tracee, const Config *config) { word_t sysnum; sysnum = get_sysnum(tracee, ORIGINAL); switch (sysnum) { case PR_setuid: case PR_setuid32: case PR_setgid: case PR_setgid32: case PR_setreuid: case PR_setreuid32: case PR_setregid: case PR_setregid32: case PR_setresuid: case PR_setresuid32: case PR_setresgid: case PR_setresgid32: case PR_setfsuid: case PR_setfsuid32: case PR_setfsgid: case PR_setfsgid32: /* These syscalls are fully emulated. */ set_sysnum(tracee, PR_void); return 0; case PR_chown: case PR_chown32: case PR_lchown: case PR_lchown32: case PR_fchown: case PR_fchown32: case PR_fchownat: { Reg uid_sysarg; Reg gid_sysarg; uid_t uid; gid_t gid; if (sysnum == PR_fchownat) { uid_sysarg = SYSARG_3; gid_sysarg = SYSARG_4; } else { uid_sysarg = SYSARG_2; gid_sysarg = SYSARG_3; } uid = peek_reg(tracee, ORIGINAL, uid_sysarg); gid = peek_reg(tracee, ORIGINAL, gid_sysarg); /* Swap actual and emulated ids to get a chance of * success. */ if (uid == config->ruid) poke_reg(tracee, uid_sysarg, getuid()); if (gid == config->rgid) poke_reg(tracee, gid_sysarg, getgid()); return 0; } case PR_setgroups: case PR_setgroups32: case PR_getgroups: case PR_getgroups32: /* TODO */ default: return 0; } /* Never reached */ assert(0); return 0; } /** * Copy config->@field to the tracee's memory location pointed to by @sysarg. */ #define POKE_MEM_ID(sysarg, field) do { \ poke_uint32(tracee, peek_reg(tracee, ORIGINAL, sysarg), config->field); \ if (errno != 0) \ return -errno; \ } while (0) /** * Emulate setuid(2) and setgid(2). */ #define SETXID(id) do { \ id ## _t id = peek_reg(tracee, ORIGINAL, SYSARG_1); \ bool allowed; \ \ /* "EPERM: The user is not privileged (does not have the \ * CAP_SETUID capability) and uid does not match the real UID \ * or saved set-user-ID of the calling process." -- man \ * setuid */ \ allowed = (config->euid == 0 /* TODO: || HAS_CAP(SETUID) */ \ || id == config->r ## id \ || id == config->e ## id \ || id == config->s ## id); \ if (!allowed) \ return -EPERM; \ \ /* "If the effective UID of the caller is root, the real UID \ * and saved set-user-ID are also set." -- man setuid */ \ if (config->e ## id == 0) { \ config->r ## id = id; \ config->s ## id = id; \ } \ \ /* "whenever the effective user ID is changed, fsuid will also \ * be changed to the new value of the effective user ID." -- \ * man setfsuid */ \ config->e ## id = id; \ config->fs ## id = id; \ \ poke_reg(tracee, SYSARG_RESULT, 0); \ return 0; \ } while (0) /** * Check whether @id is set or not. */ #define UNSET_ID(id) (id == (uid_t) -1) /** * Check whether @id is change or not. */ #define UNCHANGED_ID(id) (UNSET_ID(id) || id == config->id) /** * Emulate setreuid(2) and setregid(2). */ #define SETREXID(id) do { \ id ## _t r ## id = peek_reg(tracee, ORIGINAL, SYSARG_1); \ id ## _t e ## id = peek_reg(tracee, ORIGINAL, SYSARG_2); \ bool allowed; \ \ /* "Unprivileged processes may only set the effective user ID \ * to the real user ID, the effective user ID, or the saved \ * set-user-ID. \ * \ * Unprivileged users may only set the real user ID to the \ * real user ID or the effective user ID." \ * * "EPERM: The calling process is not privileged (does not \ * have the CAP_SETUID) and a change other than: \ * 1. swapping the effective user ID with the real user ID, \ * or; \ * 2. setting one to the value of the other, or ; \ * 3. setting the effective user ID to the value of the saved \ * set-user-ID \ * was specified." -- man setreuid \ * \ * Is it possible to "ruid <- euid" and "euid <- suid" at the \ * same time? */ \ allowed = (config->euid == 0 /* TODO: || HAS_CAP(SETUID) */ \ || (UNCHANGED_ID(e ## id) && UNCHANGED_ID(r ## id)) \ || (r ## id == config->e ## id && (e ## id == config->r ## id || UNCHANGED_ID(e ## id))) \ || (e ## id == config->r ## id && (r ## id == config->e ## id || UNCHANGED_ID(r ## id))) \ || (e ## id == config->s ## id && UNCHANGED_ID(r ## id))); \ if (!allowed) \ return -EPERM; \ \ /* "Supplying a value of -1 for either the real or effective \ * user ID forces the system to leave that ID unchanged. \ * [...] If the real user ID is set or the effective user ID \ * is set to a value not equal to the previous real user ID, \ * the saved set-user-ID will be set to the new effective user \ * ID." -- man setreuid */ \ if (!UNSET_ID(e ## id)) { \ if (e ## id != config->r ## id) \ config->s ## id = e ## id; \ \ config->e ## id = e ## id; \ config->fs ## id = e ## id; \ } \ \ /* Since it changes the current ruid value, this has to be \ * done after euid handling. */ \ if (!UNSET_ID(r ## id)) { \ if (!UNSET_ID(e ## id)) \ config->s ## id = e ## id; \ config->r ## id = r ## id; \ } \ \ poke_reg(tracee, SYSARG_RESULT, 0); \ return 0; \ } while (0) /** * Check if @var is equal to any config->r{@type}id's. */ #define EQUALS_ANY_ID(var, type) (var == config->r ## type ## id \ || var == config->e ## type ## id \ || var == config->s ## type ## id) /** * Emulate setresuid(2) and setresgid(2). */ #define SETRESXID(type) do { \ type ## id_t r ## type ## id = peek_reg(tracee, ORIGINAL, SYSARG_1); \ type ## id_t e ## type ## id = peek_reg(tracee, ORIGINAL, SYSARG_2); \ type ## id_t s ## type ## id = peek_reg(tracee, ORIGINAL, SYSARG_3); \ bool allowed; \ \ /* "Unprivileged user processes may change the real UID, \ * effective UID, and saved set-user-ID, each to one of: the \ * current real UID, the current effective UID or the current \ * saved set-user-ID. \ * \ * Privileged processes (on Linux, those having the CAP_SETUID \ * capability) may set the real UID, effective UID, and saved \ * set-user-ID to arbitrary values." -- man setresuid */ \ allowed = (config->euid == 0 /* || HAS_CAP(SETUID) */ \ || ((UNSET_ID(r ## type ## id) || EQUALS_ANY_ID(r ## type ## id, type)) \ && (UNSET_ID(e ## type ## id) || EQUALS_ANY_ID(e ## type ## id, type)) \ && (UNSET_ID(s ## type ## id) || EQUALS_ANY_ID(s ## type ## id, type)))); \ if (!allowed) \ return -EPERM; \ \ /* "If one of the arguments equals -1, the corresponding value \ * is not changed." -- man setresuid */ \ if (!UNSET_ID(r ## type ## id)) \ config->r ## type ## id = r ## type ## id; \ \ if (!UNSET_ID(e ## type ## id)) { \ /* "the file system UID is always set to the same \ * value as the (possibly new) effective UID." -- man \ * setresuid */ \ config->e ## type ## id = e ## type ## id; \ config->fs ## type ## id = e ## type ## id; \ } \ \ if (!UNSET_ID(s ## type ## id)) \ config->s ## type ## id = s ## type ## id; \ \ poke_reg(tracee, SYSARG_RESULT, 0); \ return 0; \ } while (0) /** * Emulate setfsuid(2) and setfsgid(2). */ #define SETFSXID(type) do { \ uid_t fs ## type ## id = peek_reg(tracee, ORIGINAL, SYSARG_1); \ uid_t old_fs ## type ## id = config->fs ## type ## id; \ bool allowed; \ \ /* "setfsuid() will succeed only if the caller is the \ * superuser or if fsuid matches either the real user ID, \ * effective user ID, saved set-user-ID, or the current value \ * of fsuid." -- man setfsuid */ \ allowed = (config->euid == 0 /* TODO: || HAS_CAP(SETUID) */ \ || fs ## type ## id == config->fs ## type ## id \ || EQUALS_ANY_ID(fs ## type ## id, type)); \ if (allowed) \ config->fs ## type ## id = fs ## type ## id; \ \ /* "On success, the previous value of fsuid is returned. On \ * error, the current value of fsuid is returned." -- man \ * setfsuid */ \ poke_reg(tracee, SYSARG_RESULT, old_fs ## type ## id); \ return 0; \ } while (0) /** * Adjust current @tracee's syscall result according to @config. This * function returns -errno if an error occured, otherwise 0. */ static int handle_sysexit_end(Tracee *tracee, Config *config) { word_t sysnum; word_t result; sysnum = get_sysnum(tracee, ORIGINAL); switch (sysnum) { case PR_setuid: case PR_setuid32: SETXID(uid); case PR_setgid: case PR_setgid32: SETXID(gid); case PR_setreuid: case PR_setreuid32: SETREXID(uid); case PR_setregid: case PR_setregid32: SETREXID(gid); case PR_setresuid: case PR_setresuid32: SETRESXID(u); case PR_setresgid: case PR_setresgid32: SETRESXID(g); case PR_setfsuid: case PR_setfsuid32: SETFSXID(u); case PR_setfsgid: case PR_setfsgid32: SETFSXID(g); case PR_getuid: case PR_getuid32: poke_reg(tracee, SYSARG_RESULT, config->ruid); return 0; case PR_getgid: case PR_getgid32: poke_reg(tracee, SYSARG_RESULT, config->rgid); return 0; case PR_geteuid: case PR_geteuid32: poke_reg(tracee, SYSARG_RESULT, config->euid); return 0; case PR_getegid: case PR_getegid32: poke_reg(tracee, SYSARG_RESULT, config->egid); return 0; case PR_getresuid: case PR_getresuid32: POKE_MEM_ID(SYSARG_1, ruid); POKE_MEM_ID(SYSARG_2, euid); POKE_MEM_ID(SYSARG_3, suid); return 0; case PR_getresgid: case PR_getresgid32: POKE_MEM_ID(SYSARG_1, rgid); POKE_MEM_ID(SYSARG_2, egid); POKE_MEM_ID(SYSARG_3, sgid); return 0; case PR_setdomainname: case PR_sethostname: case PR_setgroups: case PR_setgroups32: case PR_mknod: case PR_mknodat: case PR_capset: case PR_setxattr: case PR_lsetxattr: case PR_fsetxattr: case PR_chmod: case PR_chown: case PR_fchmod: case PR_fchown: case PR_lchown: case PR_chown32: case PR_fchown32: case PR_lchown32: case PR_fchmodat: case PR_fchownat: { word_t result; /* Override only permission errors. */ result = peek_reg(tracee, CURRENT, SYSARG_RESULT); if ((int) result != -EPERM) return 0; /* Force success if the tracee was supposed to have * the capability. */ if (config->euid == 0) /* TODO: || HAS_CAP(...) */ poke_reg(tracee, SYSARG_RESULT, 0); return 0; } case PR_fstatat64: case PR_newfstatat: case PR_stat64: case PR_lstat64: case PR_fstat64: case PR_stat: case PR_statx: case PR_lstat: case PR_fstat: { word_t address; Reg sysarg; uid_t uid; gid_t gid; off_t uid_offset; off_t gid_offset; /* Override only if it succeed. */ result = peek_reg(tracee, CURRENT, SYSARG_RESULT); if (result != 0) return 0; /* Get the address of the 'stat' structure. */ if (sysnum == PR_statx) { sysarg = SYSARG_5; uid_offset = OFFSETOF_STATX_UID; gid_offset = OFFSETOF_STATX_GID; } else { if (sysnum == PR_fstatat64 || sysnum == PR_newfstatat) sysarg = SYSARG_3; else sysarg = SYSARG_2; uid_offset = offsetof_stat_uid(tracee); gid_offset = offsetof_stat_gid(tracee); } address = peek_reg(tracee, ORIGINAL, sysarg); /* Sanity checks. */ assert(__builtin_types_compatible_p(uid_t, uint32_t)); assert(__builtin_types_compatible_p(gid_t, uint32_t)); /* Get the uid & gid values from the 'stat' structure. */ uid = peek_uint32(tracee, address + uid_offset); if (errno != 0) uid = 0; /* Not fatal. */ gid = peek_uint32(tracee, address + gid_offset); if (errno != 0) gid = 0; /* Not fatal. */ /* Override only if the file is owned by the current user. * Errors are not fatal here. */ if (uid == getuid()) poke_uint32(tracee, address + uid_offset, config->suid); if (gid == getgid()) poke_uint32(tracee, address + gid_offset, config->sgid); return 0; } case PR_chroot: { char path[PATH_MAX]; char abspath[PATH_MAX]; word_t input; int status; if (config->euid != 0) /* TODO: && !HAS_CAP(SYS_CHROOT) */ return 0; /* Override only permission errors. */ result = peek_reg(tracee, CURRENT, SYSARG_RESULT); if ((int) result != -EPERM) return 0; input = peek_reg(tracee, MODIFIED, SYSARG_1); status = read_path(tracee, path, input); if (status < 0) return status; /* Resolve relative path segments. */ if (!realpath(path, abspath)) return 0; /* Only "new rootfs == current rootfs" is supported yet. */ status = compare_paths(get_root(tracee), abspath); if (status != PATHS_ARE_EQUAL) return 0; /* Force success. */ poke_reg(tracee, SYSARG_RESULT, 0); return 0; } default: return 0; } } #undef POKE_MEM_ID #undef SETXID #undef UNSET_ID #undef UNCHANGED_ID #undef SETREXID #undef EQUALS_ANY_ID #undef SETRESXID #undef SETFSXID /** * Adjust some ELF auxiliary vectors. This function assumes the * "argv, envp, auxv" stuff is pointed to by @tracee's stack pointer, * as expected right after a successful call to execve(2). */ static int adjust_elf_auxv(Tracee *tracee, Config *config) { ElfAuxVector *vectors; ElfAuxVector *vector; word_t vectors_address; vectors_address = get_elf_aux_vectors_address(tracee); if (vectors_address == 0) return 0; vectors = fetch_elf_aux_vectors(tracee, vectors_address); if (vectors == NULL) return 0; for (vector = vectors; vector->type != AT_NULL; vector++) { switch (vector->type) { case AT_UID: vector->value = config->ruid; break; case AT_EUID: vector->value = config->euid; break; case AT_GID: vector->value = config->rgid; break; case AT_EGID: vector->value = config->egid; break; default: break; } } push_elf_aux_vectors(tracee, vectors, vectors_address); return 0; } /** * Handler for this @extension. It is triggered each time an @event * occurred. See ExtensionEvent for the meaning of @data1 and @data2. */ int fake_id0_callback(Extension *extension, ExtensionEvent event, intptr_t data1, intptr_t data2) { switch (event) { case INITIALIZATION: { const char *uid_string = (const char *) data1; const char *gid_string; Config *config; int uid, gid; errno = 0; uid = strtol(uid_string, NULL, 10); if (errno != 0) uid = getuid(); gid_string = strchr(uid_string, ':'); if (gid_string == NULL) { errno = EINVAL; } else { errno = 0; gid = strtol(gid_string + 1, NULL, 10); } /* Fallback to the current gid if an error occured. */ if (errno != 0) gid = getgid(); extension->config = talloc(extension, Config); if (extension->config == NULL) return -1; config = talloc_get_type_abort(extension->config, Config); config->ruid = uid; config->euid = uid; config->suid = uid; config->fsuid = uid; config->rgid = gid; config->egid = gid; config->sgid = gid; config->fsgid = gid; extension->filtered_sysnums = filtered_sysnums; return 0; } case INHERIT_PARENT: /* Inheritable for sub reconfiguration ... */ return 1; case INHERIT_CHILD: { /* Copy the parent configuration to the child. The * structure should not be shared as uid/gid changes * in one process should not affect other processes. * This assertion is not true for POSIX threads * sharing the same group, however Linux threads never * share uid/gid information. As a consequence, the * GlibC emulates the POSIX behavior on Linux by * sending a signal to all group threads to cause them * to invoke the system call too. Finally, PRoot * doesn't have to worry about clone flags. */ Extension *parent = (Extension *) data1; extension->config = talloc_zero(extension, Config); if (extension->config == NULL) return -1; memcpy(extension->config, parent->config, sizeof(Config)); return 0; } case HOST_PATH: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); /* Force permissions if the tracee was supposed to * have the capability. */ if (config->euid == 0) /* TODO: || HAS_CAP(DAC_OVERRIDE) */ override_permissions(tracee, (char*) data1, (bool) data2); return 0; } case SYSCALL_ENTER_END: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); return handle_sysenter_end(tracee, config); } case SYSCALL_EXIT_END: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); return handle_sysexit_end(tracee, config); } case SYSCALL_EXIT_START: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); word_t result = peek_reg(tracee, CURRENT, SYSARG_RESULT); word_t sysnum = get_sysnum(tracee, ORIGINAL); struct stat mode; int status; if ((int) result < 0 || sysnum != PR_execve) return 0; /* This has to be done before PRoot pushes the load * script into tracee's stack. */ adjust_elf_auxv(tracee, config); status = stat(tracee->load_info->host_path, &mode); if (status < 0) return 0; /* Not fatal. */ if ((mode.st_mode & S_ISUID) != 0) { config->euid = 0; config->suid = 0; } if ((mode.st_mode & S_ISGID) != 0) { config->egid = 0; config->sgid = 0; } return 0; } default: return 0; } } proot-5.4.0/src/extension/kompat/000077500000000000000000000000001442763353300167735ustar00rootroot00000000000000proot-5.4.0/src/extension/kompat/kompat.c000066400000000000000000000643041442763353300204410ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* intptr_t, */ #include /* strtoul(3), */ #include /* KERNEL_VERSION, */ #include /* assert(3), */ #include /* uname(2), utsname, */ #include /* str*(3), memcpy(3), */ #include /* talloc_*, */ #include /* AT_*, */ #include /* linux.git:c0a3a20b */ #include /* errno, */ #include /* AT_, */ #include /* FUTEX_PRIVATE_FLAG */ #include /* MIN, */ #include "extension/extension.h" #include "syscall/seccomp.h" #include "syscall/sysnum.h" #include "syscall/chain.h" #include "tracee/tracee.h" #include "tracee/reg.h" #include "tracee/abi.h" #include "tracee/mem.h" #include "execve/auxv.h" #include "cli/note.h" #include "arch.h" #include "attribute.h" #include "compat.h" #define MAX_ARG_SHIFT 2 typedef struct { int expected_release; word_t new_sysarg_num; struct { Reg sysarg; /* first argument to be moved. */ size_t nb_args; /* number of arguments to be moved. */ int offset; /* offset to be applied. */ } shifts[MAX_ARG_SHIFT]; } Modif; #define NONE {{0, 0, 0}} typedef struct { int actual_release; int virtual_release; struct utsname utsname; word_t hwcap; } Config; /** * Return whether the @expected_release is newer than * @config->actual_release and older than @config->virtual_release. */ static bool needs_kompat(const Config *config, int expected_release) { return (expected_release > config->actual_release && expected_release <= config->virtual_release); } /** * Modify the current syscall of @tracee as described by @modif * regarding the given @config. This function returns whether the * syscall was modified or not. */ static bool modify_syscall(Tracee *tracee, const Config *config, const Modif *modif) { size_t i, j; word_t syscall; assert(config != NULL); if (!needs_kompat(config, modif->expected_release)) return false; /* Check if this syscall is supported on this architecture. */ syscall = detranslate_sysnum(get_abi(tracee), modif->new_sysarg_num); if (syscall == SYSCALL_AVOIDER) return false; set_sysnum(tracee, modif->new_sysarg_num); /* Shift syscall arguments. */ for (i = 0; i < MAX_ARG_SHIFT; i++) { Reg sysarg = modif->shifts[i].sysarg; size_t nb_args = modif->shifts[i].nb_args; int offset = modif->shifts[i].offset; for (j = 0; j < nb_args; j++) { word_t arg = peek_reg(tracee, CURRENT, sysarg + j); poke_reg(tracee, sysarg + j + offset, arg); } } return true; } /** * Return the numeric value for the given kernel @release. */ static int parse_kernel_release(const char *release) { unsigned long major = 0; unsigned long minor = 0; unsigned long revision = 0; char *cursor = (char *)release; major = strtoul(cursor, &cursor, 10); if (*cursor == '.') { cursor++; minor = strtoul(cursor, &cursor, 10); } if (*cursor == '.') { cursor++; revision = strtoul(cursor, &cursor, 10); } return KERNEL_VERSION(major, minor, revision); } /** * Remove @discarded_flags from the given @tracee's @sysarg register * if the actual kernel release is not compatible with the * @expected_release. */ static void discard_fd_flags(Tracee *tracee, const Config *config, int discarded_flags, int expected_release, Reg sysarg) { word_t flags; if (!needs_kompat(config, expected_release)) return; flags = peek_reg(tracee, CURRENT, sysarg); poke_reg(tracee, sysarg, flags & ~discarded_flags); } /** * Replace current @tracee's syscall with an older and compatible one * whenever it's required, i.e. when the syscall is supported by the * kernel as specified by @config->virtual_release but it isn't * supported by the actual kernel. */ static int handle_sysenter_end(Tracee *tracee, Config *config) { /* Note: syscalls like "openat" can be replaced by "open" since PRoot * has canonicalized "fd + path" into "path". */ switch (get_sysnum(tracee, ORIGINAL)) { case PR_accept4: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,28), .new_sysarg_num = PR_accept, .shifts = NONE }; modify_syscall(tracee, config, &modif); return 0; } case PR_dup3: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,27), .new_sysarg_num = PR_dup2, .shifts = NONE }; /* "If oldfd equals newfd, then dup3() fails with the * error EINVAL" -- man dup3 */ if (peek_reg(tracee, CURRENT, SYSARG_1) == peek_reg(tracee, CURRENT, SYSARG_2)) return -EINVAL; modify_syscall(tracee, config, &modif); return 0; } case PR_epoll_create1: { bool modified; Modif modif = { .expected_release = KERNEL_VERSION(2,6,27), .new_sysarg_num = PR_epoll_create, .shifts = NONE }; /* "the size argument is ignored, but must be greater * than zero" -- man epoll_create */ modified = modify_syscall(tracee, config, &modif); if (modified) poke_reg(tracee, SYSARG_1, 1); return 0; } case PR_epoll_pwait: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,19), .new_sysarg_num = PR_epoll_wait, .shifts = NONE }; modify_syscall(tracee, config, &modif); return 0; } case PR_eventfd2: { bool modified; word_t flags; Modif modif = { .expected_release = KERNEL_VERSION(2,6,27), .new_sysarg_num = PR_eventfd, .shifts = NONE }; modified = modify_syscall(tracee, config, &modif); if (modified) { /* EFD_SEMAPHORE can't be emulated with eventfd. */ flags = peek_reg(tracee, CURRENT, SYSARG_2); if ((flags & EFD_SEMAPHORE) != 0) return -EINVAL; } return 0; } case PR_faccessat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_access, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 2, .offset = -1 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_fchmodat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_chmod, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 2, .offset = -1 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_fchownat: { word_t flags; Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 3, .offset = -1 } } }; flags = peek_reg(tracee, CURRENT, SYSARG_5); modif.new_sysarg_num = ((flags & AT_SYMLINK_NOFOLLOW) != 0 ? PR_lchown : PR_chown); modify_syscall(tracee, config, &modif); return 0; } case PR_fcntl: { word_t command; if (!needs_kompat(config, KERNEL_VERSION(2,6,24))) return 0; command = peek_reg(tracee, ORIGINAL, SYSARG_2); if (command == F_DUPFD_CLOEXEC) poke_reg(tracee, SYSARG_2, F_DUPFD); return 0; } case PR_newfstatat: case PR_fstatat64: { word_t flags; Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 2, .offset = -1 } } }; if (config->actual_release == 0) return 0; flags = peek_reg(tracee, CURRENT, SYSARG_4); #if defined(ARCH_X86_64) if ((flags & AT_SYMLINK_NOFOLLOW) != 0) modif.new_sysarg_num = (get_abi(tracee) != ABI_2 ? PR_lstat : PR_lstat64); else modif.new_sysarg_num = (get_abi(tracee) != ABI_2 ? PR_stat : PR_stat64); #else if ((flags & AT_SYMLINK_NOFOLLOW) != 0) modif.new_sysarg_num = PR_lstat64; else modif.new_sysarg_num = PR_stat64; #endif if (modify_syscall(tracee, config, &modif)) { // Do this check only if we are patching this syscall. // New flags have been added since 2.6.38 // that we should not error on. if ((flags & ~AT_SYMLINK_NOFOLLOW) != 0) { return -EINVAL; /* Exposed by LTP. */ } } return 0; } case PR_futex: { word_t operation; static bool warned = false; if (!needs_kompat(config, KERNEL_VERSION(2,6,22)) || config->actual_release == 0) return 0; operation = peek_reg(tracee, CURRENT, SYSARG_2); if ((operation & FUTEX_PRIVATE_FLAG) == 0) return 0; if (!warned) { warned = true; note(tracee, WARNING, USER, "kompat: this kernel doesn't support private futexes " "and PRoot can't emulate them. Expect some troubles..."); } poke_reg(tracee, SYSARG_2, operation & ~FUTEX_PRIVATE_FLAG); return 0; } case PR_futimesat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_utimes, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 2, .offset = -1 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_inotify_init1: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,27), .new_sysarg_num = PR_inotify_init, .shifts = NONE }; modify_syscall(tracee, config, &modif); return 0; } case PR_linkat: { word_t flags; Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_link, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 1, .offset = -1 }, [1] = { .sysarg = SYSARG_4, .nb_args = 1, .offset = -2 } } }; flags = peek_reg(tracee, CURRENT, SYSARG_5); if ((flags & ~AT_SYMLINK_FOLLOW) != 0) return -EINVAL; /* Exposed by LTP. */ modify_syscall(tracee, config, &modif); return 0; } case PR_mkdirat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_mkdir, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 2, .offset = -1 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_mknodat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_mknod, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 3, .offset = -1 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_openat: { bool modified; Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_open, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 3, .offset = -1 } } }; modified = modify_syscall(tracee, config, &modif); discard_fd_flags(tracee, config, O_CLOEXEC, KERNEL_VERSION(2,6,23), modified ? SYSARG_2 : SYSARG_3); return 0; } case PR_open: discard_fd_flags(tracee, config, O_CLOEXEC, KERNEL_VERSION(2,6,23), SYSARG_2); return 0; case PR_pipe2: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,27), .new_sysarg_num = PR_pipe, .shifts = NONE }; modify_syscall(tracee, config, &modif); return 0; } case PR_pselect6: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .shifts = NONE }; #if defined(ARCH_X86_64) modif.new_sysarg_num = (get_abi(tracee) != ABI_2 ? PR_select : PR__newselect); #else modif.new_sysarg_num = PR__newselect; #endif modify_syscall(tracee, config, &modif); return 0; } case PR_readlinkat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_readlink, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 3, .offset = -1} } }; modify_syscall(tracee, config, &modif); return 0; } case PR_renameat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_rename, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 1, .offset =-1 }, [1] = { .sysarg = SYSARG_4, .nb_args = 1, .offset = -2 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_renameat2: { Modif modif = { .expected_release = KERNEL_VERSION(3,15,0), .new_sysarg_num = PR_rename, .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 1, .offset =-1 }, [1] = { .sysarg = SYSARG_4, .nb_args = 1, .offset = -2 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_signalfd4: { bool modified; Modif modif = { .expected_release = KERNEL_VERSION(2,6,27), .new_sysarg_num = PR_signalfd, .shifts = NONE }; /* "In Linux up to version 2.6.26, the flags argument * is unused, and must be specified as zero." -- man * signalfd */ modified = modify_syscall(tracee, config, &modif); if (modified) poke_reg(tracee, SYSARG_4, 0); return 0; } case PR_socket: case PR_socketpair: case PR_timerfd_create: discard_fd_flags(tracee, config, O_CLOEXEC | O_NONBLOCK, KERNEL_VERSION(2,6,27), SYSARG_2); return 0; case PR_symlinkat: { Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .new_sysarg_num = PR_symlink, .shifts = { [0] = { .sysarg = SYSARG_3, .nb_args = 1, .offset = -1 } } }; modify_syscall(tracee, config, &modif); return 0; } case PR_unlinkat: { word_t flags; Modif modif = { .expected_release = KERNEL_VERSION(2,6,16), .shifts = { [0] = { .sysarg = SYSARG_2, .nb_args = 1, .offset = -1 } } }; flags = peek_reg(tracee, CURRENT, SYSARG_3); modif.new_sysarg_num = ((flags & AT_REMOVEDIR) != 0 ? PR_rmdir : PR_unlink); modify_syscall(tracee, config, &modif); return 0; } default: return 0; } } /** * Adjust some ELF auxiliary vectors to improve the compatibility. * This function assumes the "argv, envp, auxv" stuff is pointed to by * @tracee's stack pointer, as expected right after a successful call * to execve(2). */ static void adjust_elf_auxv(Tracee *tracee, Config *config) { ElfAuxVector *vectors; ElfAuxVector *vector; word_t vectors_address; word_t stack_pointer; void *argv_envp; size_t size; size_t reserve_size; int status; vectors_address = get_elf_aux_vectors_address(tracee); if (vectors_address == 0) return; vectors = fetch_elf_aux_vectors(tracee, vectors_address); if (vectors == NULL) return; for (vector = vectors; vector->type != AT_NULL; vector++) { switch (vector->type) { /* Discard AT_SYSINFO* vectors: they can be used to * get the OS release number from memory instead of * from the uname syscall, and only this latter is * currently hooked by PRoot. */ case AT_SYSINFO_EHDR: case AT_SYSINFO: vector->type = AT_IGNORE; vector->value = 0; break; case AT_HWCAP: if (config->hwcap != (word_t) -1) vector->value = config->hwcap; break; case AT_RANDOM: /* Skip only if not in forced mode. */ if (config->actual_release != 0) goto end; break; default: break; } } /* Add the AT_RANDOM vector only if needed. */ if (!needs_kompat(config, KERNEL_VERSION(2,6,29))) goto end; status = add_elf_aux_vector(&vectors, AT_RANDOM, vectors_address); if (status < 0) goto end; /* Not fatal. */ /* Since a new vector needs to be added, the ELF auxiliary * vectors array can't be pushed in place. As a consequence, * argv[] and envp[] arrays are moved one vector downward to * make room for the new ELF auxiliary vectors array. * Remember, the stack layout is as follow right after execve: * * argv[], envp[], auxv[] */ stack_pointer = peek_reg(tracee, CURRENT, STACK_POINTER); size = vectors_address - stack_pointer; argv_envp = talloc_size(tracee->ctx, size); if (argv_envp == NULL) goto end; status = read_data(tracee, argv_envp, stack_pointer, size); if (status < 0) goto end; /* Allocate enough room in tracee's stack for the new ELF * auxiliary vector. */ reserve_size = 2 * sizeof_word(tracee); /* Make sure the stack is still aligned */ reserve_size = ((reserve_size - 1) / STACK_ALIGNMENT + 1) * STACK_ALIGNMENT; stack_pointer -= reserve_size; vectors_address -= reserve_size; /* Note that it is safe to update the stack pointer manually * since we are in execve sysexit. However it should be done * before transfering data since the kernel might not allow * page faults below the stack pointer. */ poke_reg(tracee, STACK_POINTER, stack_pointer); status = write_data(tracee, stack_pointer, argv_envp, size); if (status < 0) return; end: push_elf_aux_vectors(tracee, vectors, vectors_address); return; } /** * Append to the @tracee's current syscall enough calls to fcntl(@fd) * in order to set the flags from the original @sysarg register, if * there are also set in @emulated_flags. */ static void emulate_fd_flags(Tracee *tracee, word_t fd, Reg sysarg, int emulated_flags) { word_t flags; flags = peek_reg(tracee, ORIGINAL, sysarg); if (flags == 0) return; if ((emulated_flags & flags & O_CLOEXEC) != 0) register_chained_syscall(tracee, PR_fcntl, fd, F_SETFD, FD_CLOEXEC, 0, 0, 0); if ((emulated_flags & flags & O_NONBLOCK) != 0) register_chained_syscall(tracee, PR_fcntl, fd, F_SETFL, O_NONBLOCK, 0, 0, 0); force_chain_final_result(tracee, peek_reg(tracee, CURRENT, SYSARG_RESULT)); } /** * Adjust the results/output parameters for syscalls that were * modified in handle_sysenter_end(). This function returns -errno if * an error occured, otherwise 0. */ static int handle_sysexit_end(Tracee *tracee, Config *config) { word_t result; word_t sysnum; int status; result = peek_reg(tracee, CURRENT, SYSARG_RESULT); sysnum = get_sysnum(tracee, ORIGINAL); /* Error reported by the kernel. */ status = (int) result; if (status < 0) return 0; switch (sysnum) { case PR_uname: { word_t address; address = peek_reg(tracee, ORIGINAL, SYSARG_1); /* The layout of struct utsname does not depend on the * architecture, it only depends on the kernel * version. In this regards, this structure is stable * since < 2.6.0. */ status = write_data(tracee, address, &config->utsname, sizeof(config->utsname)); if (status < 0) return status; return 0; } case PR_setdomainname: case PR_sethostname: { word_t address; word_t length; char *name; name = (sysnum == PR_setdomainname ? config->utsname.domainname : config->utsname.nodename); length = peek_reg(tracee, ORIGINAL, SYSARG_2); if (length > sizeof(config->utsname.domainname) - 1) return -EINVAL; /* Because of the test above. */ assert(sizeof(config->utsname.domainname) == sizeof(config->utsname.nodename)); address = peek_reg(tracee, ORIGINAL, SYSARG_1); status = read_data(tracee, name, address, length); if (status < 0) return status; /* "name does not require a terminating null byte." -- * man 2 set{domain,host}name. */ name[length] = '\0'; return 0; } case PR_accept4: if (get_sysnum(tracee, MODIFIED) == PR_accept) emulate_fd_flags(tracee, result, SYSARG_4, O_CLOEXEC | O_NONBLOCK); return 0; case PR_dup3: if (get_sysnum(tracee, MODIFIED) == PR_dup2) emulate_fd_flags(tracee, peek_reg(tracee, ORIGINAL, SYSARG_2), SYSARG_3, O_CLOEXEC | O_NONBLOCK); return 0; case PR_epoll_create1: if (get_sysnum(tracee, MODIFIED) == PR_epoll_create) emulate_fd_flags(tracee, result, SYSARG_1, O_CLOEXEC | O_NONBLOCK); return 0; case PR_eventfd2: if (get_sysnum(tracee, MODIFIED) == PR_eventfd) emulate_fd_flags(tracee, result, SYSARG_2, O_CLOEXEC | O_NONBLOCK); return 0; case PR_fcntl: { word_t command; if (!needs_kompat(config, KERNEL_VERSION(2,6,24))) return 0; command = peek_reg(tracee, ORIGINAL, SYSARG_2); if (command != F_DUPFD_CLOEXEC) return 0; register_chained_syscall(tracee, PR_fcntl, result, F_SETFD, FD_CLOEXEC, 0, 0, 0); force_chain_final_result(tracee, peek_reg(tracee, CURRENT, SYSARG_RESULT)); return 0; } case PR_inotify_init1: if (get_sysnum(tracee, MODIFIED) == PR_inotify_init) emulate_fd_flags(tracee, result, SYSARG_1, O_CLOEXEC | O_NONBLOCK); return 0; case PR_open: if (needs_kompat(config, KERNEL_VERSION(2,6,23))) emulate_fd_flags(tracee, result, SYSARG_2, O_CLOEXEC); return 0; case PR_openat: if (needs_kompat(config, KERNEL_VERSION(2,6,23))) emulate_fd_flags(tracee, result, SYSARG_3, O_CLOEXEC); return 0; case PR_pipe2: { int fds[2]; if (get_sysnum(tracee, MODIFIED) != PR_pipe) return 0; status = read_data(tracee, fds, peek_reg(tracee, MODIFIED, SYSARG_1), sizeof(fds)); if (status < 0) return 0; emulate_fd_flags(tracee, fds[0], SYSARG_2, O_CLOEXEC | O_NONBLOCK); emulate_fd_flags(tracee, fds[1], SYSARG_2, O_CLOEXEC | O_NONBLOCK); return 0; } case PR_signalfd4: if (get_sysnum(tracee, MODIFIED) == PR_signalfd) emulate_fd_flags(tracee, result, SYSARG_4, O_CLOEXEC | O_NONBLOCK); return 0; case PR_socket: case PR_timerfd_create: if (needs_kompat(config, KERNEL_VERSION(2,6,27))) emulate_fd_flags(tracee, result, SYSARG_2, O_CLOEXEC | O_NONBLOCK); return 0; case PR_socketpair: { int fds[2]; if (!needs_kompat(config, KERNEL_VERSION(2,6,27))) return 0; status = read_data(tracee, fds, peek_reg(tracee, MODIFIED, SYSARG_4), sizeof(fds)); if (status < 0) return 0; emulate_fd_flags(tracee, fds[0], SYSARG_2, O_CLOEXEC | O_NONBLOCK); emulate_fd_flags(tracee, fds[1], SYSARG_2, O_CLOEXEC | O_NONBLOCK); return 0; } default: return 0; } return 0; } /** * Fill @config->utsname and @config->hwcap according to the content * of @string. This function returns -1 if there is a parsing error, * otherwise 0. */ static int parse_utsname(Config *config, const char *string) { struct utsname utsname; int status; assert(string != NULL); status = uname(&utsname); if (status < 0 || getenv("PROOT_FORCE_KOMPAT") != NULL) config->actual_release = 0; else config->actual_release = parse_kernel_release(utsname.release); /* Check whether it is the simple format (ie. release number), * or the complex one: * * '\sysname\nodename\release\version\machine\domainname\hwcap\' * * This complex format is ugly on purpose: it ain't to be used * directly by users. */ if (string[0] == '\\') { const char *start; const char *end; char *end2; /* Initial state of the parser. */ end = string; #define PARSE(field) do { \ size_t length; \ \ start = end + 1; \ end = strchr(start, '\\'); \ if (end == NULL) { \ note(NULL, ERROR, USER, \ "can't find %s field in '%s'", #field, string); \ return -1; \ } \ \ length = end - start; \ length = MIN(length, sizeof(config->utsname.field) - 1); \ strncpy(config->utsname.field, start, length); \ config->utsname.field[length] = '\0'; \ } while(0) PARSE(sysname); PARSE(nodename); PARSE(release); PARSE(version); PARSE(machine); PARSE(domainname); #undef PARSE /* The hwcap field is parsed as an hexadecimal value. */ errno = 0; config->hwcap = strtol(end + 1, &end2, 16); if (errno != 0 || end2[0] != '\\') { note(NULL, ERROR, USER, "can't find hwcap field in '%s'", string); return -1; } } else { size_t length; memcpy(&config->utsname, &utsname, sizeof(config->utsname)); length = MIN(strlen(string), sizeof(config->utsname.release) - 1); strncpy(config->utsname.release, string, length); config->utsname.release[length] = '\0'; config->hwcap = (word_t) -1; } config->virtual_release = parse_kernel_release(config->utsname.release); return 0; } /* List of syscalls handled by this extensions. */ static FilteredSysnum filtered_sysnums[] = { { PR_accept4, FILTER_SYSEXIT }, { PR_dup3, FILTER_SYSEXIT }, { PR_epoll_create1, FILTER_SYSEXIT }, { PR_epoll_pwait, 0 }, { PR_eventfd2, FILTER_SYSEXIT }, { PR_execve, FILTER_SYSEXIT }, { PR_faccessat, 0 }, { PR_fchmodat, 0 }, { PR_fchownat, 0 }, { PR_fcntl, FILTER_SYSEXIT }, { PR_fstatat64, 0 }, { PR_futimesat, 0 }, { PR_futex, 0 }, { PR_inotify_init1, FILTER_SYSEXIT }, { PR_linkat, 0 }, { PR_mkdirat, 0 }, { PR_mknodat, 0 }, { PR_newfstatat, 0 }, { PR_open, FILTER_SYSEXIT }, { PR_openat, FILTER_SYSEXIT }, { PR_pipe2, FILTER_SYSEXIT }, { PR_pselect6, 0 }, { PR_readlinkat, 0 }, { PR_renameat, 0 }, { PR_renameat2, 0 }, { PR_setdomainname, FILTER_SYSEXIT }, { PR_sethostname, FILTER_SYSEXIT }, { PR_signalfd4, FILTER_SYSEXIT }, { PR_socket, FILTER_SYSEXIT }, { PR_socketpair, FILTER_SYSEXIT }, { PR_symlinkat, 0 }, { PR_timerfd_create, FILTER_SYSEXIT }, { PR_uname, FILTER_SYSEXIT }, { PR_unlinkat, 0 }, FILTERED_SYSNUM_END, }; /** * Handler for this @extension. It is triggered each time an @event * occured. See ExtensionEvent for the meaning of @data1 and @data2. */ int kompat_callback(Extension *extension, ExtensionEvent event, intptr_t data1, intptr_t data2 UNUSED) { int status; switch (event) { case INITIALIZATION: { Config *config; extension->config = talloc_zero(extension, Config); if (extension->config == NULL) return -1; config = extension->config; status = parse_utsname(config, (const char *) data1); if (status < 0) return -1; extension->filtered_sysnums = filtered_sysnums; return 0; } case SYSCALL_ENTER_END: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); /* Nothing to do if this syscall is being discarded * (because of an error detected by PRoot). */ if ((int) data1 < 0) return 0; return handle_sysenter_end(tracee, config); } case SYSCALL_EXIT_END: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); return handle_sysexit_end(tracee, config); } case SYSCALL_EXIT_START: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); word_t result = peek_reg(tracee, CURRENT, SYSARG_RESULT);; word_t sysnum = get_sysnum(tracee, ORIGINAL); /* Note: this can be done only before PRoot pushes the * load script into tracee's stack. */ if ((int) result >= 0 && sysnum == PR_execve) adjust_elf_auxv(tracee, config); return 0; } default: return 0; } } proot-5.4.0/src/extension/link2symlink/000077500000000000000000000000001442763353300201265ustar00rootroot00000000000000proot-5.4.0/src/extension/link2symlink/link2symlink.c000066400000000000000000000334461442763353300227320ustar00rootroot00000000000000#include /* rename(2), */ #include /* atoi */ #include /* symlink(2), symlinkat(2), readlink(2), lstat(2), unlink(2), unlinkat(2)*/ #include /* str*, strrchr, strcat, strcpy, strncpy, strncmp */ #include /* lstat(2), */ #include /* lstat(2), */ #include /* E*, */ #include /* PATH_MAX, */ #include "extension/extension.h" #include "tracee/tracee.h" #include "tracee/mem.h" #include "syscall/syscall.h" #include "syscall/sysnum.h" #include "path/path.h" #include "arch.h" #include "attribute.h" #define PREFIX ".l2s." #define DELETED_SUFFIX " (deleted)" /** * Copy the contents of the @symlink into @value (nul terminated). * This function returns -errno if an error occured, otherwise 0. */ static int my_readlink(const char symlink[PATH_MAX], char value[PATH_MAX]) { ssize_t size; size = readlink(symlink, value, PATH_MAX); if (size < 0) return size; if (size >= PATH_MAX) return -ENAMETOOLONG; value[size] = '\0'; return 0; } /** * Move the path pointed to by @tracee's @sysarg to a new location, * symlink the original path to this new one, make @tracee's @sysarg * point to the new location. This function returns -errno if an * error occured, otherwise 0. */ static int move_and_symlink_path(Tracee *tracee, Reg sysarg) { char original[PATH_MAX]; char intermediate[PATH_MAX]; char new_intermediate[PATH_MAX]; char final[PATH_MAX]; char new_final[PATH_MAX]; char * name; struct stat statl; ssize_t size; int status; int link_count; int first_link = 1; int intermediate_suffix = 1; /* Note: this path was already canonicalized. */ size = read_string(tracee, original, peek_reg(tracee, CURRENT, sysarg), PATH_MAX); if (size < 0) return size; if (size >= PATH_MAX) return -ENAMETOOLONG; /* Sanity check: directories can't be linked. */ status = lstat(original, &statl); if (status < 0) return status; if (S_ISDIR(statl.st_mode)) return -EPERM; /* Check if it is a symbolic link. */ if (S_ISLNK(statl.st_mode)) { /* get name */ size = my_readlink(original, intermediate); if (size < 0) return size; name = strrchr(intermediate, '/'); if (name == NULL) name = intermediate; else name++; if (strncmp(name, PREFIX, strlen(PREFIX)) == 0) first_link = 0; } else { /* compute new name */ if (strlen(PREFIX) + strlen(original) + 5 >= PATH_MAX) return -ENAMETOOLONG; name = strrchr(original,'/'); if (name == NULL) name = original; else name++; strncpy(intermediate, original, strlen(original) - strlen(name)); intermediate[strlen(original) - strlen(name)] = '\0'; strcat(intermediate, PREFIX); strcat(intermediate, name); } if (first_link) { /*Move the original content to the new path. */ do { sprintf(new_intermediate, "%s%04d", intermediate, intermediate_suffix); intermediate_suffix++; } while ((access(new_intermediate,F_OK) != -1) && (intermediate_suffix < 1000)); strcpy(intermediate, new_intermediate); strcpy(final, intermediate); strcat(final, ".0002"); status = rename(original, final); if (status < 0) return status; /* Symlink the intermediate to the final file. */ status = symlink(final, intermediate); if (status < 0) return status; /* Symlink the original path to the intermediate one. */ status = symlink(intermediate, original); if (status < 0) return status; } else { /*Move the original content to new location, by incrementing count at end of path. */ size = my_readlink(intermediate, final); if (size < 0) return size; link_count = atoi(final + strlen(final) - 4); link_count++; strncpy(new_final, final, strlen(final) - 4); sprintf(new_final + strlen(final) - 4, "%04d", link_count); status = rename(final, new_final); if (status < 0) return status; strcpy(final, new_final); /* Symlink the intermediate to the final file. */ status = unlink(intermediate); if (status < 0) return status; status = symlink(final, intermediate); if (status < 0) return status; } status = set_sysarg_path(tracee, intermediate, sysarg); if (status < 0) return status; return 0; } /* If path points a file that is a symlink to a file that begins * with PREFIX, let the file be deleted, but also delete the * symlink that was created and decremnt the count that is tacked * to end of original file. */ static int decrement_link_count(Tracee *tracee, Reg sysarg) { char original[PATH_MAX]; char intermediate[PATH_MAX]; char final[PATH_MAX]; char new_final[PATH_MAX]; char * name; struct stat statl; ssize_t size; int status; int link_count; /* Note: this path was already canonicalized. */ size = read_string(tracee, original, peek_reg(tracee, CURRENT, sysarg), PATH_MAX); if (size < 0) return size; if (size >= PATH_MAX) return -ENAMETOOLONG; /* Check if it is a converted link already. */ status = lstat(original, &statl); if (status < 0) return 0; if (!S_ISLNK(statl.st_mode)) return 0; size = my_readlink(original, intermediate); if (size < 0) return size; name = strrchr(intermediate, '/'); if (name == NULL) name = intermediate; else name++; /* Check if an l2s file is pointed to */ if (strncmp(name, PREFIX, strlen(PREFIX)) != 0) return 0; size = my_readlink(intermediate, final); if (size < 0) return size; link_count = atoi(final + strlen(final) - 4); link_count--; /* Check if it is or is not the last link to delete */ if (link_count > 0) { strncpy(new_final, final, strlen(final) - 4); sprintf(new_final + strlen(final) - 4, "%04d", link_count); status = rename(final, new_final); if (status < 0) return status; strcpy(final, new_final); /* Symlink the intermediate to the final file. */ status = unlink(intermediate); if (status < 0) return status; status = symlink(final, intermediate); if (status < 0) return status; } else { /* If it is the last, delete the intermediate and final */ status = unlink(intermediate); if (status < 0) return status; status = unlink(final); if (status < 0) return status; } return 0; } /** * Make it so fake hard links look like real hard link with respect to number of links and inode * This function returns -errno if an error occured, otherwise 0. */ static int handle_sysexit_end(Tracee *tracee) { word_t sysnum; sysnum = get_sysnum(tracee, ORIGINAL); switch (sysnum) { case PR_fstatat64: //int fstatat(int dirfd, const char *pathname, struct stat *buf, int flags); case PR_newfstatat: //int fstatat(int dirfd, const char *pathname, struct stat *buf, int flags); case PR_stat64: //int stat(const char *path, struct stat *buf); case PR_lstat64: //int lstat(const char *path, struct stat *buf); case PR_fstat64: //int fstat(int fd, struct stat *buf); case PR_stat: //int stat(const char *path, struct stat *buf); case PR_statx: //int statx(int fd, const char *path, unsigned flags, unsigned mask, struct statx *buf); case PR_lstat: //int lstat(const char *path, struct stat *buf); case PR_fstat: { //int fstat(int fd, struct stat *buf); word_t result; Reg sysarg_stat; Reg sysarg_path; int status; struct stat statl; ssize_t size; char original[PATH_MAX]; char intermediate[PATH_MAX]; char final[PATH_MAX]; char * name; struct stat finalStat; /* Override only if it succeed. */ result = peek_reg(tracee, CURRENT, SYSARG_RESULT); if (result != 0) return 0; if (sysnum == PR_fstat64 || sysnum == PR_fstat) { status = readlink_proc_pid_fd(tracee->pid, peek_reg(tracee, MODIFIED, SYSARG_1), original); if (strcmp(original + strlen(original) - strlen(DELETED_SUFFIX), DELETED_SUFFIX) == 0) original[strlen(original) - strlen(DELETED_SUFFIX)] = '\0'; if (status < 0) return status; } else { if (sysnum == PR_fstatat64 || sysnum == PR_newfstatat || sysnum == PR_statx) sysarg_path = SYSARG_2; else sysarg_path = SYSARG_1; size = read_string(tracee, original, peek_reg(tracee, MODIFIED, sysarg_path), PATH_MAX); if (size < 0) return size; if (size >= PATH_MAX) return -ENAMETOOLONG; } name = strrchr(original, '/'); if (name == NULL) name = original; else name++; /* Check if it is a link */ status = lstat(original, &statl); if (strncmp(name, PREFIX, strlen(PREFIX)) == 0) { if (S_ISLNK(statl.st_mode)) { strcpy(intermediate,original); goto intermediate_proc; } else { strcpy(final,original); goto final_proc; } } if (!S_ISLNK(statl.st_mode)) return 0; size = my_readlink(original, intermediate); if (size < 0) return size; name = strrchr(intermediate, '/'); if (name == NULL) name = intermediate; else name++; if (strncmp(name, PREFIX, strlen(PREFIX)) != 0) return 0; intermediate_proc: size = my_readlink(intermediate, final); if (size < 0) return size; final_proc: status = lstat(final,&finalStat); if (status < 0) return status; finalStat.st_nlink = atoi(final + strlen(final) - 4); /* Get the address of the 'stat' structure. */ if (sysnum == PR_fstatat64 || sysnum == PR_newfstatat) sysarg_stat = SYSARG_3; else if (sysnum == PR_statx) sysarg_stat = SYSARG_5; else sysarg_stat = SYSARG_2; status = write_data(tracee, peek_reg(tracee, ORIGINAL, sysarg_stat), &finalStat, sizeof(finalStat)); if (status < 0) return status; return 0; } default: return 0; } } /** * When @translated_path is a faked hard-link, replace it with the * point it (internally) points to. */ static void translated_path(char translated_path[PATH_MAX]) { char path2[PATH_MAX]; char path[PATH_MAX]; char *component; int status; status = my_readlink(translated_path, path); if (status < 0) return; component = strrchr(path, '/'); if (component == NULL) return; component++; if (strncmp(component, PREFIX, strlen(PREFIX)) != 0) return; status = my_readlink(path, path2); if (status < 0) return; #if 0 /* Sanity check. */ component = strrchr(path, '/'); if (component == NULL) return; component++; if (strncmp(component, PREFIX, strlen(PREFIX)) != 0) return; #endif strcpy(translated_path, path2); return; } /** * Handler for this @extension. It is triggered each time an @event * occurred. See ExtensionEvent for the meaning of @data1 and @data2. */ int link2symlink_callback(Extension *extension, ExtensionEvent event, intptr_t data1, intptr_t data2 UNUSED) { int status; switch (event) { case INITIALIZATION: { /* List of syscalls handled by this extensions. */ static FilteredSysnum filtered_sysnums[] = { { PR_link, FILTER_SYSEXIT }, { PR_linkat, FILTER_SYSEXIT }, { PR_unlink, FILTER_SYSEXIT }, { PR_unlinkat, FILTER_SYSEXIT }, { PR_fstat, FILTER_SYSEXIT }, { PR_fstat64, FILTER_SYSEXIT }, { PR_fstatat64, FILTER_SYSEXIT }, { PR_lstat, FILTER_SYSEXIT }, { PR_lstat64, FILTER_SYSEXIT }, { PR_newfstatat, FILTER_SYSEXIT }, { PR_stat, FILTER_SYSEXIT }, { PR_statx, FILTER_SYSEXIT }, { PR_stat64, FILTER_SYSEXIT }, { PR_rename, FILTER_SYSEXIT }, { PR_renameat, FILTER_SYSEXIT }, FILTERED_SYSNUM_END, }; extension->filtered_sysnums = filtered_sysnums; return 0; } case SYSCALL_ENTER_END: { Tracee *tracee = TRACEE(extension); switch (get_sysnum(tracee, ORIGINAL)) { case PR_rename: /*int rename(const char *oldpath, const char *newpath); *If newpath is a psuedo hard link decrement the link count. */ status = decrement_link_count(tracee, SYSARG_2); if (status < 0) return status; break; case PR_renameat: /*int renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); *If newpath is a psuedo hard link decrement the link count. */ status = decrement_link_count(tracee, SYSARG_4); if (status < 0) return status; break; case PR_unlink: /* If path points a file that is an symlink to a file that begins * with PREFIX, let the file be deleted, but also decrement the * hard link count, if it is greater than 1, otherwise delete * the original file and intermediate file too. */ status = decrement_link_count(tracee, SYSARG_1); if (status < 0) return status; break; case PR_unlinkat: /* If path points a file that is a symlink to a file that begins * with PREFIX, let the file be deleted, but also delete the * symlink that was created and decremnt the count that is tacked * to end of original file. */ status = decrement_link_count(tracee, SYSARG_2); if (status < 0) return status; break; case PR_link: /* Convert: * * int link(const char *oldpath, const char *newpath); * * into: * * int symlink(const char *oldpath, const char *newpath); */ status = move_and_symlink_path(tracee, SYSARG_1); if (status < 0) return status; set_sysnum(tracee, PR_symlink); break; case PR_linkat: /* Convert: * * int linkat(int olddirfd, const char *oldpath, * int newdirfd, const char *newpath, int flags); * * into: * * int symlink(const char *oldpath, const char *newpath); * * Note: PRoot has already canonicalized * linkat() paths this way: * * olddirfd + oldpath -> oldpath * newdirfd + newpath -> newpath */ status = move_and_symlink_path(tracee, SYSARG_2); if (status < 0) return status; poke_reg(tracee, SYSARG_1, peek_reg(tracee, CURRENT, SYSARG_2)); poke_reg(tracee, SYSARG_2, peek_reg(tracee, CURRENT, SYSARG_4)); set_sysnum(tracee, PR_symlink); break; default: break; } return 0; } case SYSCALL_EXIT_END: { return handle_sysexit_end(TRACEE(extension)); } case TRANSLATED_PATH: translated_path((char *) data1); return 0; default: return 0; } } proot-5.4.0/src/extension/portmap/000077500000000000000000000000001442763353300171625ustar00rootroot00000000000000proot-5.4.0/src/extension/portmap/map.c000066400000000000000000000057321442763353300201120ustar00rootroot00000000000000/* * Copyright (C) 2016 Vincent Hage */ #include /* inet_ntop */ #include "cli/note.h" #include "extension/portmap/portmap.h" /** * Set all entries empty by setting their key and values to PORTMAP_DEFAULT_VALUE. * The table mask is used in get_index as a fast way of doing the modulus operation. */ void initialize_portmap(PortMap *portmap) { int i; for(i = 0; i < PORTMAP_SIZE; i++) { portmap->map[i].port_in = PORTMAP_DEFAULT_VALUE; portmap->map[i].port_out = PORTMAP_DEFAULT_VALUE; } portmap->table_mask = PORTMAP_SIZE - 1; } /** * Find an entry that is either empty or has the same key. * Return the index is successful, or PORTMAP_DEFAULT_VALUE otherwise */ uint16_t get_index(PortMap *portmap, uint16_t key) { int i = 0; uint16_t index; /* the table mask is used instead of the mod operation * to removed the unecessary bits of a number, to get an index. */ index = key & portmap->table_mask; /* we go through the map until either: * 1. the end of the map is reached * 2. an empty entry is reached (if check_empty is true) * 3. an entry with the same key is found */ while(index < PORTMAP_SIZE && portmap->map[index].port_in != PORTMAP_DEFAULT_VALUE && portmap->map[index].port_in != key) { index++; i++; } /* if a good entry has been found, we can return it directly */ if(index < PORTMAP_SIZE) return index; /* otherwise, we loop back from the beginning */ index = 0; /* we go through the map until either: * 1. i == PORTMAP_SIZE (the whole map has been explored) * 2. an empty entry is reached * 3. an entry with the same key is found */ while(i < PORTMAP_SIZE && portmap->map[index].port_in != PORTMAP_DEFAULT_VALUE && portmap->map[index].port_in != key) { index++; i++; } if(i < PORTMAP_SIZE) /* a good entry has been found */ return index; else /* the map is full */ return PORTMAP_SIZE; } /** * Add an entry to the port map by either finding an available entry, * or write on an existing one with the same key. * Return true if successful, or false otherwise. */ int add_entry(PortMap *portmap, uint16_t port_in, uint16_t port_out) { Tracee *tracee = TRACEE(global_portmap_extension); uint16_t index = get_index(portmap, port_in); /* no available entry has been found */ if(index == PORTMAP_SIZE) return -1; portmap->map[index].port_in = port_in; portmap->map[index].port_out = port_out; VERBOSE(tracee, PORTMAP_VERBOSITY, "new port mapping entry: %d -> %d", ntohs(port_in), ntohs(port_out)); return 0; } /** * Find the entry corresponding to port_in, * and returns the associated port_out. * If no entry is found, return PORTMAP_DEFAULT_VALUE. */ uint16_t get_port(PortMap *portmap, uint16_t port_in) { uint16_t index = get_index(portmap, port_in); /* no corresponding entry has been found */ if(index == PORTMAP_SIZE) return PORTMAP_DEFAULT_VALUE; if(portmap->map[index].port_in == port_in) return portmap->map[index].port_out; else return PORTMAP_DEFAULT_VALUE; } proot-5.4.0/src/extension/portmap/portmap.c000066400000000000000000000353661442763353300210250ustar00rootroot00000000000000/* * Copyright (C) 2016 Vincent Hage */ #include /* intptr_t, */ #include /* strtoul(3), */ #include /* memset */ #include /* strncpy */ #include /* AF_UNIX, AF_INET */ #include /* inet_ntop */ #include /* SYS_*, */ #include "cli/note.h" #include "extension/extension.h" #include "tracee/mem.h" /* read_data */ #include "syscall/chain.h" /* register_chained_syscall */ #include "extension/portmap/portmap.h" Extension *global_portmap_extension = NULL; typedef struct Config { PortMap portmap; bool netcoop_mode; bool need_to_check_new_port; uint16_t old_port; word_t sockfd; } Config; /** * Change the port of the socket address, if it maps with an entry. * Return 0 if no relevant entry is found, and 1 if the port has been changed. */ int change_inet_socket_port(Tracee *tracee, Config *config, word_t sockfd, struct sockaddr_in *sockaddr, bool bind_mode) { uint16_t port_in, port_out; port_in = sockaddr->sin_port; port_out = get_port(&config->portmap, port_in); if(port_out == PORTMAP_DEFAULT_VALUE) { if (bind_mode && config->netcoop_mode && !config->need_to_check_new_port) { VERBOSE(tracee, PORTMAP_VERBOSITY, "ipv4 netcoop mode with: %d", htons(port_in)); sockaddr->sin_port = 0; // the system will assign an available port config->old_port = port_in; // we keep this one for adding a new entry config->need_to_check_new_port = true; config->sockfd = sockfd; return 1; } VERBOSE(tracee, PORTMAP_VERBOSITY, "ipv4 port ignored: %d ", htons(port_in)); return 0; } sockaddr->sin_port = port_out; VERBOSE(tracee, PORTMAP_VERBOSITY, "ipv4 port translation: %d -> %d (NOT GUARANTEED: bind might still fail on target port)", htons(port_in), htons(port_out)); return 1; } /** * Change the port of the socket address, if it maps with an entry. * Return 0 if no relevant entry is found, and 1 if the port has been changed. */ int change_inet6_socket_port(Tracee *tracee, Config *config, word_t sockfd, struct sockaddr_in6 *sockaddr, bool bind_mode) { uint16_t port_in, port_out; port_in = sockaddr->sin6_port; port_out = get_port(&config->portmap, port_in); if(port_out == PORTMAP_DEFAULT_VALUE) { if (bind_mode && config->netcoop_mode && !config->need_to_check_new_port) { VERBOSE(tracee, PORTMAP_VERBOSITY, "ipv6 netcoop mode with: %d", htons(port_in)); sockaddr->sin6_port = 0; // the system will assign an available port config->old_port = port_in; // we keep this one for adding a new entry config->need_to_check_new_port = true; config->sockfd = sockfd; return 1; } VERBOSE(tracee, PORTMAP_VERBOSITY, "ipv6 port ignored: %d ", htons(port_in)); return 0; } sockaddr->sin6_port = port_out; VERBOSE(tracee, PORTMAP_VERBOSITY, "ipv6 port translation: %d -> %d (NOT GUARANTEED: bind might still fail on target port)", htons(port_in), htons(port_out)); return 1; } int prepare_getsockname_chained_syscall(Tracee *tracee, Config *config, word_t sockfd, int is_socketcall) { int status = 0; word_t sock_addr, size_addr; struct sockaddr_un sockaddr; socklen_t size; size = sizeof(sockaddr); /* we check that it's the correct socket */ if(sockfd != config->sockfd) return 0; /* we allocate a memory place to store the socket address. * This is a buffer that will be filled by the getsockname() syscall. */ sock_addr = alloc_mem(tracee, sizeof(sockaddr)); if (sock_addr == 0) return -EFAULT; size_addr = alloc_mem(tracee, sizeof(socklen_t)); if(size_addr == 0) return -EFAULT; memset(&sockaddr, '\0', sizeof(sockaddr)); /* we write the modified socket in this new address */ status = write_data(tracee, sock_addr, &sockaddr, sizeof(sockaddr)); if (status < 0) return status; status = write_data(tracee, size_addr, &size, sizeof(size)); if (status < 0) return status; /* Only by using getsockname can we retrieve the port automatically * assigned by the system to the socket. */ if (!is_socketcall) { status = register_chained_syscall( tracee, PR_getsockname, sockfd, // SYS_ARG1, socket file descriptor. sock_addr, size_addr, 0, 0, 0 ); } else { unsigned long args[6]; args[0] = sockfd; args[1] = sock_addr; args[2] = size_addr; args[3] = 0; args[4] = 0; args[5] = 0; /* We allocate a little bloc of memory to store socketcall's arguments */ word_t args_addr = alloc_mem(tracee, 6 * sizeof_word(tracee)); status = write_data(tracee, args_addr, &args, 6 * sizeof_word(tracee)); status = register_chained_syscall( tracee, PR_socketcall, SYS_GETSOCKNAME, args_addr, // SYS_ARG1, socket file descriptor. 0, 0, 0, 0 ); } if (status < 0) return status; //status = restart_original_syscall(tracee); //if (status < 0) // return status; return 0; } int translate_port(Tracee *tracee, Config *config, word_t sockfd, word_t *sock_addr, int size, int is_bind_syscall) { struct sockaddr_un sockaddr; int status; if (sock_addr == 0) return 0; /* Essential step, we clean the structure before adding data to it */ memset(&sockaddr, '\0', sizeof(sockaddr)); /* Next, we read the socket address structure from the tracee's memory */ status = read_data(tracee, &sockaddr, *sock_addr, size); if (status < 0) return status; //if(sysnum == PR_connect || sysnum == PR_bind) { /* Before binding to a socket, the system does some connect() * to the NSCD (Name Service Cache Daemon) on its own. * These connect calls are for sockets in the AF_FILE domain; * remember that AF_FILE and AF_UNIX have the same value. * Their path is always '/var/run/nscd/socket'. */ status = 0; if (sockaddr.sun_family == AF_INET) { status = change_inet_socket_port(tracee, config, sockfd, (struct sockaddr_in *) &sockaddr, is_bind_syscall); } else if (sockaddr.sun_family == AF_INET6) { status = change_inet6_socket_port(tracee, config, sockfd, (struct sockaddr_in6 *) &sockaddr, is_bind_syscall); } if (status <= 0) { /* the socket has been ignored, or an error occured */ return status; } /* we allocate a new memory place for the modified socket address */ *sock_addr = alloc_mem(tracee, sizeof(sockaddr)); if (sock_addr == 0) return -EFAULT; /* we write the modified socket in this new address */ status = write_data(tracee, *sock_addr, &sockaddr, sizeof(sockaddr)); if (status < 0) return status; return 0; } static int handle_sysenter_end(Tracee *tracee, Config *config) { int status; int sysnum; sysnum = get_sysnum(tracee, CURRENT); switch(sysnum) { #define SYSARG_ADDR(n) (args_addr + ((n) - 1) * sizeof_word(tracee)) #define PEEK_WORD(addr, forced_errno) \ peek_word(tracee, addr); \ if (errno != 0) { \ status = forced_errno ?: -errno; \ break; \ } #define POKE_WORD(addr, value) \ poke_word(tracee, addr, value); \ if (errno != 0) { \ status = -errno; \ break; \ } case PR_socketcall: { word_t sockfd; word_t args_addr; word_t sock_addr_saved; word_t sock_addr; word_t size; word_t call; int is_bind_syscall = sysnum == PR_bind; call = peek_reg(tracee, CURRENT, SYSARG_1); is_bind_syscall = call == SYS_BIND; args_addr = peek_reg(tracee, CURRENT, SYSARG_2); switch(call) { case SYS_BIND: case SYS_CONNECT: { /* Remember: PEEK_WORD puts -errno in status and breaks if an * error occured. */ sockfd = PEEK_WORD(SYSARG_ADDR(1), 0); sock_addr = PEEK_WORD(SYSARG_ADDR(2), 0); size = PEEK_WORD(SYSARG_ADDR(3), 0); sock_addr_saved = sock_addr; status = translate_port(tracee, config, sockfd, &sock_addr, size, is_bind_syscall); if (status < 0) break; /* These parameters are used/restored at the exit stage. */ poke_reg(tracee, SYSARG_5, sock_addr_saved); poke_reg(tracee, SYSARG_6, size); /* Remember: POKE_WORD puts -errno in status and breaks if an * error occured. */ POKE_WORD(SYSARG_ADDR(2), sock_addr); POKE_WORD(SYSARG_ADDR(3), sizeof(struct sockaddr_un)); return 0; } case SYS_LISTEN: { word_t sockfd; if(!config->netcoop_mode || !config->need_to_check_new_port) return 0; /* we retrieve this one from the listen() system call */ sockfd = PEEK_WORD(SYSARG_ADDR(1), 0); status = prepare_getsockname_chained_syscall(tracee, config, sockfd, true); return status; } default: return 0; } break; } #undef SYSARG_ADDR #undef PEEK_WORD #undef POKE_WORD case PR_connect: case PR_bind: { int size; int is_bind_syscall; word_t sockfd, sock_addr; /* * Get the reg address of the socket, and the size of the structure. * Note that the sockaddr and addrlen are at the same position for all 4 of these syscalls. */ sockfd = peek_reg(tracee, CURRENT, SYSARG_1); sock_addr = peek_reg(tracee, CURRENT, SYSARG_2); size = (int) peek_reg(tracee, CURRENT, SYSARG_3); is_bind_syscall = sysnum == PR_bind; status = translate_port(tracee, config, sockfd, &sock_addr, size, is_bind_syscall); if (status < 0) { return status; } /* then we modify the syscall argument so that it uses the modified socket address */ poke_reg(tracee, SYSARG_2, sock_addr); //poke_reg(tracee, SYSARG_3, size); poke_reg(tracee, SYSARG_3, sizeof(struct sockaddr_un)); return 0; } case PR_listen: { word_t sockfd; if(!config->netcoop_mode || !config->need_to_check_new_port) return 0; /* we retrieve this one from the listen() system call */ sockfd = peek_reg(tracee, CURRENT, SYSARG_1); status = prepare_getsockname_chained_syscall(tracee, config, sockfd, false); return status; } default: return 0; } return 0; } int add_changed_port_as_entry(Tracee *tracee, Config *config, word_t sockfd, word_t sock_addr, int result) { int status; struct sockaddr_un sockaddr; struct sockaddr_in *sockaddr_in; struct sockaddr_in6 *sockaddr_in6; uint16_t port_in, port_out; if(!config->need_to_check_new_port) return 0; if (sock_addr == 0) return 0; if (sockfd != config->sockfd) return 0; if (result < 0) return -result; /* Essential step, we clean the structure before adding data to it */ memset(&sockaddr, '\0', sizeof(sockaddr)); /* Next, we read the socket address structure from the tracee's memory */ status = read_data(tracee, &sockaddr, sock_addr, sizeof(sockaddr)); if (status < 0) return status; port_in = config->old_port; if (sockaddr.sun_family == AF_INET) { sockaddr_in = (struct sockaddr_in *) &sockaddr; port_out = sockaddr_in->sin_port; } else if (sockaddr.sun_family == AF_INET6) { sockaddr_in6 = (struct sockaddr_in6 *) &sockaddr; port_out = sockaddr_in6->sin6_port; } else return 0; add_portmap_entry(htons(port_in), htons(port_out)); config->need_to_check_new_port = false; config->sockfd = 0; return 0; } static int handle_syschained_exit(Tracee *tracee, Config *config) { int sysnum; sysnum = get_sysnum(tracee, CURRENT); switch(sysnum) { #define SYSARG_ADDR(n) (args_addr + ((n) - 1) * sizeof_word(tracee)) #define PEEK_WORD(addr, forced_errno) \ peek_word(tracee, addr); \ if (errno != 0) { \ status = forced_errno ?: -errno; \ break; \ } #define POKE_WORD(addr, value) \ poke_word(tracee, addr, value); \ if (errno != 0) { \ status = -errno; \ break; \ } case PR_socketcall: { word_t args_addr; word_t call; call = peek_reg(tracee, CURRENT, SYSARG_1); args_addr = peek_reg(tracee, CURRENT, SYSARG_2); switch(call) { case SYS_GETSOCKNAME:{ word_t sockfd, sock_addr; int result, status; sockfd = PEEK_WORD(SYSARG_ADDR(1), 0); sock_addr = PEEK_WORD(SYSARG_ADDR(2), 0); result = peek_reg(tracee, CURRENT, SYSARG_RESULT); status = add_changed_port_as_entry(tracee, config, sockfd, sock_addr, result); return status; } default: return 0; } return 0; } #undef SYSARG_ADDR #undef PEEK_WORD #undef POKE_WORD case PR_getsockname:{ word_t sockfd, sock_addr; int result; // On AArch64 and ARM, SYSARG_1 is the same as SYSARG_RESULT (r0/x0) // so `peek_reg(tracee, CURRENT, SYSARG_1)` would have returned // the result of the syscall instead. // Since the chained call is currently only used for syscall with // `sockfd` as the first argument, we can just use // the ORIGINAL version to get the fd. sockfd = peek_reg(tracee, ORIGINAL, SYSARG_1); sock_addr = peek_reg(tracee, CURRENT, SYSARG_2); result = peek_reg(tracee, CURRENT, SYSARG_RESULT); return add_changed_port_as_entry(tracee, config, sockfd, sock_addr, result); } default: return 0; } } /* List of syscalls handled by this extension. */ static FilteredSysnum filtered_sysnums[] = { { PR_bind, 0 }, { PR_connect, 0 }, { PR_listen, FILTER_SYSEXIT }, /* the exit stage is required to chain syscalls */ // { PR_getsockname, FILTER_SYSEXIT }, /* not needed here, see CHAINED EXIT event */ { PR_socketcall, 0 }, /* for x86 processors with kernel < 4.3 */ FILTERED_SYSNUM_END, }; int add_portmap_entry(uint16_t port_in, uint16_t port_out) { if(global_portmap_extension == NULL) return 0; else { Config *config = talloc_get_type_abort(global_portmap_extension->config, Config); /* careful with little/big endian numbers */ return add_entry(&config->portmap, ntohs(port_in), ntohs(port_out)); } } int activate_netcoop_mode() { if(global_portmap_extension != NULL) { Config *config = talloc_get_type_abort(global_portmap_extension->config, Config); config->netcoop_mode = true; } return 0; } /** * Handler for this @extension. It is triggered each time an @event * occured. See ExtensionEvent for the meaning of @data1 and @data2. */ int portmap_callback(Extension *extension, ExtensionEvent event, intptr_t data1 UNUSED, intptr_t data2 UNUSED) { switch (event) { case INITIALIZATION: { Config *config; if(global_portmap_extension != NULL) return -1; extension->config = talloc_zero(extension, Config); if (extension->config == NULL) return -1; config = talloc_get_type_abort(extension->config, Config); initialize_portmap(&config->portmap); config->netcoop_mode = false; config->need_to_check_new_port = false; config->sockfd = 0; extension->filtered_sysnums = filtered_sysnums; global_portmap_extension = extension; return 0; } case SYSCALL_ENTER_END: { /* As PRoot only translate unix sockets, * it doesn't actually matter whether we do this * on the ENTER_START or ENTER_END stage. */ Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); return handle_sysenter_end(tracee, config); } case SYSCALL_CHAINED_EXIT: { Tracee *tracee = TRACEE(extension); Config *config = talloc_get_type_abort(extension->config, Config); return handle_syschained_exit(tracee, config); } case INHERIT_PARENT: { /* Shared configuration with the parent, * as port maps do not change from tracee to tracee. */ return 0; } default: return 0; } } proot-5.4.0/src/extension/portmap/portmap.h000066400000000000000000000014311442763353300210140ustar00rootroot00000000000000/* * Copyright (C) 2016 Vincent Hage */ #ifndef PORTMAP_H #define PORTMAP_H #include "extension/extension.h" #define PORTMAP_SIZE 4096 /* must be a power of 2 */ #define PORTMAP_DEFAULT_VALUE 0 /* default value that indicates an unused entry */ #define PORTMAP_VERBOSITY 1 typedef struct PortMapEntry { uint16_t port_in; uint16_t port_out; } PortMapEntry; typedef struct PortMap { PortMapEntry map[PORTMAP_SIZE]; uint16_t table_mask; } PortMap; void initialize_portmap(PortMap *portmap); uint16_t get_index(PortMap *portmap, uint16_t key); int add_entry(PortMap *portmap, uint16_t port_in, uint16_t port_out); uint16_t get_port(PortMap *portmap, uint16_t port_in); int add_portmap_entry(uint16_t port_in, uint16_t port_out); int activate_netcoop_mode(); #endif /* PORTMAP_H */ proot-5.4.0/src/extension/python/000077500000000000000000000000001442763353300170215ustar00rootroot00000000000000proot-5.4.0/src/extension/python/proot.i000066400000000000000000000045551442763353300203470ustar00rootroot00000000000000%module proot %{ #define SWIG_FILE_WITH_INIT #include "arch.h" #include "syscall/sysnum.h" #include "tracee/tracee.h" #include "tracee/reg.h" #include "tracee/mem.h" #include "extension/extension.h" /* define an internal global with correct PR number */ #define SYSNUM(item) static const int PR_internal ## item = PR_ ## item; #include "syscall/sysnums.list" #undef SYSNUM %} /* now say PR_item has value PR_internal */ /* works but ugly. Another way to do this ? */ #define SYSNUM(item) static const int PR_ ## item = PR_internal ## item; %include "syscall/sysnums.list" #undef SYSNUM /* python extension helper */ %inline %{ Tracee *get_tracee_from_extension(long extension_handle) { Extension *extension = (Extension *)extension_handle; Tracee *tracee = TRACEE(extension); return tracee; } %} /* arch.h */ typedef unsigned long word_t; /* tracee/tracee.h */ typedef enum { CURRENT = 0, ORIGINAL = 1, MODIFIED = 2, NB_REG_VERSION } RegVersion; /* syscall/sysnum.h */ typedef enum Sysnum; extern Sysnum get_sysnum(const Tracee *tracee, RegVersion version); extern void set_sysnum(Tracee *tracee, Sysnum sysnum); /* tracee/reg.h */ typedef enum { SYSARG_NUM = 0, SYSARG_1, SYSARG_2, SYSARG_3, SYSARG_4, SYSARG_5, SYSARG_6, SYSARG_RESULT, STACK_POINTER, INSTR_POINTER, RTLD_FINI, STATE_FLAGS, USERARG_1, } Reg; extern word_t peek_reg(const Tracee *tracee, RegVersion version, Reg reg); extern void poke_reg(Tracee *tracee, Reg reg, word_t value); /* tracee/mem.h */ /* make read_data / write_data pythonic */ %apply (char *STRING, size_t LENGTH) { (const void *src_tracer, word_t size2) }; extern int write_data(const Tracee *tracee, word_t dest_tracee, const void *src_tracer, word_t size2); %include %rename(read_data) read_data_for_python; %cstring_output_withsize(void *dest_tracer, int *size2); %inline %{ void read_data_for_python(const Tracee *tracee, word_t src_tracee, void *dest_tracer, int *size2) { int res = read_data(tracee, dest_tracer, src_tracee, *size2); /* in case of error we return empty string */ if (res) *size2 = 0; } %} /* extension/extention.h */ typedef enum { GUEST_PATH, HOST_PATH, SYSCALL_ENTER_START, SYSCALL_ENTER_END, SYSCALL_EXIT_START, SYSCALL_EXIT_END, NEW_STATUS, INHERIT_PARENT, INHERIT_CHILD, SYSCALL_CHAINED_ENTER, SYSCALL_CHAINED_EXIT, INITIALIZATION, REMOVED, PRINT_CONFIG, PRINT_USAGE, } ExtensionEvent; proot-5.4.0/src/extension/python/python.c000066400000000000000000000117741442763353300205200ustar00rootroot00000000000000#include #include #include #include #include "extension/extension.h" #include "cli/note.h" #include "path/temp.h" /* FIXME: need to handle error code properly */ static PyObject *python_callback_func; //static bool is_seccomp_disabling_done = false; /* List of syscalls handled by this extensions. */ static FilteredSysnum filtered_sysnums[] = { FILTERED_SYSNUM_END, }; /* build by swig */ extern void init_proot(void); extern void PyInit__proot(void); /* create python files */ extern unsigned char _binary_python_extension_py_start; extern unsigned char _binary_python_extension_py_end; extern unsigned char _binary_proot_py_start; extern unsigned char _binary_proot_py_end; static int create_python_file(const char *tmp_dir, const char *python_file_name, unsigned char *start_file, unsigned char *end_file) { void *start = (void *) start_file; size_t size = end_file - start_file; char python_full_file_name[PATH_MAX]; int fd; int status; status = snprintf(python_full_file_name, PATH_MAX, "%s/%s", tmp_dir, python_file_name); if (status < 0 || status >= PATH_MAX) { status = -1; } else { fd = open(python_full_file_name, O_WRONLY | O_CREAT, S_IRWXU); if (fd >= 0) { status = write(fd, start, size); close(fd); } } return status>0?0:-1; } static int create_python_extension(const char *tmp_dir) { return create_python_file(tmp_dir, "python_extension.py", &_binary_python_extension_py_start, &_binary_python_extension_py_end); } static int create_proot(const char *tmp_dir) { return create_python_file(tmp_dir, "proot.py", &_binary_proot_py_start, &_binary_proot_py_end); } /* init python once */ void init_python_env() { static bool is_done = false; if (!is_done) { char path_insert[PATH_MAX]; PyObject *pName, *pModule; const char *tmp_dir; int status; tmp_dir = create_temp_directory(NULL, "proot-python"); status = snprintf(path_insert, PATH_MAX, "sys.path.insert(0, '%s')", tmp_dir); if (status < 0 || status >= PATH_MAX) { note(NULL, ERROR, USER, "Unable to create tmp directory\n"); } else if (create_python_extension(tmp_dir) || create_proot(tmp_dir)) { note(NULL, ERROR, USER, "Unable to create python file\n"); is_done = true; } else { Py_Initialize(); #if PY_VERSION_HEX >= 0x03000000 PyInit__proot(); #else init_proot(); #endif PyRun_SimpleString("import sys"); PyRun_SimpleString(path_insert); pName = PyUnicode_FromString("python_extension"); if (pName) { pModule = PyImport_Import(pName); Py_DECREF(pName); if (pModule) { python_callback_func = PyObject_GetAttrString(pModule, "python_callback"); if (python_callback_func && PyCallable_Check(python_callback_func)) ;//note(NULL, INFO, USER, "python_callback find\n"); else note(NULL, ERROR, USER, "python_callback_func error\n"); } else { PyErr_Print(); note(NULL, ERROR, USER, "pModule error\n"); } } else note(NULL, ERROR, USER, "pName error\n"); is_done = true; } } } /* call python callback */ static int python_callback_func_wrapper(Extension *extension, ExtensionEvent event, intptr_t data1, intptr_t data2) { int res = 0; PyObject *pArgs; PyObject *pValue; pArgs = PyTuple_New(4); if (pArgs) { /* setargs */ pValue = PyLong_FromLong((long) extension); if (!pValue) note(NULL, ERROR, USER, "pValue allocation failure\n"); PyTuple_SetItem(pArgs, 0, pValue); pValue = PyLong_FromLong(event); if (!pValue) note(NULL, ERROR, USER, "pValue allocation failure\n"); PyTuple_SetItem(pArgs, 1, pValue); pValue = PyLong_FromLong(data1); if (!pValue) note(NULL, ERROR, USER, "pValue allocation failure\n"); PyTuple_SetItem(pArgs, 2, pValue); pValue = PyLong_FromLong(data2); if (!pValue) note(NULL, ERROR, USER, "pValue allocation failure\n"); PyTuple_SetItem(pArgs, 3, pValue); /* call function */ pValue = PyObject_CallObject(python_callback_func, pArgs); if (pValue != NULL) { res = PyLong_AsLong(pValue); Py_DECREF(pValue); } else { PyErr_Print(); note(NULL, ERROR, USER, "fail to call callback\n"); } Py_DECREF(pArgs); } else note(NULL, ERROR, USER, "pArgs allocation failure\n"); return res; } /** * Handler for this @extension. It is triggered each time an @event * occurred. See ExtensionEvent for the meaning of @data1 and @data2. */ int python_callback(Extension *extension, ExtensionEvent event, intptr_t data1, intptr_t data2) { int res = 0; switch (event) { case INITIALIZATION: { /* not working. Use 'export PROOT_NO_SECCOMP=1' */ /*if (!is_seccomp_disabling_done) { Tracee *tracee = TRACEE(extension); if (tracee->seccomp == ENABLED) tracee->seccomp = DISABLING; is_seccomp_disabling_done = true; }*/ init_python_env(); res = python_callback_func_wrapper(extension, event, data1, data2); extension->filtered_sysnums = filtered_sysnums; } break; default: res = python_callback_func_wrapper(extension, event, data1, data2); } return res; } proot-5.4.0/src/extension/python/python_extension.py000066400000000000000000000006301442763353300230070ustar00rootroot00000000000000from proot import * import ctypes import imp client = None def python_callback(extension, event, data1, data2): global client res = 0 if event == 11: if client: print "Already have a client => refuse to use %s" % (ctypes.string_at(data1)) else: client = imp.load_source('client', ctypes.string_at(data1)) if client: return client.python_callback(extension, event, data1, data2) return 0 proot-5.4.0/src/loader/000077500000000000000000000000001442763353300147325ustar00rootroot00000000000000proot-5.4.0/src/loader/assembly-arm.h000066400000000000000000000052371442763353300175060ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ /* According to the ARM EABI, all registers have undefined values at * program startup except: * * - the instruction pointer (r15) * - the stack pointer (r13) * - the rtld_fini pointer (r0) */ #define BRANCH(stack_pointer, destination) do { \ asm volatile ( \ "// Restore initial stack pointer. \n\t" \ "mov sp, %0 \n\t" \ " \n\t" \ "// Clear rtld_fini. \n\t" \ "mov r0, #0 \n\t" \ " \n\t" \ "// Start the program. \n\t" \ "mov pc, %1 \n" \ : /* no output */ \ : "r" (stack_pointer), "r" (destination) \ : "memory", "sp", "r0", "pc"); \ __builtin_unreachable(); \ } while (0) #define PREPARE_ARGS_1(arg1_) \ register word_t arg1 asm("r0") = arg1_; \ #define PREPARE_ARGS_3(arg1_, arg2_, arg3_) \ PREPARE_ARGS_1(arg1_) \ register word_t arg2 asm("r1") = arg2_; \ register word_t arg3 asm("r2") = arg3_; \ #define PREPARE_ARGS_6(arg1_, arg2_, arg3_, arg4_, arg5_, arg6_) \ PREPARE_ARGS_3(arg1_, arg2_, arg3_) \ register word_t arg4 asm("r3") = arg4_; \ register word_t arg5 asm("r4") = arg5_; \ register word_t arg6 asm("r5") = arg6_; #define OUTPUT_CONTRAINTS_1 \ "r" (arg1) #define OUTPUT_CONTRAINTS_3 \ OUTPUT_CONTRAINTS_1, \ "r" (arg2), "r" (arg3) #define OUTPUT_CONTRAINTS_6 \ OUTPUT_CONTRAINTS_3, \ "r" (arg4), "r" (arg5), "r" (arg6) #define SYSCALL(number_, nb_args, args...) \ ({ \ register word_t number asm("r7") = number_; \ register word_t result asm("r0"); \ PREPARE_ARGS_##nb_args(args) \ asm volatile ( \ "svc #0x00000000 \n\t" \ : "=r" (result) \ : "r" (number), \ OUTPUT_CONTRAINTS_##nb_args \ : "memory"); \ result; \ }) #define OPEN 5 #define CLOSE 6 #define MMAP 192 #define MMAP_OFFSET_SHIFT 12 #define EXECVE 11 #define EXIT 1 #define PRCTL 172 #define MPROTECT 125 proot-5.4.0/src/loader/assembly-arm64.h000066400000000000000000000054501442763353300176550ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ /* According to the ARM64 EABI, all registers have undefined values at * program startup except: * * - the instruction pointer (pc) * - the stack pointer (sp) * - the rtld_fini pointer (x0) */ #define BRANCH(stack_pointer, destination) do { \ asm volatile ( \ "// Restore initial stack pointer. \n\t" \ "mov sp, %0 \n\t" \ " \n\t" \ "// Clear rtld_fini. \n\t" \ "mov x0, #0 \n\t" \ " \n\t" \ "// Start the program. \n\t" \ "br %1 \n" \ : /* no output */ \ : "r" (stack_pointer), "r" (destination) \ : "memory", "sp", "x0"); \ __builtin_unreachable(); \ } while (0) #define PREPARE_ARGS_1(arg1_) \ register word_t arg1 asm("x0") = arg1_; \ #define PREPARE_ARGS_3(arg1_, arg2_, arg3_) \ PREPARE_ARGS_1(arg1_) \ register word_t arg2 asm("x1") = arg2_; \ register word_t arg3 asm("x2") = arg3_; \ #define PREPARE_ARGS_4(arg1_, arg2_, arg3_, arg4_) \ PREPARE_ARGS_3(arg1_, arg2_, arg3_) \ register word_t arg4 asm("x3") = arg4_; \ #define PREPARE_ARGS_6(arg1_, arg2_, arg3_, arg4_, arg5_, arg6_) \ PREPARE_ARGS_4(arg1_, arg2_, arg3_, arg4_) \ register word_t arg5 asm("x4") = arg5_; \ register word_t arg6 asm("x5") = arg6_; #define OUTPUT_CONTRAINTS_1 \ "r" (arg1) #define OUTPUT_CONTRAINTS_3 \ OUTPUT_CONTRAINTS_1, \ "r" (arg2), "r" (arg3) #define OUTPUT_CONTRAINTS_4 \ OUTPUT_CONTRAINTS_3, \ "r" (arg4) #define OUTPUT_CONTRAINTS_6 \ OUTPUT_CONTRAINTS_4, \ "r" (arg5), "r" (arg6) #define SYSCALL(number_, nb_args, args...) \ ({ \ register word_t number asm("x8") = number_; \ register word_t result asm("x0"); \ PREPARE_ARGS_##nb_args(args) \ asm volatile ( \ "svc #0x00000000 \n\t" \ : "=r" (result) \ : "r" (number), \ OUTPUT_CONTRAINTS_##nb_args \ : "memory"); \ result; \ }) #define OPENAT 56 #define CLOSE 57 #define MMAP 222 #define EXECVE 221 #define EXIT 93 #define PRCTL 167 #define MPROTECT 226 proot-5.4.0/src/loader/assembly-x86.h000066400000000000000000000042041442763353300173450ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ /* According to the x86 ABI, all registers have undefined values at * program startup except: * * - the instruction pointer (rip) * - the stack pointer (rsp) * - the rtld_fini pointer (rdx) * - the system flags (eflags) */ #define BRANCH(stack_pointer, destination) do { \ asm volatile ( \ "// Restore initial stack pointer. \n\t" \ "movl %0, %%esp \n\t" \ " \n\t" \ "// Clear state flags. \n\t" \ "pushl $0 \n\t" \ "popfl \n\t" \ " \n\t" \ "// Clear rtld_fini. \n\t" \ "movl $0, %%edx \n\t" \ " \n\t" \ "// Start the program. \n\t" \ "jmpl *%%eax \n" \ : /* no output */ \ : "irm" (stack_pointer), "a" (destination) \ : "memory", "cc", "esp", "edx"); \ __builtin_unreachable(); \ } while (0) extern word_t syscall_6(word_t number, word_t arg1, word_t arg2, word_t arg3, word_t arg4, word_t arg5, word_t arg6); extern word_t syscall_3(word_t number, word_t arg1, word_t arg2, word_t arg3); extern word_t syscall_1(word_t number, word_t arg1); #define SYSCALL(number, nb_args, args...) syscall_##nb_args(number, args) #define OPEN 5 #define CLOSE 6 #define MMAP 192 #define MMAP_OFFSET_SHIFT 12 #define EXECVE 11 #define EXIT 1 #define PRCTL 172 #define MPROTECT 125 proot-5.4.0/src/loader/assembly-x86_64.h000066400000000000000000000054431442763353300176640ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ /* According to the x86_64 ABI, all registers have undefined values at * program startup except: * * - the instruction pointer (rip) * - the stack pointer (rsp) * - the rtld_fini pointer (rdx) * - the system flags (rflags) */ #define BRANCH(stack_pointer, destination) do { \ asm volatile ( \ "// Restore initial stack pointer. \n\t" \ "movq %0, %%rsp \n\t" \ " \n\t" \ "// Clear state flags. \n\t" \ "pushq $0 \n\t" \ "popfq \n\t" \ " \n\t" \ "// Clear rtld_fini. \n\t" \ "movq $0, %%rdx \n\t" \ " \n\t" \ "// Start the program. \n\t" \ "jmpq *%%rax \n" \ : /* no output */ \ : "irm" (stack_pointer), "a" (destination) \ : "memory", "cc", "rsp", "rdx"); \ __builtin_unreachable(); \ } while (0) #define PREPARE_ARGS_1(arg1_) \ register word_t arg1 asm("rdi") = arg1_; \ #define PREPARE_ARGS_3(arg1_, arg2_, arg3_) \ PREPARE_ARGS_1(arg1_) \ register word_t arg2 asm("rsi") = arg2_; \ register word_t arg3 asm("rdx") = arg3_; \ #define PREPARE_ARGS_6(arg1_, arg2_, arg3_, arg4_, arg5_, arg6_) \ PREPARE_ARGS_3(arg1_, arg2_, arg3_) \ register word_t arg4 asm("r10") = arg4_; \ register word_t arg5 asm("r8") = arg5_; \ register word_t arg6 asm("r9") = arg6_; #define OUTPUT_CONTRAINTS_1 \ "r" (arg1) #define OUTPUT_CONTRAINTS_3 \ OUTPUT_CONTRAINTS_1, \ "r" (arg2), "r" (arg3) #define OUTPUT_CONTRAINTS_6 \ OUTPUT_CONTRAINTS_3, \ "r" (arg4), "r" (arg5), "r" (arg6) #define SYSCALL(number_, nb_args, args...) \ ({ \ register word_t number asm("rax") = number_; \ register word_t result asm("rax"); \ PREPARE_ARGS_##nb_args(args) \ asm volatile ( \ "syscall \n\t" \ : "=r" (result) \ : "r" (number), \ OUTPUT_CONTRAINTS_##nb_args \ : "memory", "cc", "rcx", "r11"); \ result; \ }) #define OPEN 2 #define CLOSE 3 #define MMAP 9 #define EXECVE 59 #define EXIT 60 #define PRCTL 157 #define MPROTECT 10 proot-5.4.0/src/loader/assembly.S000066400000000000000000000020071442763353300166740ustar00rootroot00000000000000#if defined(__i386__) .text /* ABI user-land kernel-land ====== ========= =========== number %eax %eax arg1 %edx %ebx arg2 %ecx %ecx arg3 16(%esp) %edx arg4 12(%esp) %esi arg5 8(%esp) %edi arg6 4(%esp) %ebp result N/A %eax */ .globl syscall_6 .type syscall_6, @function syscall_6: /* Callee-saved registers. */ pushl %ebp // %esp -= 0x04 pushl %edi // %esp -= 0x08 pushl %esi // %esp -= 0x0c pushl %ebx // %esp -= 0x10 // mov %eax, %eax // number mov %edx, %ebx // arg1 // mov %ecx, %ecx // arg2 mov 0x14(%esp), %edx // arg3 mov 0x18(%esp), %esi // arg4 mov 0x1c(%esp), %edi // arg5 mov 0x20(%esp), %ebp // arg6 int $0x80 popl %ebx popl %esi popl %edi popl %ebp // mov %eax, %eax // result ret .globl syscall_3 .type syscall_3, @function syscall_3: pushl %ebx mov %edx, %ebx mov 0x8(%esp), %edx int $0x80 popl %ebx ret .globl syscall_1 .type syscall_1, @function syscall_1: pushl %ebx mov %edx, %ebx int $0x80 popl %ebx ret #endif /* defined(__i386__) */ proot-5.4.0/src/loader/loader.c000066400000000000000000000147551442763353300163600ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* bool, true, false, */ #define NO_LIBC_HEADER #include "loader/script.h" #include "compat.h" #include "arch.h" #define GCC_VERSION (__GNUC__ * 10000 \ + __GNUC_MINOR__ * 100 \ + __GNUC_PATCHLEVEL__) #if GCC_VERSION < 40500 #define __builtin_unreachable() #endif #if defined(ARCH_X86_64) # include "loader/assembly-x86_64.h" #elif defined(ARCH_ARM_EABI) # include "loader/assembly-arm.h" #elif defined(ARCH_X86) # include "loader/assembly-x86.h" #elif defined(ARCH_ARM64) # include "loader/assembly-arm64.h" #else # error "Unsupported architecture" #endif #if !defined(MMAP_OFFSET_SHIFT) # define MMAP_OFFSET_SHIFT 0 #endif #define FATAL() do { \ SYSCALL(EXIT, 1, 182); \ __builtin_unreachable(); \ } while (0) #define unlikely(expr) __builtin_expect(!!(expr), 0) /** * Clear the memory from @start (inclusive) to @end (exclusive). */ static inline void clear(word_t start, word_t end) { byte_t *start_misaligned; byte_t *end_misaligned; word_t *start_aligned; word_t *end_aligned; /* Compute the number of mis-aligned bytes. */ word_t start_bytes = start % sizeof(word_t); word_t end_bytes = end % sizeof(word_t); /* Compute aligned addresses. */ start_aligned = (word_t *) (start_bytes ? start + sizeof(word_t) - start_bytes : start); end_aligned = (word_t *) (end - end_bytes); /* Clear leading mis-aligned bytes. */ start_misaligned = (byte_t *) start; while (start_misaligned < (byte_t *) start_aligned) *start_misaligned++ = 0; /* Clear aligned bytes. */ while (start_aligned < end_aligned) *start_aligned++ = 0; /* Clear trailing mis-aligned bytes. */ end_misaligned = (byte_t *) end_aligned; while (end_misaligned < (byte_t *) end) *end_misaligned++ = 0; } /** * Return the address of the last path component of @string_. Note * that @string_ is not modified. */ static inline word_t basename(word_t string_) { byte_t *string = (byte_t *) string_; byte_t *cursor; for (cursor = string; *cursor != 0; cursor++) ; for (; *cursor != (byte_t) '/' && cursor > string; cursor--) ; if (cursor != string) cursor++; return (word_t) cursor; } /** * Interpret the load script pointed to by @cursor. */ void _start(void *cursor) { bool traced = false; bool reset_at_base = true; word_t at_base = 0; word_t fd = -1; word_t status; while(1) { LoadStatement *stmt = cursor; switch (stmt->action) { case LOAD_ACTION_OPEN_NEXT: status = SYSCALL(CLOSE, 1, fd); if (unlikely((int) status < 0)) FATAL(); /* Fall through. */ case LOAD_ACTION_OPEN: #if defined(OPEN) fd = SYSCALL(OPEN, 3, stmt->open.string_address, O_RDONLY, 0); #else fd = SYSCALL(OPENAT, 4, AT_FDCWD, stmt->open.string_address, O_RDONLY, 0); #endif if (unlikely((int) fd < 0)) FATAL(); reset_at_base = true; cursor += LOAD_STATEMENT_SIZE(*stmt, open); break; case LOAD_ACTION_MMAP_FILE: status = SYSCALL(MMAP, 6, stmt->mmap.addr, stmt->mmap.length, stmt->mmap.prot, MAP_PRIVATE | MAP_FIXED, fd, stmt->mmap.offset >> MMAP_OFFSET_SHIFT); if (unlikely(status != stmt->mmap.addr)) FATAL(); if (stmt->mmap.clear_length != 0) clear(stmt->mmap.addr + stmt->mmap.length - stmt->mmap.clear_length, stmt->mmap.addr + stmt->mmap.length); if (reset_at_base) { at_base = stmt->mmap.addr; reset_at_base = false; } cursor += LOAD_STATEMENT_SIZE(*stmt, mmap); break; case LOAD_ACTION_MMAP_ANON: status = SYSCALL(MMAP, 6, stmt->mmap.addr, stmt->mmap.length, stmt->mmap.prot, MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0); if (unlikely(status != stmt->mmap.addr)) FATAL(); cursor += LOAD_STATEMENT_SIZE(*stmt, mmap); break; case LOAD_ACTION_MAKE_STACK_EXEC: SYSCALL(MPROTECT, 3, stmt->make_stack_exec.start, 1, PROT_READ | PROT_WRITE | PROT_EXEC | PROT_GROWSDOWN); cursor += LOAD_STATEMENT_SIZE(*stmt, make_stack_exec); break; case LOAD_ACTION_START_TRACED: traced = true; /* Fall through. */ case LOAD_ACTION_START: { word_t *cursor2 = (word_t *) stmt->start.stack_pointer; const word_t argc = cursor2[0]; const word_t at_execfn = cursor2[1]; word_t name; status = SYSCALL(CLOSE, 1, fd); if (unlikely((int) status < 0)) FATAL(); /* Right after execve, the stack content is as follow: * * +------+--------+--------+--------+ * | argc | argv[] | envp[] | auxv[] | * +------+--------+--------+--------+ */ /* Skip argv[]. */ cursor2 += argc + 1; /* Skip envp[]. */ do cursor2++; while (cursor2[0] != 0); cursor2++; /* Adjust auxv[]. */ do { switch (cursor2[0]) { case AT_PHDR: cursor2[1] = stmt->start.at_phdr; break; case AT_PHENT: cursor2[1] = stmt->start.at_phent; break; case AT_PHNUM: cursor2[1] = stmt->start.at_phnum; break; case AT_ENTRY: cursor2[1] = stmt->start.at_entry; break; case AT_BASE: cursor2[1] = at_base; break; case AT_EXECFN: /* stmt->start.at_execfn can't be used for now since it is * currently stored in a location that will be scratched * by the process (below the final stack pointer). */ cursor2[1] = at_execfn; break; default: break; } cursor2 += 2; } while (cursor2[0] != AT_NULL); /* Note that only 2 arguments are actually necessary... */ name = basename(stmt->start.at_execfn); SYSCALL(PRCTL, 3, PR_SET_NAME, name, 0); if (unlikely(traced)) SYSCALL(EXECVE, 6, 1, stmt->start.stack_pointer, stmt->start.entry_point, 2, 3, 4); else BRANCH(stmt->start.stack_pointer, stmt->start.entry_point); FATAL(); } default: FATAL(); } } FATAL(); } proot-5.4.0/src/loader/script.h000066400000000000000000000037101442763353300164100ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef SCRIPT #define SCRIPT #include "arch.h" #include "attribute.h" struct load_statement { word_t action; union { struct { word_t string_address; } open; struct { word_t addr; word_t length; word_t prot; word_t offset; word_t clear_length; } mmap; struct { word_t start; } make_stack_exec; struct { word_t stack_pointer; word_t entry_point; word_t at_phdr; word_t at_phent; word_t at_phnum; word_t at_entry; word_t at_execfn; } start; }; } PACKED; typedef struct load_statement LoadStatement; #define LOAD_STATEMENT_SIZE(statement, type) \ (sizeof((statement).action) + sizeof((statement).type)) /* Don't use enum, since sizeof(enum) doesn't have to be equal to * sizeof(word_t). Keep values in the same order as their respective * actions appear in loader.c to get a change GCC produces a jump * table. */ #define LOAD_ACTION_OPEN_NEXT 0 #define LOAD_ACTION_OPEN 1 #define LOAD_ACTION_MMAP_FILE 2 #define LOAD_ACTION_MMAP_ANON 3 #define LOAD_ACTION_MAKE_STACK_EXEC 4 #define LOAD_ACTION_START_TRACED 5 #define LOAD_ACTION_START 6 #endif /* SCRIPT */ proot-5.4.0/src/path/000077500000000000000000000000001442763353300144205ustar00rootroot00000000000000proot-5.4.0/src/path/binding.c000066400000000000000000000510761442763353300162070ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* lstat(2), */ #include /* getcwd(2), lstat(2), */ #include /* string(3), */ #include /* bzero(3), */ #include /* assert(3), */ #include /* PATH_MAX, */ #include /* E* */ #include /* CIRCLEQ_*, */ #include /* talloc_*, */ #include "path/binding.h" #include "path/path.h" #include "path/canon.h" #include "cli/note.h" #include "compat.h" #define HEAD(tracee, side) \ (side == GUEST \ ? (tracee)->fs->bindings.guest \ : (side == HOST \ ? (tracee)->fs->bindings.host \ : (tracee)->fs->bindings.pending)) #define NEXT(binding, side) \ (side == GUEST \ ? CIRCLEQ_NEXT(binding, link.guest) \ : (side == HOST \ ? CIRCLEQ_NEXT(binding, link.host) \ : CIRCLEQ_NEXT(binding, link.pending))) #define CIRCLEQ_FOREACH_(tracee, binding, side) \ for (binding = CIRCLEQ_FIRST(HEAD(tracee, side)); \ binding != (void *) HEAD(tracee, side); \ binding = NEXT(binding, side)) #define CIRCLEQ_INSERT_AFTER_(tracee, previous, binding, side) do { \ switch (side) { \ case GUEST: CIRCLEQ_INSERT_AFTER(HEAD(tracee, side), previous, binding, link.guest); break; \ case HOST: CIRCLEQ_INSERT_AFTER(HEAD(tracee, side), previous, binding, link.host); break; \ default: CIRCLEQ_INSERT_AFTER(HEAD(tracee, side), previous, binding, link.pending); break; \ } \ (void) talloc_reference(HEAD(tracee, side), binding); \ } while (0) #define CIRCLEQ_INSERT_BEFORE_(tracee, next, binding, side) do { \ switch (side) { \ case GUEST: CIRCLEQ_INSERT_BEFORE(HEAD(tracee, side), next, binding, link.guest); break; \ case HOST: CIRCLEQ_INSERT_BEFORE(HEAD(tracee, side), next, binding, link.host); break; \ default: CIRCLEQ_INSERT_BEFORE(HEAD(tracee, side), next, binding, link.pending); break; \ } \ (void) talloc_reference(HEAD(tracee, side), binding); \ } while (0) #define CIRCLEQ_INSERT_HEAD_(tracee, binding, side) do { \ switch (side) { \ case GUEST: CIRCLEQ_INSERT_HEAD(HEAD(tracee, side), binding, link.guest); break; \ case HOST: CIRCLEQ_INSERT_HEAD(HEAD(tracee, side), binding, link.host); break; \ default: CIRCLEQ_INSERT_HEAD(HEAD(tracee, side), binding, link.pending); break; \ } \ (void) talloc_reference(HEAD(tracee, side), binding); \ } while (0) #define IS_LINKED(binding, link) \ ((binding)->link.cqe_next != NULL && (binding)->link.cqe_prev != NULL) #define CIRCLEQ_REMOVE_(tracee, binding, name) do { \ CIRCLEQ_REMOVE((tracee)->fs->bindings.name, binding, link.name);\ (binding)->link.name.cqe_next = NULL; \ (binding)->link.name.cqe_prev = NULL; \ talloc_unlink((tracee)->fs->bindings.name, binding); \ } while (0) /** * Print all bindings (verbose purpose). */ static void print_bindings(const Tracee *tracee) { const Binding *binding; if (tracee->fs->bindings.guest == NULL) return; CIRCLEQ_FOREACH_(tracee, binding, GUEST) { if (compare_paths(binding->host.path, binding->guest.path) == PATHS_ARE_EQUAL) note(tracee, INFO, USER, "binding = %s", binding->host.path); else note(tracee, INFO, USER, "binding = %s:%s", binding->host.path, binding->guest.path); } } /** * Get the binding for the given @path (relatively to the given * binding @side). */ Binding *get_binding(const Tracee *tracee, Side side, const char path[PATH_MAX]) { Binding *binding; size_t path_length = strlen(path); /* Sanity checks. */ assert(path != NULL && path[0] == '/'); CIRCLEQ_FOREACH_(tracee, binding, side) { Comparison comparison; const Path *ref; switch (side) { case GUEST: ref = &binding->guest; break; case HOST: ref = &binding->host; break; default: assert(0); return NULL; } comparison = compare_paths2(ref->path, ref->length, path, path_length); if ( comparison != PATHS_ARE_EQUAL && comparison != PATH1_IS_PREFIX) continue; /* Avoid false positive when a prefix of the rootfs is * used as an asymmetric binding, ex.: * * proot -m /usr:/location /usr/local/slackware */ if ( side == HOST && compare_paths(get_root(tracee), "/") != PATHS_ARE_EQUAL && belongs_to_guestfs(tracee, path)) continue; return binding; } return NULL; } /** * Get the binding path for the given @path (relatively to the given * binding @side). */ const char *get_path_binding(const Tracee *tracee, Side side, const char path[PATH_MAX]) { const Binding *binding; binding = get_binding(tracee, side, path); if (!binding) return NULL; switch (side) { case GUEST: return binding->guest.path; case HOST: return binding->host.path; default: assert(0); return NULL; } } /** * Return the path to the guest rootfs for the given @tracee, from the * host point-of-view obviously. Depending on whether * initialize_bindings() was called or not, the path is retrieved from * the "bindings.guest" list or from the "bindings.pending" list, * respectively. */ const char *get_root(const Tracee* tracee) { const Binding *binding; if (tracee == NULL || tracee->fs == NULL) return NULL; if (tracee->fs->bindings.guest == NULL) { if (tracee->fs->bindings.pending == NULL || CIRCLEQ_EMPTY(tracee->fs->bindings.pending)) return NULL; binding = CIRCLEQ_LAST(tracee->fs->bindings.pending); if (compare_paths(binding->guest.path, "/") != PATHS_ARE_EQUAL) return NULL; return binding->host.path; } assert(!CIRCLEQ_EMPTY(tracee->fs->bindings.guest)); binding = CIRCLEQ_LAST(tracee->fs->bindings.guest); assert(strcmp(binding->guest.path, "/") == 0); return binding->host.path; } /** * Substitute the guest path (if any) with the host path in @path. * This function returns: * * * -errno if an error occured * * * 0 if it is a binding location but no substitution is needed * ("symetric" binding) * * * 1 if it is a binding location and a substitution was performed * ("asymmetric" binding) */ int substitute_binding(const Tracee *tracee, Side side, char path[PATH_MAX]) { const Path *reverse_ref; const Path *ref; const Binding *binding; binding = get_binding(tracee, side, path); if (!binding) return -ENOENT; /* Is it a "symetric" binding? */ if (!binding->need_substitution) return 0; switch (side) { case GUEST: ref = &binding->guest; reverse_ref = &binding->host; break; case HOST: ref = &binding->host; reverse_ref = &binding->guest; break; default: assert(0); return -EACCES; } substitute_path_prefix(path, ref->length, reverse_ref->path, reverse_ref->length); return 1; } /** * Remove @binding from all the @tracee's lists of bindings it belongs to. */ void remove_binding_from_all_lists(const Tracee *tracee, Binding *binding) { if (IS_LINKED(binding, link.pending)) CIRCLEQ_REMOVE_(tracee, binding, pending); if (IS_LINKED(binding, link.guest)) CIRCLEQ_REMOVE_(tracee, binding, guest); if (IS_LINKED(binding, link.host)) CIRCLEQ_REMOVE_(tracee, binding, host); } /** * Insert @binding into the list of @bindings, in a sorted manner so * as to make the substitution of nested bindings determistic, ex.: * * -b /bin:/foo/bin -b /usr/bin/more:/foo/bin/more * * Note: "nested" from the @side point-of-view. */ static void insort_binding(const Tracee *tracee, Side side, Binding *binding) { Binding *iterator; Binding *previous = NULL; Binding *next = CIRCLEQ_FIRST(HEAD(tracee, side)); /* Find where it should be added in the list. */ CIRCLEQ_FOREACH_(tracee, iterator, side) { Comparison comparison; const Path *binding_path; const Path *iterator_path; switch (side) { case PENDING: case GUEST: binding_path = &binding->guest; iterator_path = &iterator->guest; break; case HOST: binding_path = &binding->host; iterator_path = &iterator->host; break; default: assert(0); return; } comparison = compare_paths2(binding_path->path, binding_path->length, iterator_path->path, iterator_path->length); switch (comparison) { case PATHS_ARE_EQUAL: if (side == HOST) { previous = iterator; break; } if (tracee->verbose > 0 && getenv("PROOT_IGNORE_MISSING_BINDINGS") == NULL) { note(tracee, WARNING, USER, "both '%s' and '%s' are bound to '%s', " "only the last binding is active.", iterator->host.path, binding->host.path, binding->guest.path); } /* Replace this iterator with the new binding. */ CIRCLEQ_INSERT_AFTER_(tracee, iterator, binding, side); remove_binding_from_all_lists(tracee, iterator); return; case PATH1_IS_PREFIX: /* The new binding contains the iterator. */ previous = iterator; break; case PATH2_IS_PREFIX: /* The iterator contains the new binding. * Use the deepest container. */ if (next == (void *) HEAD(tracee, side)) next = iterator; break; case PATHS_ARE_NOT_COMPARABLE: break; default: assert(0); return; } } /* Insert this binding in the list. */ if (previous != NULL) CIRCLEQ_INSERT_AFTER_(tracee, previous, binding, side); else if (next != (void *) HEAD(tracee, side)) CIRCLEQ_INSERT_BEFORE_(tracee, next, binding, side); else CIRCLEQ_INSERT_HEAD_(tracee, binding, side); } /** * c.f. function above. */ static void insort_binding2(const Tracee *tracee, Binding *binding) { binding->need_substitution = compare_paths(binding->host.path, binding->guest.path) != PATHS_ARE_EQUAL; insort_binding(tracee, GUEST, binding); insort_binding(tracee, HOST, binding); } /** * Create and insert a new binding (@host_path:@guest_path) into the * list of @tracee's bindings. The Talloc parent of this new binding * is @context. This function returns NULL if an error occurred, * otherwise a pointer to the newly created binding. */ Binding *insort_binding3(const Tracee *tracee, const TALLOC_CTX *context, const char host_path[PATH_MAX], const char guest_path[PATH_MAX]) { Binding *binding; binding = talloc_zero(context, Binding); if (binding == NULL) return NULL; strcpy(binding->host.path, host_path); strcpy(binding->guest.path, guest_path); binding->host.length = strlen(binding->host.path); binding->guest.length = strlen(binding->guest.path); insort_binding2(tracee, binding); return binding; } /** * Free all bindings from @bindings. * * Note: this is a Talloc destructor. */ static int remove_bindings(Bindings *bindings) { Binding *binding; Tracee *tracee; /* Unlink all bindings from the @link list. */ #define CIRCLEQ_REMOVE_ALL(name) do { \ binding = CIRCLEQ_FIRST(bindings); \ while (binding != (void *) bindings) { \ Binding *next = CIRCLEQ_NEXT(binding, link.name);\ CIRCLEQ_REMOVE_(tracee, binding, name); \ binding = next; \ } \ } while (0) /* Search which link is used by this list. */ tracee = TRACEE(bindings); if (bindings == tracee->fs->bindings.pending) CIRCLEQ_REMOVE_ALL(pending); else if (bindings == tracee->fs->bindings.guest) CIRCLEQ_REMOVE_ALL(guest); else if (bindings == tracee->fs->bindings.host) CIRCLEQ_REMOVE_ALL(host); bzero(bindings, sizeof(Bindings)); return 0; } /** * Allocate a new binding "@host:@guest" and attach it to * @tracee->fs->bindings.pending. This function complains about * missing @host path only if @must_exist is true. This function * returns the allocated binding on success, NULL on error. */ Binding *new_binding(Tracee *tracee, const char *host, const char *guest, bool must_exist) { Binding *binding; char base[PATH_MAX]; int status; /* Lasy allocation of the list of bindings specified by the * user. This list will be used by initialize_bindings(). */ if (tracee->fs->bindings.pending == NULL) { tracee->fs->bindings.pending = talloc_zero(tracee->fs, Bindings); if (tracee->fs->bindings.pending == NULL) return NULL; CIRCLEQ_INIT(tracee->fs->bindings.pending); talloc_set_destructor(tracee->fs->bindings.pending, remove_bindings); } /* Allocate an empty binding. */ binding = talloc_zero(tracee->ctx, Binding); if (binding == NULL) return NULL; /* Canonicalize the host part of the binding, as expected by * get_binding(). */ status = realpath2(tracee->reconf.tracee, binding->host.path, host, true); if (status < 0) { if (must_exist && getenv("PROOT_IGNORE_MISSING_BINDINGS") == NULL) note(tracee, WARNING, INTERNAL, "can't sanitize binding \"%s\": %s", host, strerror(-status)); goto error; } binding->host.length = strlen(binding->host.path); /* Symetric binding? */ guest = guest ?: host; /* When not absolute, assume the guest path is relative to the * current working directory, as with ``-b .`` for instance. */ if (guest[0] != '/') { status = getcwd2(tracee->reconf.tracee, base); if (status < 0) { note(tracee, WARNING, INTERNAL, "can't sanitize binding \"%s\": %s", binding->guest.path, strerror(-status)); goto error; } } else strcpy(base, "/"); status = join_paths(2, binding->guest.path, base, guest); if (status < 0) { note(tracee, WARNING, SYSTEM, "can't sanitize binding \"%s\"", binding->guest.path); goto error; } binding->guest.length = strlen(binding->guest.path); /* Keep the list of bindings specified by the user ordered, * for the sake of consistency. For instance binding to "/" * has to be the last in the list. */ insort_binding(tracee, PENDING, binding); return binding; error: TALLOC_FREE(binding); return NULL; } /** * Canonicalize the guest part of the given @binding, insert it into * @tracee->fs->bindings.guest and @tracee->fs->bindings.host. This * function returns -1 if an error occured, 0 otherwise. */ static void initialize_binding(Tracee *tracee, Binding *binding) { char path[PATH_MAX]; struct stat statl; int status; /* All bindings but "/" must be canonicalized. The exception * for "/" is required to bootstrap the canonicalization. */ if (compare_paths(binding->guest.path, "/") != PATHS_ARE_EQUAL) { bool dereference; size_t length; strcpy(path, binding->guest.path); length = strlen(path); assert(length > 0); /* Does the user explicitly tell not to dereference * guest path? */ dereference = (path[length - 1] != '!'); if (!dereference) path[length - 1] = '\0'; /* Initial state before canonicalization. */ strcpy(binding->guest.path, "/"); /* Remember the type of the final component, it will * be used in build_glue() later. */ status = lstat(binding->host.path, &statl); tracee->glue_type = (status < 0 || S_ISBLK(statl.st_mode) || S_ISCHR(statl.st_mode) ? S_IFREG : statl.st_mode & S_IFMT); /* Sanitize the guest path of the binding within the alternate rootfs since it is assumed by substitute_binding(). */ status = canonicalize(tracee, path, dereference, binding->guest.path, 0); if (status < 0) { note(tracee, WARNING, INTERNAL, "sanitizing the guest path (binding) \"%s\": %s", path, strerror(-status)); return; } /* Remove the trailing "/" or "/." as expected by * substitute_binding(). */ chop_finality(binding->guest.path); /* Disable definitively the creation of the glue for * this binding. */ tracee->glue_type = 0; } binding->guest.length = strlen(binding->guest.path); insort_binding2(tracee, binding); } /** * Add bindings induced by @new_binding when @tracee is being sub-reconfigured. * For example, if the previous configuration ("-r /rootfs1") contains this * binding: * * -b /home/ced:/usr/local/ced * * and if the current configuration ("-r /rootfs2") introduces such a new * binding: * * -b /usr:/media * * then the following binding is induced: * * -b /home/ced:/media/local/ced */ static void add_induced_bindings(Tracee *tracee, const Binding *new_binding) { Binding *old_binding; char path[PATH_MAX]; int status; /* Only for reconfiguration. */ if (tracee->reconf.tracee == NULL) return; /* From the example, PRoot has already converted "-b /usr:/media" into * "-b /rootfs1/usr:/media" in order to ensure the host part is really a * host path. Here, the host part is converted back to "/usr" since the * comparison can't be made on "/rootfs1/usr". */ strcpy(path, new_binding->host.path); status = detranslate_path(tracee->reconf.tracee, path, NULL); if (status < 0) return; CIRCLEQ_FOREACH_(tracee->reconf.tracee, old_binding, GUEST) { Binding *induced_binding; Comparison comparison; char path2[PATH_MAX]; size_t prefix_length; /* Check if there's an induced binding by searching a common * path prefix in between new/old bindings: * * -b /home/ced:[/usr]/local/ced * -b [/usr]:/media */ comparison = compare_paths(path, old_binding->guest.path); if (comparison != PATH1_IS_PREFIX) continue; /* Convert the path of this induced binding to the new * filesystem namespace. From the example, "/usr/local/ced" is * converted into "/media/local/ced". Note: substitute_binding * can't be used in this case since it would expect * "/rootfs1/usr/local/ced instead". */ prefix_length = strlen(path); if (prefix_length == 1) prefix_length = 0; status = join_paths(2, path2, new_binding->guest.path, old_binding->guest.path + prefix_length); if (status < 0) continue; /* Install the induced binding. From the example: * * -b /home/ced:/media/local/ced */ induced_binding = talloc_zero(tracee->ctx, Binding); if (induced_binding == NULL) continue; strcpy(induced_binding->host.path, old_binding->host.path); strcpy(induced_binding->guest.path, path2); induced_binding->host.length = strlen(induced_binding->host.path); induced_binding->guest.length = strlen(induced_binding->guest.path); VERBOSE(tracee, 2, "induced binding: %s:%s (old) & %s:%s (new) -> %s:%s (induced)", old_binding->host.path, old_binding->guest.path, path, new_binding->guest.path, induced_binding->host.path, induced_binding->guest.path); insort_binding2(tracee, induced_binding); } } /** * Allocate @tracee->fs->bindings.guest and * @tracee->fs->bindings.host, then call initialize_binding() on each * binding listed in @tracee->fs->bindings.pending. */ int initialize_bindings(Tracee *tracee) { Binding *binding; /* Sanity checks. */ assert(get_root(tracee) != NULL); assert(tracee->fs->bindings.pending != NULL); assert(tracee->fs->bindings.guest == NULL); assert(tracee->fs->bindings.host == NULL); /* Allocate @tracee->fs->bindings.guest and * @tracee->fs->bindings.host. */ tracee->fs->bindings.guest = talloc_zero(tracee->fs, Bindings); tracee->fs->bindings.host = talloc_zero(tracee->fs, Bindings); if (tracee->fs->bindings.guest == NULL || tracee->fs->bindings.host == NULL) { note(tracee, ERROR, INTERNAL, "can't allocate enough memory"); TALLOC_FREE(tracee->fs->bindings.guest); TALLOC_FREE(tracee->fs->bindings.host); return -1; } CIRCLEQ_INIT(tracee->fs->bindings.guest); CIRCLEQ_INIT(tracee->fs->bindings.host); talloc_set_destructor(tracee->fs->bindings.guest, remove_bindings); talloc_set_destructor(tracee->fs->bindings.host, remove_bindings); /* The binding to "/" has to be installed before other * bindings since this former is required to canonicalize * these latters. */ binding = CIRCLEQ_LAST(tracee->fs->bindings.pending); assert(compare_paths(binding->guest.path, "/") == PATHS_ARE_EQUAL); /* Call initialize_binding() on each pending binding in * reverse order: the last binding "/" is used to bootstrap * the canonicalization. */ while (binding != (void *) tracee->fs->bindings.pending) { Binding *previous; previous = CIRCLEQ_PREV(binding, link.pending); /* Canonicalize then insert this binding into * tracee->fs->bindings.guest/host. */ initialize_binding(tracee, binding); /* Add induced bindings on sub-reconfiguration. */ add_induced_bindings(tracee, binding); binding = previous; } TALLOC_FREE(tracee->fs->bindings.pending); if (tracee->verbose > 0) print_bindings(tracee); return 0; } proot-5.4.0/src/path/binding.h000066400000000000000000000037151442763353300162110ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef BINDING_H #define BINDING_H #include /* PATH_MAX, */ #include #include "tracee/tracee.h" #include "path.h" typedef struct binding { Path host; Path guest; bool need_substitution; bool must_exist; struct { CIRCLEQ_ENTRY(binding) pending; CIRCLEQ_ENTRY(binding) guest; CIRCLEQ_ENTRY(binding) host; } link; } Binding; typedef CIRCLEQ_HEAD(bindings, binding) Bindings; extern Binding *insort_binding3(const Tracee *tracee, const TALLOC_CTX *context, const char host_path[PATH_MAX], const char guest_path[PATH_MAX]); extern Binding *new_binding(Tracee *tracee, const char *host, const char *guest, bool must_exist); extern int initialize_bindings(Tracee *tracee); extern const char *get_path_binding(const Tracee* tracee, Side side, const char path[PATH_MAX]); extern Binding *get_binding(const Tracee *tracee, Side side, const char path[PATH_MAX]); extern const char *get_root(const Tracee* tracee); extern int substitute_binding(const Tracee* tracee, Side side, char path[PATH_MAX]); extern void remove_binding_from_all_lists(const Tracee *tracee, Binding *binding); #endif /* BINDING_H */ proot-5.4.0/src/path/canon.c000066400000000000000000000250561442763353300156720ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* pid_t */ #include /* PATH_MAX, */ #include /* MAXSYMLINKS, */ #include /* E*, */ #include /* lstat(2), S_ISREG(), */ #include /* access(2), lstat(2), */ #include /* string(3), */ #include /* assert(3), */ #include /* sscanf(3), */ #include "path/canon.h" #include "path/path.h" #include "path/binding.h" #include "path/glue.h" #include "path/proc.h" #include "extension/extension.h" /** * Put an end-of-string ('\0') right before the last component of @path. */ static inline void pop_component(char *path) { int offset; /* Sanity checks. */ assert(path != NULL); offset = strlen(path) - 1; assert(offset >= 0); /* Don't pop over "/", it doesn't mean anything. */ if (offset == 0) { assert(path[0] == '/' && path[1] == '\0'); return; } /* Skip trailing path separators. */ while (offset > 1 && path[offset] == '/') offset--; /* Search for the previous path separator. */ while (offset > 1 && path[offset] != '/') offset--; /* Cut the end of the string before the last component. */ path[offset] = '\0'; assert(path[0] == '/'); } /** * Copy in @component the first path component pointed to by @cursor, * this later is updated to point to the next component for a further * call. This function returns: * * - -errno if an error occured. * * - FINAL_SLASH if it the last component of the path but we * really expect a directory. * * - FINAL_NORMAL if it the last component of the path. * * - 0 otherwise. */ static inline Finality next_component(char component[NAME_MAX], const char **cursor) { const char *start; ptrdiff_t length; bool want_dir; /* Sanity checks. */ assert(component != NULL); assert(cursor != NULL); /* Skip leading path separators. */ while (**cursor != '\0' && **cursor == '/') (*cursor)++; /* Find the next component. */ start = *cursor; while (**cursor != '\0' && **cursor != '/') (*cursor)++; length = *cursor - start; if (length >= NAME_MAX) return -ENAMETOOLONG; /* Extract the component. */ strncpy(component, start, length); component[length] = '\0'; /* Check if a [link to a] directory is expected. */ want_dir = (**cursor == '/'); /* Skip trailing path separators. */ while (**cursor != '\0' && **cursor == '/') (*cursor)++; if (**cursor == '\0') return (want_dir ? FINAL_SLASH : FINAL_NORMAL); return NOT_FINAL; } /** * Resolve bindings (if any) in @guest_path and copy the translated * path into @host_path. Also, this function checks that a non-final * component is either a directory (returned value is 0) or a symlink * (returned value is 1), otherwise it returns -errno or -ENOTDIR. */ static inline int substitute_binding_stat(Tracee *tracee, Finality finality, unsigned int recursion_level, const char guest_path[PATH_MAX], char host_path[PATH_MAX]) { struct stat statl; int status; strcpy(host_path, guest_path); status = substitute_binding(tracee, GUEST, host_path); if (status < 0) return status; /* Don't notify extensions during the initialization of a binding. */ if (tracee->glue_type == 0) { status = notify_extensions(tracee, HOST_PATH, (intptr_t)host_path, IS_FINAL(finality) && recursion_level == 0); if (status < 0) return status; } statl.st_mode = 0; status = lstat(host_path, &statl); /* Build the glue between the hostfs and the guestfs during * the initialization of a binding. */ if (status < 0 && tracee->glue_type != 0) { statl.st_mode = build_glue(tracee, guest_path, host_path, finality); if (statl.st_mode == 0) status = -1; } /* Return an error if a non-final component isn't a directory * nor a symlink. The error depends on why the component * could not be accessed (ENOENT, EACCES, ...), otherwise the * error is "Not a directory". */ if (!IS_FINAL(finality) && !S_ISDIR(statl.st_mode) && !S_ISLNK(statl.st_mode)) return (status < 0 ? -errno : -ENOTDIR); return (S_ISLNK(statl.st_mode) ? 1 : 0); } /** * Copy in @guest_path the canonicalization (see `man 3 realpath`) of * @user_path regarding to @tracee->root. The path to canonicalize * could be either absolute or relative to @guest_path. When the last * component of @user_path is a link, it is dereferenced only if * @deref_final is true -- it is useful for syscalls like lstat(2). * The parameter @recursion_level should be set to 0 unless you know * what you are doing. This function returns -errno if an error * occured, otherwise it returns 0. */ int canonicalize(Tracee *tracee, const char *user_path, bool deref_final, char guest_path[PATH_MAX], unsigned int recursion_level) { char scratch_path[PATH_MAX]; char host_path[PATH_MAX]; Finality finality; const char *cursor; int status; /* Avoid infinite loop on circular links. */ if (recursion_level > MAXSYMLINKS) return -ELOOP; /* Sanity checks. */ assert(user_path != NULL); assert(guest_path != NULL); assert(user_path != guest_path); if (strnlen(guest_path, PATH_MAX) >= PATH_MAX) return -ENAMETOOLONG; if (user_path[0] != '/') { /* Ensure 'guest_path' contains an absolute base of * the relative `user_path`. */ if (guest_path[0] != '/') return -EINVAL; } else strcpy(guest_path, "/"); /* Resolve bindings for the initial '/' component or user_path, * which is not handled in the loop below. * In particular HOST_PATH extensions are called from there. */ status = substitute_binding_stat(tracee, NOT_FINAL, recursion_level, guest_path, host_path); if (status < 0) return status; /* Canonicalize recursely 'user_path' into 'guest_path'. */ cursor = user_path; finality = NOT_FINAL; while (!IS_FINAL(finality)) { Comparison comparison; char component[NAME_MAX]; finality = next_component(component, &cursor); status = (int) finality; if (status < 0) return status; if (strcmp(component, ".") == 0) { if (IS_FINAL(finality)) finality = FINAL_DOT; continue; } if (strcmp(component, "..") == 0) { pop_component(guest_path); if (IS_FINAL(finality)) finality = FINAL_SLASH; continue; } status = join_paths(2, scratch_path, guest_path, component); if (status < 0) return status; /* Resolve bindings and check that a non-final * component exists and either is a directory or is a * symlink. For this latter case, we check that the * symlink points to a directory once it is * canonicalized, at the end of this loop. */ status = substitute_binding_stat(tracee, finality, recursion_level, scratch_path, host_path); if (status < 0) return status; /* Nothing special to do if it's not a link or if we * explicitly ask to not dereference 'user_path', as * required by syscalls like lstat(2). Obviously, this * later condition does not apply to intermediate path * components. Errors are explicitly ignored since * they should be handled by the caller. */ if (status <= 0 || (finality == FINAL_NORMAL && !deref_final)) { strcpy(scratch_path, guest_path); status = join_paths(2, guest_path, scratch_path, component); if (status < 0) return status; continue; } /* It's a link, so we have to dereference *and* * canonicalize to ensure we are not going outside the * new root. */ comparison = compare_paths("/proc", guest_path); switch (comparison) { case PATHS_ARE_EQUAL: case PATH1_IS_PREFIX: /* Some links in "/proc" are generated * dynamically by the kernel. PRoot has to * emulate some of them. */ status = readlink_proc(tracee, scratch_path, guest_path, component, comparison); switch (status) { case CANONICALIZE: /* The symlink is already dereferenced, * now canonicalize it. */ goto canon; case DONT_CANONICALIZE: /* If and only very final, this symlink * shouldn't be dereferenced nor canonicalized. */ if (finality == FINAL_NORMAL) { strcpy(guest_path, scratch_path); return 0; } break; default: if (status < 0) return status; } default: break; } status = readlink(host_path, scratch_path, sizeof(scratch_path)); if (status < 0) return status; else if (status == sizeof(scratch_path)) return -ENAMETOOLONG; scratch_path[status] = '\0'; /* Remove the leading "root" part if needed, it's * useful for "/proc/self/cwd/" for instance. */ status = detranslate_path(tracee, scratch_path, host_path); if (status < 0) return status; canon: /* Canonicalize recursively the referee in case it * is/contains a link, moreover if it is not an * absolute link then it is relative to * 'guest_path'. */ status = canonicalize(tracee, scratch_path, true, guest_path, recursion_level + 1); if (status < 0) return status; /* Check that a non-final canonicalized/dereferenced * symlink exists and is a directory. */ status = substitute_binding_stat(tracee, finality, recursion_level, guest_path, host_path); if (status < 0) return status; /* Here, 'guest_path' shouldn't be a symlink anymore, * unless it is a named file descriptor. */ assert(status != 1 || sscanf(guest_path, "/proc/%*d/fd/%d", &status) == 1); } /* At the exit stage of the first level of recursion, * `guest_path` is fully canonicalized but a terminating '/' * or a terminating '.' may be required to keep the initial * semantic of `user_path`. */ if (recursion_level == 0) { switch (finality) { case FINAL_NORMAL: break; case FINAL_SLASH: strcpy(scratch_path, guest_path); status = join_paths(2, guest_path, scratch_path, ""); if (status < 0) return status; break; case FINAL_DOT: strcpy(scratch_path, guest_path); status = join_paths(2, guest_path, scratch_path, "."); if (status < 0) return status; break; default: assert(0); } } return 0; } proot-5.4.0/src/path/canon.h000066400000000000000000000021371442763353300156720ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef CANON_H #define CANON_H #include #include #include "tracee/tracee.h" extern int canonicalize(Tracee *tracee, const char *user_path, bool deref_final, char guest_path[PATH_MAX], unsigned int nb_recursion); #endif /* CANON_H */ proot-5.4.0/src/path/glue.c000066400000000000000000000132311442763353300155200ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* mkdir(2), lstat(2), */ #include /* mkdir(2), lstat(2), */ #include /* mknod(2), */ #include /* mknod(2), lstat(2), unlink(2), rmdir(2), */ #include /* string(3), */ #include /* assert(3), */ #include /* PATH_MAX, */ #include /* errno, E* */ #include /* talloc_*, */ #include "path/binding.h" #include "path/path.h" #include "path/temp.h" #include "cli/note.h" #include "compat.h" /** * Remove @path if it is empty only. * * Note: this is a Talloc destructor. */ static int remove_placeholder(char *path) { struct stat statl; int status; status = lstat(path, &statl); if (status) return 0; /* Not fatal. */ if (!S_ISDIR(statl.st_mode)) { if (statl.st_size != 0) return 0; /* Not fatal. */ status = unlink(path); } else status = rmdir(path); if (status) return 0; /* Not fatal. */ return 0; } /** * Attach a copy of @path to the autofree context, and set its * destructor to remove_placeholder(). */ static void set_placeholder_destructor(const char *path) { TALLOC_CTX *autofreed; char *placeholder; autofreed = talloc_autofree_context(); if (autofreed == NULL) return; placeholder = talloc_strdup(autofreed, path); if (placeholder == NULL) return; talloc_set_destructor(placeholder, remove_placeholder); } /** * Build in a temporary filesystem the glue between the guest part and * the host part of the @binding_path. This function returns the type * of the bound path, otherwise 0 if an error occured. * * For example, assuming the host path "/opt" is mounted/bound to the * guest path "/black/holes/and/revelations", and assuming this path * can't be created in the guest rootfs (eg. permission denied), then * it is created in a temporary rootfs and all these paths are glued * that way: * * $GUEST/black/ --> $GLUE/black/ * ./holes * ./holes/and * ./holes/and/revelations --> $HOST/opt/ * * This glue allows operations on paths that do not exist in the guest * rootfs but that were specified as the guest part of a binding. */ mode_t build_glue(Tracee *tracee, const char *guest_path, char host_path[PATH_MAX], Finality finality) { bool belongs_to_gluefs; Comparison comparison; Binding *binding; mode_t type; mode_t mode; int status; assert(tracee->glue_type != 0); /* Create the temporary directory where the "glue" rootfs will * lie. */ if (tracee->glue == NULL) { tracee->glue = create_temp_directory(NULL, tracee->tool_name); if (tracee->glue == NULL) { note(tracee, ERROR, INTERNAL, "can't create glue rootfs"); return 0; } talloc_set_name_const(tracee->glue, "$glue"); } comparison = compare_paths(tracee->glue, host_path); belongs_to_gluefs = (comparison == PATHS_ARE_EQUAL || comparison == PATH1_IS_PREFIX); /* If it's not a final component then it is a directory. I definitely * hate how the potential type of the final component is propagated * from initialize_binding() down to here, sadly there's no elegant way * to know its type at this stage. */ if (IS_FINAL(finality)) { type = tracee->glue_type; mode = (belongs_to_gluefs ? 0777 : 0); } else { type = S_IFDIR; mode = 0777; } if (getenv("PROOT_DONT_POLLUTE_ROOTFS") != NULL && !belongs_to_gluefs) goto create_binding; /* Try to create this component into the "guest" or "glue" * rootfs (depending if there were a glue previously). */ if (S_ISDIR(type)) status = mkdir(host_path, mode); else /* S_IFREG, S_IFCHR, S_IFBLK, S_IFIFO or S_IFSOCK. */ status = mknod(host_path, mode | type, 0); /* Remove placeholders from the guest rootfs once PRoot is * terminated. */ if (status >= 0 && !belongs_to_gluefs) set_placeholder_destructor(host_path); /* Nothing else to do if the path already exists or if it is * the final component since it will be pointed to by the * binding being initialized (from the example, * "$GUEST/black/holes/and/revelations" -> "$HOST/opt"). */ if (status >= 0 || errno == EEXIST || IS_FINAL(finality)) return type; /* mkdir/mknod are supposed to always succeed in * tracee->glue. */ if (belongs_to_gluefs) { note(tracee, WARNING, SYSTEM, "mkdir/mknod"); return 0; } create_binding: /* Sanity checks. */ if ( strnlen(tracee->glue, PATH_MAX) >= PATH_MAX || strnlen(guest_path, PATH_MAX) >= PATH_MAX) { note(tracee, WARNING, INTERNAL, "installing the binding: guest path too long"); return 0; } /* From the example, create the binding "/black" -> * "$GLUE/black". */ binding = insort_binding3(tracee, tracee->glue, tracee->glue, guest_path); if (binding == NULL) return 0; /* TODO: emulation of getdents(parent(guest_path)) to finalize * the glue, "black" in getdents("/") from the example. */ return type; } proot-5.4.0/src/path/glue.h000066400000000000000000000021201442763353300155200ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef GLUE_H #define GLUE_H #include /* PATH_MAX, */ #include "tracee/tracee.h" #include "path.h" extern mode_t build_glue(Tracee *tracee, const char *guest_path, char host_path[PATH_MAX], Finality finality); #endif /* GLUE_H */ proot-5.4.0/src/path/path.c000066400000000000000000000463131442763353300155270ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* string(3), */ #include /* va_*(3), */ #include /* assert(3), */ #include /* AT_*, */ #include /* readlink*(2), *stat(2), getpid(2), */ #include /* pid_t, */ #include /* S_ISDIR, */ #include /* opendir(3), readdir(3), */ #include /* snprintf(3), */ #include /* E*, */ #include /* ptrdiff_t, */ #include /* PRI*, */ #include "path/path.h" #include "path/binding.h" #include "path/canon.h" #include "path/proc.h" #include "extension/extension.h" #include "cli/note.h" #include "build.h" #include "compat.h" /** * Copy in @result the concatenation of several paths (@number_paths) * and adds a path separator ('/') in between when needed. This * function returns -errno if an error occured, otherwise it returns 0. */ int join_paths(int number_paths, char result[PATH_MAX], ...) { va_list paths; size_t length; int status; int i; result[0] = '\0'; length = 0; status = 0; /* Parse the list of variadic arguments. */ va_start(paths, result); for (i = 0; i < number_paths; i++) { const char *path; size_t path_length; size_t new_length; path = va_arg(paths, const char *); if (path == NULL) continue; path_length = strlen(path); /* A new path separator is needed. */ if (length > 0 && result[length - 1] != '/' && path[0] != '/') { new_length = length + path_length + 1; if (new_length + 1 >= PATH_MAX) { status = -ENAMETOOLONG; break; } strcat(result + length, "/"); strcat(result + length, path); length = new_length; } /* There are already two path separators. */ else if (length > 0 && result[length - 1] == '/' && path[0] == '/') { new_length = length + path_length - 1; if (new_length + 1 >= PATH_MAX) { status = -ENAMETOOLONG; break; } strcat(result + length, path + 1); length += path_length - 1; } /* There's already one path separator or result[] is empty. */ else { new_length = length + path_length; if (new_length + 1 >= PATH_MAX) { status = -ENAMETOOLONG; break; } strcat(result + length, path); length += path_length; } status = 0; } va_end(paths); return status; } /** * Put in @host_path the full path to the given shell @command. The * @command is searched in @paths if not null, otherwise in $PATH * (relatively to the @tracee's file-system name-space). This * function always returns -1 on error, otherwise 0. */ int which(Tracee *tracee, const char *paths, char host_path[PATH_MAX], const char *command) { char path[PATH_MAX]; const char *cursor; struct stat statr; int status; bool is_explicit; bool found; assert(command != NULL); is_explicit = (strchr(command, '/') != NULL); /* Is the command available without any $PATH look-up? */ status = realpath2(tracee, host_path, command, true); if (status == 0 && stat(host_path, &statr) == 0) { if (is_explicit && !S_ISREG(statr.st_mode)) { note(tracee, ERROR, USER, "'%s' is not a regular file", command); return -EACCES; } if (is_explicit && (statr.st_mode & S_IXUSR) == 0) { note(tracee, ERROR, USER, "'%s' is not executable", command); return -EACCES; } found = true; /* Don't dereference the final component to preserve * argv0 in case it is a symlink to script. */ (void) realpath2(tracee, host_path, command, false); } else found = false; /* Is the the explicit command was found? */ if (is_explicit) { if (found) return 0; else goto not_found; } /* Otherwise search the command in $PATH. */ paths = paths ?: getenv("PATH"); if (paths == NULL || strcmp(paths, "") == 0) goto not_found; cursor = paths; do { size_t length; length = strcspn(cursor, ":"); cursor += length + 1; if (length >= PATH_MAX) continue; else if (length == 0) strcpy(path, "."); else { strncpy(path, cursor - length - 1, length); path[length] = '\0'; } /* Avoid buffer-overflow. */ if (length + strlen(command) + 2 >= PATH_MAX) continue; strcat(path, "/"); strcat(path, command); status = realpath2(tracee, host_path, path, true); if (status == 0 && stat(host_path, &statr) == 0 && S_ISREG(statr.st_mode) && (statr.st_mode & S_IXUSR) != 0) { /* Don't dereference the final component to preserve * argv0 in case it is a symlink to script. */ (void) realpath2(tracee, host_path, path, false); return 0; } } while (*(cursor - 1) != '\0'); not_found: status = getcwd2(tracee, path); if (status < 0) strcpy(path, ""); note(tracee, ERROR, USER, "'%s' not found (root = %s, cwd = %s, $PATH=%s)", command, get_root(tracee), path, paths); /* Check if the command was found without any $PATH look-up * but it didn't contain "/". */ if (found && !is_explicit) note(tracee, ERROR, USER, "to execute a local program, use the './' prefix, for example: ./%s", command); return -1; } /** * Put in @host_path the canonicalized form of @path. In the nominal * case (@tracee == NULL), this function is barely equivalent to * realpath(), but when doing sub-reconfiguration, the path is * canonicalized relatively to the current @tracee's file-system * name-space. This function returns -errno on error, otherwise 0. */ int realpath2(Tracee *tracee, char host_path[PATH_MAX], const char *path, bool deref_final) { int status; if (tracee == NULL) status = (realpath(path, host_path) == NULL ? -errno : 0); else status = translate_path(tracee, host_path, AT_FDCWD, path, deref_final); return status; } /** * Put in @guest_path the canonicalized current working directory. In * the nominal case (@tracee == NULL), this function is barely * equivalent to realpath(), but when doing sub-reconfiguration, the * path is canonicalized relatively to the current @tracee's * file-system name-space. This function returns -errno on error, * otherwise 0. */ int getcwd2(Tracee *tracee, char guest_path[PATH_MAX]) { if (tracee == NULL) { if (getcwd(guest_path, PATH_MAX) == NULL) return -errno; } else { if (strlen(tracee->fs->cwd) >= PATH_MAX) return -ENAMETOOLONG; strcpy(guest_path, tracee->fs->cwd); } return 0; } /** * Remove the trailing "/" or "/.". */ void chop_finality(char *path) { size_t length = strlen(path); if (path[length - 1] == '.') { assert(length >= 2); /* Special case for "/." */ if (length == 2) path[length - 1] = '\0'; else path[length - 2] = '\0'; } else if (path[length - 1] == '/') { /* Special case for "/" */ if (length > 1) path[length - 1] = '\0'; } } /** * Put in @path the result of readlink(/proc/@pid/fd/@fd). This * function returns -errno if an error occured, otherwise 0. */ int readlink_proc_pid_fd(pid_t pid, int fd, char path[PATH_MAX]) { char link[32]; /* 32 > sizeof("/proc//cwd") + sizeof(#ULONG_MAX) */ int status; /* Format the path to the "virtual" link. */ status = snprintf(link, sizeof(link), "/proc/%d/fd/%d", pid, fd); if (status < 0) return -EBADF; if ((size_t) status >= sizeof(link)) return -EBADF; /* Read the value of this "virtual" link. */ status = readlink(link, path, PATH_MAX); if (status < 0) return -EBADF; if (status >= PATH_MAX) return -ENAMETOOLONG; path[status] = '\0'; return 0; } /** * Copy in @result the equivalent of "@tracee->root + canon(@dir_fd + * @user_path)". If @user_path is not absolute then it is relative to * the directory referred by the descriptor @dir_fd (AT_FDCWD is for * the current working directory). See the documentation of * canonicalize() for the meaning of @deref_final. This function * returns -errno if an error occured, otherwise 0. */ int translate_path(Tracee *tracee, char result[PATH_MAX], int dir_fd, const char *user_path, bool deref_final) { char guest_path[PATH_MAX]; int status; /* Use "/" as the base if it is an absolute guest path. */ if (user_path[0] == '/') { strcpy(result, "/"); } /* It is relative to a directory referred by a descriptor, see * openat(2) for details. */ else if (dir_fd != AT_FDCWD) { /* /proc/@tracee->pid/fd/@dir_fd -> result. */ status = readlink_proc_pid_fd(tracee->pid, dir_fd, result); if (status < 0) return status; /* Named file descriptors may reference special * objects like pipes, sockets, inodes, ... Such * objects do not belong to the file-system. */ if (result[0] != '/') return -ENOTDIR; /* Remove the leading "root" part of the base * (required!). */ status = detranslate_path(tracee, result, NULL); if (status < 0) return status; } /* It is relative to the current working directory. */ else { status = getcwd2(tracee, result); if (status < 0) return status; } VERBOSE(tracee, 2, "vpid %" PRIu64 ": translate(\"%s\" + \"%s\")", tracee != NULL ? tracee->vpid : 0, result, user_path); status = notify_extensions(tracee, GUEST_PATH, (intptr_t) result, (intptr_t) user_path); if (status < 0) return status; if (status > 0) goto skip; /* So far "result" was used as a base path, it's time to join * it to the user path. */ assert(result[0] == '/'); status = join_paths(2, guest_path, result, user_path); if (status < 0) return status; strcpy(result, "/"); /* Canonicalize regarding the new root. */ status = canonicalize(tracee, guest_path, deref_final, result, 0); if (status < 0) return status; /* Final binding substitution to convert "result" into a host * path, since canonicalize() works from the guest * point-of-view. */ status = substitute_binding(tracee, GUEST, result); if (status < 0) return status; skip: VERBOSE(tracee, 2, "vpid %" PRIu64 ": -> \"%s\"", tracee != NULL ? tracee->vpid : 0, result); status = notify_extensions(tracee, TRANSLATED_PATH, (intptr_t) result, 0); if (status < 0) return status; return 0; } /** * Remove/substitute the leading part of a "translated" @path. It * returns 0 if no transformation is required (ie. symmetric binding), * otherwise it returns the size in bytes of the updated @path, * including the end-of-string terminator. On error it returns * -errno. */ int detranslate_path(Tracee *tracee, char path[PATH_MAX], const char t_referrer[PATH_MAX]) { size_t prefix_length; ssize_t new_length; bool sanity_check; bool follow_binding; /* Sanity check. */ if (strnlen(path, PATH_MAX) >= PATH_MAX) return -ENAMETOOLONG; /* Don't try to detranslate relative paths (typically the * target of a relative symbolic link). */ if (path[0] != '/') return 0; /* Is it a symlink? */ if (t_referrer != NULL) { Comparison comparison; sanity_check = false; follow_binding = false; /* In some cases bindings have to be resolved. */ comparison = compare_paths("/proc", t_referrer); if (comparison == PATH1_IS_PREFIX) { /* Some links in "/proc" are generated * dynamically by the kernel. PRoot has to * emulate some of them. */ char proc_path[PATH_MAX]; strcpy(proc_path, path); new_length = readlink_proc2(tracee, proc_path, t_referrer); if (new_length < 0) return new_length; if (new_length != 0) { strcpy(path, proc_path); return new_length + 1; } /* Always resolve bindings for symlinks in * "/proc", they always point to the emulated * file-system namespace by design. */ follow_binding = true; } else if (!belongs_to_guestfs(tracee, t_referrer)) { const char *binding_referree; const char *binding_referrer; binding_referree = get_path_binding(tracee, HOST, path); binding_referrer = get_path_binding(tracee, HOST, t_referrer); assert(binding_referrer != NULL); /* Resolve bindings for symlinks that belong * to a binding and point to the same binding. * For example, if "-b /lib:/foo" is specified * and the symlink "/lib/a -> /lib/b" exists * in the host rootfs namespace, then it * should appear as "/foo/a -> /foo/b" in the * guest rootfs namespace for consistency * reasons. */ if (binding_referree != NULL) { comparison = compare_paths(binding_referree, binding_referrer); follow_binding = (comparison == PATHS_ARE_EQUAL); } } } else { sanity_check = true; follow_binding = true; } if (follow_binding) { switch (substitute_binding(tracee, HOST, path)) { case 0: return 0; case 1: return strlen(path) + 1; default: break; } } switch (compare_paths(get_root(tracee), path)) { case PATH1_IS_PREFIX: /* Remove the leading part, that is, the "root". */ prefix_length = strlen(get_root(tracee)); /* Special case when path to the guest rootfs == "/". */ if (prefix_length == 1) prefix_length = 0; new_length = strlen(path) - prefix_length; memmove(path, path + prefix_length, new_length); path[new_length] = '\0'; break; case PATHS_ARE_EQUAL: /* Special case when path == root. */ new_length = 1; strcpy(path, "/"); break; default: /* Ensure the path is within the new root. */ if (sanity_check) return -EPERM; else return 0; } return new_length + 1; } /** * Check if the translated @host_path belongs to the guest rootfs, * that is, isn't from a binding. */ bool belongs_to_guestfs(const Tracee *tracee, const char *host_path) { Comparison comparison; comparison = compare_paths(get_root(tracee), host_path); return (comparison == PATHS_ARE_EQUAL || comparison == PATH1_IS_PREFIX); } /** * Compare @path1 with @path2, which are respectively @length1 and * @length2 long. * * This function works only with paths canonicalized in the same * namespace (host/guest)! */ Comparison compare_paths2(const char *path1, size_t length1, const char *path2, size_t length2) { size_t length_min; bool is_prefix; char sentinel; #if defined DEBUG_OPATH assert(length(path1) == length1); assert(length(path2) == length2); #endif assert(length1 > 0); assert(length2 > 0); if (!length1 || !length2) { return PATHS_ARE_NOT_COMPARABLE; } /* Remove potential trailing '/' for the comparison. */ if (path1[length1 - 1] == '/') length1--; if (path2[length2 - 1] == '/') length2--; if (length1 < length2) { length_min = length1; sentinel = path2[length_min]; } else { length_min = length2; sentinel = path1[length_min]; } /* Optimize obvious cases. */ if (sentinel != '/' && sentinel != '\0') return PATHS_ARE_NOT_COMPARABLE; is_prefix = (strncmp(path1, path2, length_min) == 0); if (!is_prefix) return PATHS_ARE_NOT_COMPARABLE; if (length1 == length2) return PATHS_ARE_EQUAL; else if (length1 < length2) return PATH1_IS_PREFIX; else if (length1 > length2) return PATH2_IS_PREFIX; assert(0); return PATHS_ARE_NOT_COMPARABLE; } Comparison compare_paths(const char *path1, const char *path2) { return compare_paths2(path1, strlen(path1), path2, strlen(path2)); } typedef int (*foreach_fd_t)(const Tracee *tracee, int fd, char path[PATH_MAX]); /** * Call @callback on each open file descriptors of @pid. It returns * the status of the first failure, that is, if @callback returned * seomthing lesser than 0, otherwise 0. */ static int foreach_fd(const Tracee *tracee, foreach_fd_t callback) { struct dirent *dirent; char path[PATH_MAX]; char proc_fd[32]; /* 32 > sizeof("/proc//fd") + sizeof(#ULONG_MAX) */ int status; DIR *dirp; /* Format the path to the "virtual" directory. */ status = snprintf(proc_fd, sizeof(proc_fd), "/proc/%d/fd", tracee->pid); if (status < 0 || (size_t) status >= sizeof(proc_fd)) return 0; /* Open the virtual directory "/proc/$pid/fd". */ dirp = opendir(proc_fd); if (dirp == NULL) return 0; while ((dirent = readdir(dirp)) != NULL) { /* Read the value of this "virtual" link. Don't use * readlinkat(2) here since it would require Linux >= * 2.6.16 and Glibc >= 2.4, whereas PRoot is supposed * to work on any Linux 2.6 systems. */ char tmp[PATH_MAX]; if (strlen(proc_fd) + strlen(dirent->d_name) + 1 >= PATH_MAX) continue; strcpy(tmp, proc_fd); strcat(tmp, "/"); strcat(tmp, dirent->d_name); status = readlink(tmp, path, PATH_MAX); if (status < 0 || status >= PATH_MAX) continue; path[status] = '\0'; /* Ensure it points to a path (not a socket or somethink like that). */ if (path[0] != '/') continue; status = callback(tracee, atoi(dirent->d_name), path); if (status < 0) goto end; } status = 0; end: closedir(dirp); return status; } /** * Helper for list_open_fd(). */ static int list_open_fd_callback(const Tracee *tracee, int fd, char path[PATH_MAX]) { VERBOSE(tracee, 1, "pid %d: access to \"%s\" (fd %d) won't be translated until closed", tracee->pid, path, fd); notify_extensions((Tracee*)tracee, ALREADY_OPENED_FD, (intptr_t)path, (intptr_t)fd); return 0; } /** * Warn for files that are open. It is useful right after PRoot has * attached a process. */ int list_open_fd(const Tracee *tracee) { return foreach_fd(tracee, list_open_fd_callback); } /** * Substitute the first @old_prefix_length bytes of @path with * @new_prefix (the caller has to provides a correct * @new_prefix_length). This function returns the new length of * @path. Note: this function takes care about special cases (like * "/"). */ size_t substitute_path_prefix(char path[PATH_MAX], size_t old_prefix_length, const char *new_prefix, size_t new_prefix_length) { size_t path_length; size_t new_length; path_length = strlen(path); assert(old_prefix_length < PATH_MAX); assert(new_prefix_length < PATH_MAX); if (new_prefix_length == 1) { /* Special case: "/foo" -> "/". Substitute "/foo/bin" * with "/bin" not "//bin". */ new_length = path_length - old_prefix_length; if (new_length != 0) memmove(path, path + old_prefix_length, new_length); else { /* Special case: "/". */ path[0] = '/'; new_length = 1; } } else if (old_prefix_length == 1) { /* Special case: "/" -> "/foo". Substitute "/bin" with * "/foo/bin" not "/foobin". */ new_length = new_prefix_length + path_length; if (new_length >= PATH_MAX) return -ENAMETOOLONG; if (path_length > 1) { memmove(path + new_prefix_length, path, path_length); memcpy(path, new_prefix, new_prefix_length); } else { /* Special case: "/". */ memcpy(path, new_prefix, new_prefix_length); new_length = new_prefix_length; } } else { /* Generic case. */ new_length = path_length - old_prefix_length + new_prefix_length; if (new_length >= PATH_MAX) return -ENAMETOOLONG; memmove(path + new_prefix_length, path + old_prefix_length, path_length - old_prefix_length); memcpy(path, new_prefix, new_prefix_length); } assert(new_length < PATH_MAX); path[new_length] = '\0'; return new_length; } proot-5.4.0/src/path/path.h000066400000000000000000000057071442763353300155360ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef PATH_H #define PATH_H #include /* pid_t, */ #include /* AT_FDCWD, */ #include /* PATH_MAX, */ #include #include "tracee/tracee.h" /* File type. */ typedef enum { REGULAR, SYMLINK, } Type; /* Path point-of-view. */ typedef enum { GUEST, HOST, /* Used for bindings as specified by the user but not * canonicalized yet (new_binding, initialize_binding). */ PENDING, } Side; /* Path with cached attributes. */ typedef struct { char path[PATH_MAX]; size_t length; Side side; } Path; /* Path ending type. */ typedef enum { NOT_FINAL, FINAL_NORMAL, FINAL_SLASH, FINAL_DOT } Finality; #define IS_FINAL(a) ((a) != NOT_FINAL) /* Comparison between two paths. */ typedef enum Comparison { PATHS_ARE_EQUAL, PATH1_IS_PREFIX, PATH2_IS_PREFIX, PATHS_ARE_NOT_COMPARABLE, } Comparison; extern int which(Tracee *tracee, const char *paths, char host_path[PATH_MAX], const char *command); extern int realpath2(Tracee *tracee, char host_path[PATH_MAX], const char *path, bool deref_final); extern int getcwd2(Tracee *tracee, char guest_path[PATH_MAX]); extern void chop_finality(char *path); extern int translate_path(Tracee *tracee, char host_path[PATH_MAX], int dir_fd, const char *guest_path, bool deref_final); extern int detranslate_path(Tracee *tracee, char path[PATH_MAX], const char t_referrer[PATH_MAX]); extern bool belongs_to_guestfs(const Tracee *tracee, const char *path); extern int join_paths(int number_paths, char result[PATH_MAX], ...); extern int list_open_fd(const Tracee *tracee); extern Comparison compare_paths(const char *path1, const char *path2); extern Comparison compare_paths2(const char *path1, size_t length1, const char *path2, size_t length2); extern size_t substitute_path_prefix(char path[PATH_MAX], size_t old_prefix_length, const char *new_prefix, size_t new_prefix_length); extern int readlink_proc_pid_fd(pid_t pid, int fd, char path[PATH_MAX]); /* Check if path interpretable relatively to dirfd, see openat(2) for details. */ #define AT_FD(dirfd, path) ((dirfd) != AT_FDCWD && ((path) != NULL && (path)[0] != '/')) #endif /* PATH_H */ proot-5.4.0/src/path/proc.c000066400000000000000000000130121442763353300155240ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* snprintf(3), */ #include /* strcmp(3), */ #include /* atoi(3), strtol(3), */ #include /* E*, */ #include /* assert(3), */ #include "path/proc.h" #include "tracee/tracee.h" #include "path/path.h" #include "path/binding.h" /** * This function emulates the @result of readlink("@base/@component") * with respect to @tracee, where @base belongs to "/proc" (according * to @comparison). This function returns -errno on error, an enum * @action otherwise (c.f. above). * * Unlike readlink(), this function includes the nul terminating byte * to @result. */ Action readlink_proc(const Tracee *tracee, char result[PATH_MAX], const char base[PATH_MAX], const char component[NAME_MAX], Comparison comparison) { const Tracee *known_tracee; char proc_path[64]; /* 64 > sizeof("/proc//fd/") + 2 * sizeof(#ULONG_MAX) */ int status; pid_t pid; assert(comparison == compare_paths("/proc", base)); /* Remember: comparison = compare_paths("/proc", base) */ switch (comparison) { case PATHS_ARE_EQUAL: /* Substitute "/proc/self" with "/proc/". */ if (strcmp(component, "self") != 0) return DEFAULT; status = snprintf(result, PATH_MAX, "/proc/%d", tracee->pid); if (status < 0 || status >= PATH_MAX) return -EPERM; return CANONICALIZE; case PATH1_IS_PREFIX: /* Handle "/proc/" below, where is process * monitored by PRoot. */ break; default: return DEFAULT; } pid = atoi(base + strlen("/proc/")); if (pid == 0) return DEFAULT; /* Handle links in "/proc//". */ status = snprintf(proc_path, sizeof(proc_path), "/proc/%d", pid); if (status < 0 || (size_t) status >= sizeof(proc_path)) return -EPERM; comparison = compare_paths(proc_path, base); switch (comparison) { case PATHS_ARE_EQUAL: known_tracee = get_tracee(tracee, pid, false); if (known_tracee == NULL) return DEFAULT; #define SUBSTITUTE(name, string) \ do { \ if (strcmp(component, #name) != 0) \ break; \ \ status = strlen(string); \ if (status >= PATH_MAX) \ return -EPERM; \ \ strncpy(result, string, status + 1); \ return CANONICALIZE; \ } while (0) /* Substitute link "/proc//???" with the content * of tracee->???. */ SUBSTITUTE(exe, known_tracee->exe); SUBSTITUTE(cwd, known_tracee->fs->cwd); SUBSTITUTE(root, get_root(known_tracee)); #undef SUBSTITUTE return DEFAULT; case PATH1_IS_PREFIX: /* Handle "/proc//???" below. */ break; default: return DEFAULT; } /* Handle links in "/proc//fd/". */ status = snprintf(proc_path, sizeof(proc_path), "/proc/%d/fd", pid); if (status < 0 || (size_t) status >= sizeof(proc_path)) return -EPERM; comparison = compare_paths(proc_path, base); switch (comparison) { char *end_ptr; case PATHS_ARE_EQUAL: /* Sanity check: a number is expected. */ errno = 0; (void) strtol(component, &end_ptr, 10); if (errno != 0 || end_ptr == component) return -EPERM; /* Don't dereference "/proc//fd/???" now: they * can point to anonymous pipe, socket, ... otherwise * they point to a path already canonicalized by the * kernel. * * Note they are still correctly detranslated in * syscall/exit.c if a monitored process uses * readlink() against any of them. */ status = snprintf(result, PATH_MAX, "%s/%s", base, component); if (status < 0 || status >= PATH_MAX) return -EPERM; return DONT_CANONICALIZE; default: break; } return DEFAULT; } /** * This function emulates the @result of readlink("@referer") with * respect to @tracee, where @referer is a strict subpath of "/proc". * This function returns -errno if an error occured, the length of * @result if the readlink was emulated, 0 otherwise. * * Unlike readlink(), this function includes the nul terminating byte * to @result (but this byte is not counted in the returned value). */ ssize_t readlink_proc2(const Tracee *tracee, char result[PATH_MAX], const char referer[PATH_MAX]) { Action action; char base[PATH_MAX]; char *component; /* Sanity check. */ if (strnlen(referer, PATH_MAX) >= PATH_MAX) return -ENAMETOOLONG; assert(compare_paths("/proc", referer) == PATH1_IS_PREFIX); /* It's safe to use strrchr() here since @referer was * previously canonicalized. */ strcpy(base, referer); component = strrchr(base, '/'); /* These cases are not possible: @referer is supposed to be a * canonicalized subpath of "/proc". */ assert(component != NULL && component != base); component[0] = '\0'; component++; if (component[0] == '\0') return 0; action = readlink_proc(tracee, result, base, component, PATH1_IS_PREFIX); return (action == CANONICALIZE ? strlen(result) : 0); } proot-5.4.0/src/path/proc.h000066400000000000000000000030321442763353300155320ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef PROC_H #define PROC_H #include #include "tracee/tracee.h" #include "path/path.h" /* Action to do after a call to readlink_proc(). */ typedef enum { DEFAULT, /* Nothing special to do, treat it as a regular link. */ CANONICALIZE, /* The symlink was dereferenced, now canonicalize it. */ DONT_CANONICALIZE, /* The symlink shouldn't be dereferenced nor canonicalized. */ } Action; extern Action readlink_proc(const Tracee *tracee, char result[PATH_MAX], const char path[PATH_MAX], const char component[NAME_MAX], Comparison comparison); extern ssize_t readlink_proc2(const Tracee *tracee, char result[PATH_MAX], const char path[PATH_MAX]); #endif /* PROC_H */ proot-5.4.0/src/path/temp.c000066400000000000000000000212711442763353300155340ustar00rootroot00000000000000#include /* stat(2), opendir(3), */ #include /* stat(2), chmod(2), */ #include /* stat(2), rmdir(2), unlink(2), readlink(2), */ #include /* errno(2), */ #include /* readdir(3), opendir(3), */ #include /* strcmp(3), */ #include /* free(3), getenv(3), */ #include /* P_tmpdir, */ #include /* talloc(3), */ #include "cli/note.h" /** * Return the path to a directory where temporary files should be * created. */ const char *get_temp_directory() { static const char *temp_directory = NULL; char *tmp; if (temp_directory != NULL) return temp_directory; temp_directory = getenv("PROOT_TMP_DIR"); if (temp_directory == NULL) { temp_directory = P_tmpdir; return temp_directory; } tmp = realpath(temp_directory, NULL); if (tmp == NULL) { note(NULL, WARNING, SYSTEM, "can't canonicalize %s, using %s instead of PROOT_TMP_DIR", temp_directory, P_tmpdir); temp_directory = P_tmpdir; return temp_directory; } temp_directory = talloc_strdup(talloc_autofree_context(), tmp); if (temp_directory == NULL) temp_directory = tmp; else free(tmp); return temp_directory; } /** * Handle the return of d_type = DT_UNKNOWN by readdir(3) * Not all filesystems support returning d_type in readdir(3) */ static int get_dtype(struct dirent *de) { int dtype = de ? de->d_type : DT_UNKNOWN; struct stat st; if (dtype != DT_UNKNOWN) return dtype; if (lstat(de->d_name, &st)) return dtype; if (S_ISREG(st.st_mode)) return DT_REG; if (S_ISDIR(st.st_mode)) return DT_DIR; if (S_ISLNK(st.st_mode)) return DT_LNK; return dtype; } /** * Remove recursively the content of the current working directory. * This latter has to lie in temp_directory (ie. "/tmp" on most * systems). This function returns -1 if a fatal error occured * (ie. the recursion must be stopped), the number of non-fatal errors * otherwise. * * WARNING: this function changes the current working directory for * the calling process. */ static int clean_temp_cwd() { const char *temp_directory = get_temp_directory(); const size_t length_temp_directory = strlen(temp_directory); char *prefix = NULL; int nb_errors = 0; DIR *dir = NULL; int status; prefix = talloc_size(NULL, length_temp_directory + 1); if (prefix == NULL) { note(NULL, WARNING, INTERNAL, "can't allocate memory"); nb_errors++; goto end; } /* Sanity check: ensure the current directory lies in * "/tmp". */ status = readlink("/proc/self/cwd", prefix, length_temp_directory); if (status < 0) { note(NULL, WARNING, SYSTEM, "can't readlink '/proc/self/cwd'"); nb_errors++; goto end; } prefix[status] = '\0'; if (strncmp(prefix, temp_directory, length_temp_directory) != 0) { note(NULL, ERROR, INTERNAL, "trying to remove a directory outside of '%s', " "please report this error.\n", temp_directory); nb_errors++; goto end; } dir = opendir("."); if (dir == NULL) { note(NULL, WARNING, SYSTEM, "can't open '.'"); nb_errors++; goto end; } while (1) { struct dirent *entry; errno = 0; entry = readdir(dir); if (entry == NULL) break; if ( strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; status = chmod(entry->d_name, 0700); if (status < 0) { note(NULL, WARNING, SYSTEM, "cant chmod '%s'", entry->d_name); nb_errors++; continue; } if (get_dtype(entry) == DT_DIR) { status = chdir(entry->d_name); if (status < 0) { note(NULL, WARNING, SYSTEM, "can't chdir '%s'", entry->d_name); nb_errors++; continue; } /* Recurse. */ status = clean_temp_cwd(); if (status < 0) { nb_errors = -1; goto end; } nb_errors += status; status = chdir(".."); if (status < 0) { note(NULL, ERROR, SYSTEM, "can't chdir to '..'"); nb_errors = -1; goto end; } status = rmdir(entry->d_name); } else { status = unlink(entry->d_name); } if (status < 0) { note(NULL, WARNING, SYSTEM, "can't remove '%s'", entry->d_name); nb_errors++; continue; } } if (errno != 0) { note(NULL, WARNING, SYSTEM, "can't readdir '.'"); nb_errors++; } end: TALLOC_FREE(prefix); if (dir != NULL) (void) closedir(dir); return nb_errors; } /** * Remove recursively @path. This latter has to be a directory lying * in temp_directory (ie. "/tmp" on most systems). This function * returns -1 on error, otherwise 0. */ static int remove_temp_directory2(const char *path) { int result; int status; char *cwd; #ifdef __ANDROID__ cwd = malloc(PATH_MAX); getcwd(cwd, PATH_MAX); #else cwd = get_current_dir_name(); #endif status = chmod(path, 0700); if (status < 0) { note(NULL, ERROR, SYSTEM, "can't chmod '%s'", path); result = -1; goto end; } status = chdir(path); if (status < 0) { note(NULL, ERROR, SYSTEM, "can't chdir to '%s'", path); result = -1; goto end; } status = clean_temp_cwd(); result = (status == 0 ? 0 : -1); /* Try to remove path even if something went wrong. */ status = chdir(".."); if (status < 0) { note(NULL, ERROR, SYSTEM, "can't chdir to '..'"); result = -1; goto end; } status = rmdir(path); if (status < 0) { note(NULL, ERROR, SYSTEM, "cant remove '%s'", path); result = -1; goto end; } end: if (cwd != NULL) { status = chdir(cwd); if (status < 0) { result = -1; note(NULL, ERROR, SYSTEM, "can't chdir to '%s'", cwd); } free(cwd); } return result; } /** * Like remove_temp_directory2() but always return 0. * * Note: this is a talloc destructor. */ static int remove_temp_directory(char *path) { (void) remove_temp_directory2(path); return 0; } /** * Remove the file @path. This function always returns 0. * * Note: this is a talloc destructor. */ static int remove_temp_file(char *path) { int status; status = unlink(path); if (status < 0) note(NULL, ERROR, SYSTEM, "can't remove '%s'", path); return 0; } /** * Create a path name with the following format: * "/tmp/@prefix-$PID-XXXXXX". The returned C string is either * auto-freed if @context is NULL. This function returns NULL if an * error occurred. */ char *create_temp_name(TALLOC_CTX *context, const char *prefix) { const char *temp_directory = get_temp_directory(); char *name; if (context == NULL) context = talloc_autofree_context(); name = talloc_asprintf(context, "%s/%s-%d-XXXXXX", temp_directory, prefix, getpid()); if (name == NULL) { note(NULL, ERROR, INTERNAL, "can't allocate memory"); return NULL; } return name; } /** * Create a directory that will be automatically removed either on * PRoot termination if @context is NULL, or once its path name * (attached to @context) is freed. This function returns NULL on * error, otherwise the absolute path name to the created directory * (@prefix-ed). */ const char *create_temp_directory(TALLOC_CTX *context, const char *prefix) { char *name; name = create_temp_name(context, prefix); if (name == NULL) return NULL; name = mkdtemp(name); if (name == NULL) { note(NULL, ERROR, SYSTEM, "can't create temporary directory"); note(NULL, INFO, USER, "Please set PROOT_TMP_DIR env. variable " "to an alternate location (with write permission)."); return NULL; } talloc_set_destructor(name, remove_temp_directory); return name; } /** * Create a file that will be automatically removed either on PRoot * termination if @context is NULL, or once its path name (attached to * @context) is freed. This function returns NULL on error, * otherwise the absolute path name to the created file (@prefix-ed). */ const char *create_temp_file(TALLOC_CTX *context, const char *prefix) { char *name; int fd; name = create_temp_name(context, prefix); if (name == NULL) return NULL; fd = mkstemp(name); if (fd < 0) { note(NULL, ERROR, SYSTEM, "can't create temporary file"); note(NULL, INFO, USER, "Please set PROOT_TMP_DIR env. variable " "to an alternate location (with write permission)."); return NULL; } close(fd); talloc_set_destructor(name, remove_temp_file); return name; } /** * Like create_temp_file() but returns an open file stream to the * created file. It's up to the caller to close returned stream. */ FILE* open_temp_file(TALLOC_CTX *context, const char *prefix) { char *name; FILE *file; int fd; name = create_temp_name(context, prefix); if (name == NULL) return NULL; fd = mkstemp(name); if (fd < 0) goto error; talloc_set_destructor(name, remove_temp_file); file = fdopen(fd, "w"); if (file == NULL) goto error; return file; error: if (fd >= 0) close(fd); note(NULL, ERROR, SYSTEM, "can't create temporary file"); note(NULL, INFO, USER, "Please set PROOT_TMP_DIR env. variable " "to an alternate location (with write permission)."); return NULL; } proot-5.4.0/src/path/temp.h000066400000000000000000000023701442763353300155400ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef TEMP_H #define TEMP_H #include extern char *create_temp_name(TALLOC_CTX *context, const char *prefix); extern const char *create_temp_directory(TALLOC_CTX *context, const char *prefix); extern const char *create_temp_file(TALLOC_CTX *context, const char *prefix); extern FILE* open_temp_file(TALLOC_CTX *context, const char *prefix); extern const char *get_temp_directory(); #endif /* TEMP_H */ proot-5.4.0/src/ptrace/000077500000000000000000000000001442763353300147425ustar00rootroot00000000000000proot-5.4.0/src/ptrace/ptrace.c000066400000000000000000000416641442763353300163770ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* PTRACE_*, */ #include /* E*, */ #include /* assert(3), */ #include /* bool, true, false, */ #include /* siginfo_t, */ #include /* struct iovec, */ #include /* MIN(), MAX(), */ #include /* memcpy(3), */ #include "ptrace/ptrace.h" #include "ptrace/user.h" #include "tracee/tracee.h" #include "syscall/sysnum.h" #include "tracee/reg.h" #include "tracee/mem.h" #include "tracee/abi.h" #include "tracee/event.h" #include "cli/note.h" #include "arch.h" #include "compat.h" #if defined(ARCH_X86_64) || defined(ARCH_X86) #include /* struct user_desc, */ #endif #if defined(ARCH_X86_64) #include /* ARCH_{G,S}ET_{F,G}S, */ #endif #if defined(ARCH_ARM_EABI) #define user_fpregs_struct user_fpregs #endif #if defined(ARCH_ARM64) #define user_fpregs_struct user_fpsimd_struct #endif static const char *stringify_ptrace(PTRACE_REQUEST_TYPE request) { #define CASE_STR(a) case a: return #a; break; switch ((int) request) { CASE_STR(PTRACE_TRACEME) CASE_STR(PTRACE_PEEKTEXT) CASE_STR(PTRACE_PEEKDATA) CASE_STR(PTRACE_PEEKUSER) CASE_STR(PTRACE_POKETEXT) CASE_STR(PTRACE_POKEDATA) CASE_STR(PTRACE_POKEUSER) CASE_STR(PTRACE_CONT) CASE_STR(PTRACE_KILL) CASE_STR(PTRACE_SINGLESTEP) CASE_STR(PTRACE_GETREGS) CASE_STR(PTRACE_SETREGS) CASE_STR(PTRACE_GETFPREGS) CASE_STR(PTRACE_SETFPREGS) CASE_STR(PTRACE_ATTACH) CASE_STR(PTRACE_DETACH) CASE_STR(PTRACE_GETFPXREGS) CASE_STR(PTRACE_SETFPXREGS) CASE_STR(PTRACE_SYSCALL) CASE_STR(PTRACE_SETOPTIONS) CASE_STR(PTRACE_GETEVENTMSG) CASE_STR(PTRACE_GETSIGINFO) CASE_STR(PTRACE_SETSIGINFO) CASE_STR(PTRACE_GETREGSET) CASE_STR(PTRACE_SETREGSET) CASE_STR(PTRACE_SEIZE) CASE_STR(PTRACE_INTERRUPT) CASE_STR(PTRACE_LISTEN) CASE_STR(PTRACE_SET_SYSCALL) CASE_STR(PTRACE_GET_THREAD_AREA) CASE_STR(PTRACE_SET_THREAD_AREA) CASE_STR(PTRACE_GETVFPREGS) CASE_STR(PTRACE_SINGLEBLOCK) CASE_STR(PTRACE_ARCH_PRCTL) default: return "PTRACE_???"; } } /** * Translate the ptrace syscall made by @tracee into a "void" syscall * in order to emulate the ptrace mechanism within PRoot. This * function returns -errno if an error occured (unsupported request), * otherwise 0. */ int translate_ptrace_enter(Tracee *tracee) { /* The ptrace syscall have to be emulated since it can't be nested. */ set_sysnum(tracee, PR_void); return 0; } /** * Set @ptracee's tracer to @ptracer, and increment ptracees counter * of this later. */ void attach_to_ptracer(Tracee *ptracee, Tracee *ptracer) { bzero(&(PTRACEE), sizeof(PTRACEE)); PTRACEE.ptracer = ptracer; PTRACER.nb_ptracees++; } /** * Unset @ptracee's tracer, and decrement ptracees counter of this * later. */ void detach_from_ptracer(Tracee *ptracee) { Tracee *ptracer = PTRACEE.ptracer; PTRACEE.ptracer = NULL; assert(PTRACER.nb_ptracees > 0); PTRACER.nb_ptracees--; } /** * Emulate the ptrace syscall made by @tracee. This function returns * -errno if an error occured (unsupported request), otherwise 0. */ int translate_ptrace_exit(Tracee *tracee) { word_t request, pid, address, data, result; Tracee *ptracee, *ptracer; int forced_signal = -1; int signal; int status; /* Read ptrace parameters. */ request = peek_reg(tracee, ORIGINAL, SYSARG_1); pid = peek_reg(tracee, ORIGINAL, SYSARG_2); address = peek_reg(tracee, ORIGINAL, SYSARG_3); data = peek_reg(tracee, ORIGINAL, SYSARG_4); /* Propagate signedness for this special value. */ if (is_32on64_mode(tracee) && pid == 0xFFFFFFFF) pid = (word_t) -1; /* The TRACEME request is the only one used by a tracee. */ if (request == PTRACE_TRACEME) { ptracer = tracee->parent; ptracee = tracee; /* The emulated ptrace in PRoot has the same * limitation as the real ptrace in the Linux kernel: * only one tracer per process. */ if (PTRACEE.ptracer != NULL || ptracee == ptracer) return -EPERM; attach_to_ptracer(ptracee, ptracer); /* Detect when the ptracer has gone to wait before the * ptracee did the ptrace(ATTACHME) request. */ if (PTRACER.waits_in == WAITS_IN_KERNEL) { status = kill(ptracer->pid, SIGSTOP); if (status < 0) note(tracee, WARNING, INTERNAL, "can't wake ptracer %d", ptracer->pid); else { ptracer->sigstop = SIGSTOP_IGNORED; PTRACER.waits_in = WAITS_IN_PROOT; } } /* Disable seccomp acceleration for this tracee and * all its children since we can't assume what are the * syscalls its tracer is interested with. */ if (tracee->seccomp == ENABLED) tracee->seccomp = DISABLING; return 0; } /* The ATTACH, SEIZE, and INTERRUPT requests are the only ones * where the ptracee is in an unknown state. */ if (request == PTRACE_ATTACH) { ptracer = tracee; ptracee = get_tracee(ptracer, pid, false); if (ptracee == NULL) return -ESRCH; /* The emulated ptrace in PRoot has the same * limitation as the real ptrace in the Linux kernel: * only one tracer per process. */ if (PTRACEE.ptracer != NULL || ptracee == ptracer) return -EPERM; attach_to_ptracer(ptracee, ptracer); /* The tracee is sent a SIGSTOP, but will not * necessarily have stopped by the completion of this * call. * * -- man 2 ptrace. */ kill(pid, SIGSTOP); return 0; } /* Here, the tracee is a ptracer. Also, the requested ptracee * has to be in the "stopped for ptracer" state. */ ptracer = tracee; ptracee = get_stopped_ptracee(ptracer, pid, false, __WALL); if (ptracee == NULL) { static bool warned = false; /* Ensure we didn't get there only because inheritance * mechanism has missed this one. */ ptracee = get_tracee(tracee, pid, false); if (ptracee != NULL && ptracee->exe == NULL && !warned) { warned = true; note(ptracer, WARNING, INTERNAL, "ptrace request to an unexpected ptracee"); } return -ESRCH; } /* Sanity checks. */ if ( PTRACEE.is_zombie || PTRACEE.ptracer != ptracer || pid == (word_t) -1) return -ESRCH; switch (request) { case PTRACE_SYSCALL: PTRACEE.ignore_syscalls = false; forced_signal = (int) data; status = 0; break; /* Restart the ptracee. */ case PTRACE_CONT: PTRACEE.ignore_syscalls = true; forced_signal = (int) data; status = 0; break; /* Restart the ptracee. */ case PTRACE_SINGLESTEP: ptracee->restart_how = PTRACE_SINGLESTEP; forced_signal = (int) data; status = 0; break; /* Restart the ptracee. */ case PTRACE_SINGLEBLOCK: ptracee->restart_how = PTRACE_SINGLEBLOCK; forced_signal = (int) data; status = 0; break; /* Restart the ptracee. */ case PTRACE_DETACH: detach_from_ptracer(ptracee); status = 0; break; /* Restart the ptracee. */ case PTRACE_KILL: status = ptrace(request, pid, NULL, NULL); break; /* Restart the ptracee. */ case PTRACE_SETOPTIONS: if (data & PTRACE_O_TRACESECCOMP) { /* We don't really support forwarding seccomp traps */ note(ptracer, WARNING, INTERNAL, "ptrace option PTRACE_O_TRACESECCOMP " "not supported yet"); return -EINVAL; } PTRACEE.options = data; return 0; /* Don't restart the ptracee. */ case PTRACE_GETEVENTMSG: { status = ptrace(request, pid, NULL, &result); if (status < 0) return -errno; poke_word(ptracer, data, result); if (errno != 0) return -errno; return 0; /* Don't restart the ptracee. */ } case PTRACE_PEEKUSER: if (is_32on64_mode(ptracer)) { address = convert_user_offset(address); if (address == (word_t) -1) return -EIO; } /* Fall through. */ case PTRACE_PEEKTEXT: case PTRACE_PEEKDATA: errno = 0; result = (word_t) ptrace(request, pid, address, NULL); if (errno != 0) return -errno; poke_word(ptracer, data, result); if (errno != 0) return -errno; return 0; /* Don't restart the ptracee. */ case PTRACE_POKEUSER: if (is_32on64_mode(ptracer)) { address = convert_user_offset(address); if (address == (word_t) -1) return -EIO; } status = ptrace(request, pid, address, data); if (status < 0) return -errno; return 0; /* Don't restart the ptracee. */ case PTRACE_POKETEXT: case PTRACE_POKEDATA: if (is_32on64_mode(ptracer)) { word_t tmp; errno = 0; tmp = (word_t) ptrace(PTRACE_PEEKDATA, ptracee->pid, address, NULL); if (errno != 0) return -errno; data |= (tmp & 0xFFFFFFFF00000000ULL); } status = ptrace(request, pid, address, data); if (status < 0) return -errno; return 0; /* Don't restart the ptracee. */ case PTRACE_GETSIGINFO: { siginfo_t siginfo; status = ptrace(request, pid, NULL, &siginfo); if (status < 0) return -errno; status = write_data(ptracer, data, &siginfo, sizeof(siginfo)); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_SETSIGINFO: { siginfo_t siginfo; status = read_data(ptracer, &siginfo, data, sizeof(siginfo)); if (status < 0) return status; status = ptrace(request, pid, NULL, &siginfo); if (status < 0) return -errno; return 0; /* Don't restart the ptracee. */ } case PTRACE_GETREGS: { size_t size; union { struct user_regs_struct regs; uint32_t regs32[USER32_NB_REGS]; } buffer; status = ptrace(request, pid, NULL, &buffer); if (status < 0) return -errno; if (is_32on64_mode(tracee)) { struct user_regs_struct regs64; memcpy(®s64, &buffer.regs, sizeof(struct user_regs_struct)); convert_user_regs_struct(false, (uint64_t *) ®s64, buffer.regs32); size = sizeof(buffer.regs32); } else size = sizeof(buffer.regs); status = write_data(ptracer, data, &buffer, size); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_SETREGS: { size_t size; union { struct user_regs_struct regs; uint32_t regs32[USER32_NB_REGS]; } buffer; size = (is_32on64_mode(ptracer) ? sizeof(buffer.regs32) : sizeof(buffer.regs)); status = read_data(ptracer, &buffer, data, size); if (status < 0) return status; if (is_32on64_mode(ptracer)) { uint32_t regs32[USER32_NB_REGS]; memcpy(regs32, buffer.regs32, sizeof(regs32)); convert_user_regs_struct(true, (uint64_t *) &buffer.regs, regs32); } status = ptrace(request, pid, NULL, &buffer); if (status < 0) return -errno; return 0; /* Don't restart the ptracee. */ } case PTRACE_GETFPREGS: { size_t size; union { struct user_fpregs_struct fpregs; uint32_t fpregs32[USER32_NB_FPREGS]; } buffer; status = ptrace(request, pid, NULL, &buffer); if (status < 0) return -errno; if (is_32on64_mode(tracee)) { #if 0 /* TODO */ struct user_fpregs_struct fpregs64; memcpy(&fpregs64, &buffer.fpregs, sizeof(struct user_fpregs_struct)); convert_user_fpregs_struct(false, (uint64_t *) &fpregs64, buffer.fpregs32); #else static bool warned = false; if (!warned) note(ptracer, WARNING, INTERNAL, "ptrace 32-bit request '%s' not supported on 64-bit yet", stringify_ptrace(request)); warned = true; bzero(&buffer, sizeof(buffer)); #endif size = sizeof(buffer.fpregs32); } else size = sizeof(buffer.fpregs); status = write_data(ptracer, data, &buffer, size); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_SETFPREGS: { size_t size; union { struct user_fpregs_struct fpregs; uint32_t fpregs32[USER32_NB_FPREGS]; } buffer; size = (is_32on64_mode(ptracer) ? sizeof(buffer.fpregs32) : sizeof(buffer.fpregs)); status = read_data(ptracer, &buffer, data, size); if (status < 0) return status; if (is_32on64_mode(ptracer)) { #if 0 /* TODO */ uint32_t fpregs32[USER32_NB_FPREGS]; memcpy(fpregs32, buffer.fpregs32, sizeof(fpregs32)); convert_user_fpregs_struct(true, (uint64_t *) &buffer.fpregs, fpregs32); #else static bool warned = false; if (!warned) note(ptracer, WARNING, INTERNAL, "ptrace 32-bit request '%s' not supported on 64-bit yet", stringify_ptrace(request)); warned = true; return -ENOTSUP; #endif } status = ptrace(request, pid, NULL, &buffer); if (status < 0) return -errno; return 0; /* Don't restart the ptracee. */ } #if defined(ARCH_X86_64) || defined(ARCH_X86) case PTRACE_GET_THREAD_AREA: { struct user_desc user_desc; status = ptrace(request, pid, address, &user_desc); if (status < 0) return -errno; status = write_data(ptracer, data, &user_desc, sizeof(user_desc)); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_SET_THREAD_AREA: { struct user_desc user_desc; status = read_data(ptracer, &user_desc, data, sizeof(user_desc)); if (status < 0) return status; status = ptrace(request, pid, address, &user_desc); if (status < 0) return -errno; return 0; /* Don't restart the ptracee. */ } #endif case PTRACE_GETREGSET: { struct iovec local_iovec; word_t remote_iovec_base; word_t remote_iovec_len; remote_iovec_base = peek_word(ptracer, data); if (errno != 0) return -errno; remote_iovec_len = peek_word(ptracer, data + sizeof_word(ptracer)); if (errno != 0) return -errno; /* Sanity check. */ assert(sizeof(local_iovec.iov_len) == sizeof(word_t)); local_iovec.iov_len = remote_iovec_len; local_iovec.iov_base = talloc_zero_size(ptracer->ctx, remote_iovec_len); if (local_iovec.iov_base == NULL) return -ENOMEM; status = ptrace(PTRACE_GETREGSET, pid, address, &local_iovec); if (status < 0) return status; remote_iovec_len = local_iovec.iov_len = MIN(remote_iovec_len, local_iovec.iov_len); /* Update remote vector content. */ status = writev_data(ptracer, remote_iovec_base, &local_iovec, 1); if (status < 0) return status; /* Update remote vector length. */ poke_word(ptracer, data + sizeof_word(ptracer), remote_iovec_len); if (errno != 0) return -errno; return 0; /* Don't restart the ptracee. */ } case PTRACE_SETREGSET: { struct iovec local_iovec; word_t remote_iovec_base; word_t remote_iovec_len; remote_iovec_base = peek_word(ptracer, data); if (errno != 0) return -errno; remote_iovec_len = peek_word(ptracer, data + sizeof_word(ptracer)); if (errno != 0) return -errno; /* Sanity check. */ assert(sizeof(local_iovec.iov_len) == sizeof(word_t)); local_iovec.iov_len = remote_iovec_len; local_iovec.iov_base = talloc_zero_size(ptracer->ctx, remote_iovec_len); if (local_iovec.iov_base == NULL) return -ENOMEM; /* Copy remote content into the local vector. */ status = read_data(ptracer, local_iovec.iov_base, remote_iovec_base, local_iovec.iov_len); if (status < 0) return status; status = ptrace(PTRACE_SETREGSET, pid, address, &local_iovec); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_GETVFPREGS: case PTRACE_GETFPXREGS: { static bool warned = false; if (!warned) note(ptracer, WARNING, INTERNAL, "ptrace request '%s' not supported yet", stringify_ptrace(request)); warned = true; return -ENOTSUP; } #if defined(ARCH_X86_64) case PTRACE_ARCH_PRCTL: switch (data) { case ARCH_GET_GS: case ARCH_GET_FS: status = ptrace(request, pid, &result, data); if (status < 0) return -errno; poke_word(ptracer, address, result); if (errno != 0) return -errno; break; case ARCH_SET_GS: case ARCH_SET_FS: { static bool warned = false; if (!warned) note(ptracer, WARNING, INTERNAL, "ptrace request '%s' ARCH_SET_{G,F}S not supported yet", stringify_ptrace(request)); return -ENOTSUP; } default: return -ENOTSUP; } return 0; /* Don't restart the ptracee. */ #endif case PTRACE_SET_SYSCALL: status = ptrace(request, pid, address, data); if (status < 0) return -errno; return 0; /* Don't restart the ptracee. */ default: note(ptracer, WARNING, INTERNAL, "ptrace request '%s' not supported yet", stringify_ptrace(request)); return -ENOTSUP; } /* Now, the initial tracee's event can be handled. */ signal = PTRACEE.event4.proot.pending ? handle_tracee_event(ptracee, PTRACEE.event4.proot.value) : PTRACEE.event4.proot.value; /* The restarting signal from the ptracer overrides the * restarting signal from PRoot. */ if (forced_signal != -1) signal = forced_signal; (void) restart_tracee(ptracee, signal); return status; } proot-5.4.0/src/ptrace/ptrace.h000066400000000000000000000023221442763353300163700ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef PTRACE_H #define PTRACE_H #include "tracee/tracee.h" extern int translate_ptrace_enter(Tracee *tracee); extern int translate_ptrace_exit(Tracee *tracee); extern void attach_to_ptracer(Tracee *ptracee, Tracee *ptracer); extern void detach_from_ptracer(Tracee *ptracee); #define PTRACEE (ptracee->as_ptracee) #define PTRACER (ptracer->as_ptracer) #endif /* PTRACE_H */ proot-5.4.0/src/ptrace/user.c000066400000000000000000000141061442763353300160660ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include #include #include #include #include #include #include "ptrace/user.h" #include "cli/note.h" #if defined(ARCH_X86_64) /** * Return the index in the "regs" field of a 64-bit "user" area that * corresponds to the specified @index in the "regs" field of a 32-bit * "user" area. */ static inline size_t convert_user_regs_index(size_t index) { static size_t mapping[USER32_NB_REGS] = { 05, /* ?bx */ 11, /* ?cx */ 12, /* ?dx */ 13, /* ?si */ 14, /* ?di */ 04, /* ?bp */ 10, /* ?ax */ 23, /* ds */ 24, /* es */ 25, /* fs */ 26, /* gs */ 15, /* orig_?ax */ 16, /* ?ip */ 17, /* cs */ 18, /* eflags */ 19, /* ?sp */ 20, /* ss */ }; /* Sanity check. */ assert(index < USER32_NB_REGS); return mapping[index]; } /* Layout of a 32-bit "user" area. */ #define USER32_REGS_OFFSET 0 #define USER32_REGS_SIZE (USER32_NB_REGS * sizeof(uint32_t)) #define USER32_FPVALID_OFFSET (USER32_REGS_OFFSET + USER32_REGS_SIZE) #define USER32_I387_OFFSET (USER32_FPVALID_OFFSET + sizeof(uint32_t)) #define USER32_I387_SIZE (USER32_NB_FPREGS * sizeof(uint32_t)) #define USER32_TSIZE_OFFSET (USER32_I387_OFFSET + USER32_I387_SIZE) #define USER32_DSIZE_OFFSET (USER32_TSIZE_OFFSET + sizeof(uint32_t)) #define USER32_SSIZE_OFFSET (USER32_DSIZE_OFFSET + sizeof(uint32_t)) #define USER32_START_CODE_OFFSET (USER32_SSIZE_OFFSET + sizeof(uint32_t)) #define USER32_START_STACK_OFFSET (USER32_START_CODE_OFFSET + sizeof(uint32_t)) #define USER32_SIGNAL_OFFSET (USER32_START_STACK_OFFSET + sizeof(uint32_t)) #define USER32_RESERVED_OFFSET (USER32_SIGNAL_OFFSET + sizeof(uint32_t)) #define USER32_AR0_OFFSET (USER32_RESERVED_OFFSET + sizeof(uint32_t)) #define USER32_FPSTATE_OFFSET (USER32_AR0_OFFSET + sizeof(uint32_t)) #define USER32_MAGIC_OFFSET (USER32_FPSTATE_OFFSET + sizeof(uint32_t)) #define USER32_COMM_OFFSET (USER32_MAGIC_OFFSET + sizeof(uint32_t)) #define USER32_COMM_SIZE (32 * sizeof(uint8_t)) #define USER32_DEBUGREG_OFFSET (USER32_COMM_OFFSET + USER32_COMM_SIZE) #define USER32_DEBUGREG_SIZE (8 * sizeof(uint32_t)) /** * Return the offset in the "debugreg" field of a 64-bit "user" area * that corresponds to the specified @offset in the "debugreg" field * of a 32-bit "user" area. */ static inline size_t convert_user_debugreg_offset(size_t offset) { size_t index; /* Sanity check. */ assert(offset >= USER32_DEBUGREG_OFFSET && offset < USER32_DEBUGREG_OFFSET + USER32_DEBUGREG_SIZE); index = (offset - USER32_DEBUGREG_OFFSET) / sizeof(uint32_t); return offsetof(struct user, u_debugreg) + index * sizeof(uint64_t); } /** * Return the offset in a 64-bit "user" area that corresponds to the * specified @offset in a 32-bit "user" area. This function returns * "(word_t) -1" if the specified @offset is invalid. */ word_t convert_user_offset(word_t offset) { const char *area_name = NULL; if (/* offset >= 0 && */ offset < USER32_REGS_OFFSET + USER32_REGS_SIZE) { /* Sanity checks. */ if ((offset % sizeof(uint32_t)) != 0) return (word_t) -1; return convert_user_regs_index(offset / sizeof(uint32_t)) * sizeof(uint64_t); } else if (offset == USER32_FPVALID_OFFSET) area_name = "fpvalid"; /* Not yet supported. */ else if (offset >= USER32_I387_OFFSET && offset < USER32_I387_OFFSET + USER32_I387_SIZE) area_name = "i387"; /* Not yet supported. */ else if (offset == USER32_TSIZE_OFFSET) area_name = "tsize"; /* Not yet supported. */ else if (offset == USER32_DSIZE_OFFSET) area_name = "dsize"; /* Not yet supported. */ else if (offset == USER32_SSIZE_OFFSET) area_name = "ssize"; /* Not yet supported. */ else if (offset == USER32_START_CODE_OFFSET) area_name = "start_code"; /* Not yet supported. */ else if (offset == USER32_START_STACK_OFFSET) area_name = "start_stack"; /* Not yet supported. */ else if (offset == USER32_SIGNAL_OFFSET) area_name = "signal"; /* Not yet supported. */ else if (offset == USER32_RESERVED_OFFSET) area_name = "reserved"; /* Not yet supported. */ else if (offset == USER32_AR0_OFFSET) area_name = "ar0"; /* Not yet supported. */ else if (offset == USER32_FPSTATE_OFFSET) area_name = "fpstate"; /* Not yet supported. */ else if (offset == USER32_MAGIC_OFFSET) area_name = "magic"; /* Not yet supported. */ else if (offset >= USER32_COMM_OFFSET && offset < USER32_COMM_OFFSET + USER32_COMM_SIZE) area_name = "comm"; /* Not yet supported. */ else if (offset >= USER32_DEBUGREG_OFFSET && offset < USER32_DEBUGREG_OFFSET + USER32_DEBUGREG_SIZE) return convert_user_debugreg_offset(offset); else area_name = ""; note(NULL, WARNING, INTERNAL, "ptrace user area '%s' not supported yet", area_name); return (word_t) -1; /* Unknown offset. */ } /** * Convert the "regs" field from a 64-bit "user" area into a "regs" * field from a 32-bit "user" area, or vice versa according to * @reverse. */ void convert_user_regs_struct(bool reverse, uint64_t *user_regs64, uint32_t user_regs32[USER32_NB_REGS]) { size_t index32; for (index32 = 0; index32 < USER32_NB_REGS; index32++) { size_t index64 = convert_user_regs_index(index32); assert(index64 != (size_t) -1); if (reverse) user_regs64[index64] = (uint64_t) user_regs32[index32]; else user_regs32[index32] = (uint32_t) user_regs64[index64]; } } #endif /* ARCH_X86_64 */ proot-5.4.0/src/ptrace/user.h000066400000000000000000000027661442763353300161040ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include #include #include #include "arch.h" #include "attribute.h" #if defined(ARCH_X86_64) #define USER32_NB_REGS 17 #define USER32_NB_FPREGS 27 extern word_t convert_user_offset(word_t offset); extern void convert_user_regs_struct(bool reverse, uint64_t *user_regs64, uint32_t user_regs32[USER32_NB_REGS]); #else #define USER32_NB_REGS 0 #define USER32_NB_FPREGS 0 static inline word_t convert_user_offset(word_t offset UNUSED) { assert(0); } static inline void convert_user_regs_struct(bool reverse UNUSED, uint64_t *user_regs64 UNUSED, uint32_t user_regs32[USER32_NB_REGS] UNUSED) { assert(0); } #endif proot-5.4.0/src/ptrace/wait.c000066400000000000000000000252241442763353300160570ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* PTRACE_*, */ #include /* E*, */ #include /* assert(3), */ #include /* bool, true, false, */ #include /* SIG*, */ #include /* talloc*, */ #include "ptrace/wait.h" #include "ptrace/ptrace.h" #include "syscall/sysnum.h" #include "syscall/chain.h" #include "tracee/tracee.h" #include "tracee/event.h" #include "tracee/reg.h" #include "tracee/mem.h" #include "attribute.h" static const char *stringify_event(int event) UNUSED; static const char *stringify_event(int event) { if (WIFEXITED(event)) return "exited"; else if (WIFSIGNALED(event)) return "signaled"; else if (WIFCONTINUED(event)) return "continued"; else if (WIFSTOPPED(event)) { switch ((event & 0xfff00) >> 8) { case SIGTRAP: return "stopped: SIGTRAP"; case SIGTRAP | 0x80: return "stopped: SIGTRAP: 0x80"; case SIGTRAP | PTRACE_EVENT_VFORK << 8: return "stopped: SIGTRAP: PTRACE_EVENT_VFORK"; case SIGTRAP | PTRACE_EVENT_FORK << 8: return "stopped: SIGTRAP: PTRACE_EVENT_FORK"; case SIGTRAP | PTRACE_EVENT_VFORK_DONE << 8: return "stopped: SIGTRAP: PTRACE_EVENT_VFORK_DONE"; case SIGTRAP | PTRACE_EVENT_CLONE << 8: return "stopped: SIGTRAP: PTRACE_EVENT_CLONE"; case SIGTRAP | PTRACE_EVENT_EXEC << 8: return "stopped: SIGTRAP: PTRACE_EVENT_EXEC"; case SIGTRAP | PTRACE_EVENT_EXIT << 8: return "stopped: SIGTRAP: PTRACE_EVENT_EXIT"; case SIGTRAP | PTRACE_EVENT_SECCOMP2 << 8: return "stopped: SIGTRAP: PTRACE_EVENT_SECCOMP2"; case SIGTRAP | PTRACE_EVENT_SECCOMP << 8: return "stopped: SIGTRAP: PTRACE_EVENT_SECCOMP"; case SIGSTOP: return "stopped: SIGSTOP"; default: return "stopped: unknown"; } } return "unknown"; } /** * Translate the wait syscall made by @ptracer into a "void" syscall * if the expected pid is one of its ptracees, in order to emulate the * ptrace mechanism within PRoot. This function returns -errno if an * error occured (unsupported request), otherwise 0. */ int translate_wait_enter(Tracee *ptracer) { Tracee *ptracee; pid_t pid; PTRACER.waits_in = WAITS_IN_KERNEL; /* Don't emulate the ptrace mechanism if it's not a ptracer. */ if (PTRACER.nb_ptracees == 0) return 0; /* Don't emulate the ptrace mechanism if the requested pid is * not a ptracee. */ pid = (pid_t) peek_reg(ptracer, ORIGINAL, SYSARG_1); if (pid != -1) { ptracee = get_ptracee(ptracer, pid, false, true, peek_reg(ptracer, ORIGINAL, SYSARG_3)); if (ptracee == NULL || PTRACEE.ptracer != ptracer) return 0; } /* This syscall is canceled at the enter stage in order to be * handled at the exit stage. */ set_sysnum(ptracer, PR_void); PTRACER.waits_in = WAITS_IN_PROOT; return 0; } /** * Update pid & wait status of @ptracer's wait(2) for the given * @ptracee. This function returns -errno if an error occurred, 0 if * the wait syscall will be restarted (ie. the event is discarded), * otherwise @ptracee's pid. */ static int update_wait_status(Tracee *ptracer, Tracee *ptracee) { word_t address; int result; /* Special case: the Linux kernel reports the terminating * event issued by a process to both its parent and its * tracer, except when they are the same. In this case the * Linux kernel reports the terminating event only once to the * tracing parent ... */ if (PTRACEE.ptracer == ptracee->parent && (WIFEXITED(PTRACEE.event4.ptracer.value) || WIFSIGNALED(PTRACEE.event4.ptracer.value))) { /* ... So hide this terminating event (toward its * tracer, ie. PRoot) and make the second one appear * (towards its parent, ie. the ptracer). This will * ensure its exit status is collected from a kernel * point-of-view (ie. it doesn't stay a zombie * forever). */ restart_original_syscall(ptracer); /* Detach this ptracee from its ptracer, PRoot doesn't * have anything else to emulate. */ detach_from_ptracer(ptracee); /* Zombies can rest in peace once the ptracer is * notified. */ if (PTRACEE.is_zombie) TALLOC_FREE(ptracee); return 0; } address = peek_reg(ptracer, ORIGINAL, SYSARG_2); if (address != 0) { poke_int32(ptracer, address, PTRACEE.event4.ptracer.value); if (errno != 0) return -errno; } PTRACEE.event4.ptracer.pending = false; /* Be careful; ptracee might get freed before its pid is * returned. */ result = ptracee->pid; /* Zombies can rest in peace once the ptracer is notified. */ if (PTRACEE.is_zombie) { detach_from_ptracer(ptracee); TALLOC_FREE(ptracee); } return result; } /** * Emulate the wait* syscall made by @ptracer if it was in the context * of the ptrace mechanism. This function returns -errno if an error * occured, otherwise the pid of the expected tracee. */ int translate_wait_exit(Tracee *ptracer, bool *set_result) { Tracee *ptracee; word_t options; int status; pid_t pid; *set_result = true; assert(PTRACER.waits_in == WAITS_IN_PROOT); PTRACER.waits_in = DOESNT_WAIT; pid = (pid_t) peek_reg(ptracer, ORIGINAL, SYSARG_1); options = peek_reg(ptracer, ORIGINAL, SYSARG_3); /* Is there such a stopped ptracee with an event not yet * passed to its ptracer? */ ptracee = get_stopped_ptracee(ptracer, pid, true, options); if (ptracee == NULL) { /* Is there still living ptracees? */ if (PTRACER.nb_ptracees == 0) return -ECHILD; /* Non blocking wait(2) ? */ if ((options & WNOHANG) != 0) { /* if WNOHANG was specified and one or more * child(ren) specified by pid exist, but have * not yet changed state, then 0 is returned. * On error, -1 is returned. * * -- man 2 waitpid */ return (has_ptracees(ptracer, pid, options) ? 0 : -ECHILD); } /* Otherwise put this ptracer in the "waiting for * ptracee" state, it will be woken up in * handle_ptracee_event() later. */ PTRACER.wait_pid = pid; PTRACER.wait_options = options; return 0; } status = update_wait_status(ptracer, ptracee); // If the syscall is restarted, don't touch the result. // Not only is it unnecessary, it could overwrite syscall argument on ARM. if (status == 0) *set_result = false; return status; } /** * For the given @ptracee, pass its current @event to its ptracer if * this latter is waiting for it, otherwise put the @ptracee in the * "waiting for ptracer" state. This function returns whether * @ptracee shall be kept in the stop state or not. */ bool handle_ptracee_event(Tracee *ptracee, int event) { bool handled_by_proot_first = false; Tracee *ptracer = PTRACEE.ptracer; bool keep_stopped; assert(ptracer != NULL); /* Remember what the event initially was, this will be * required by PRoot to handle this event later. */ PTRACEE.event4.proot.value = event; PTRACEE.event4.proot.pending = true; /* By default, this ptracee should be kept stopped until its * ptracer restarts it. */ keep_stopped = true; /* Not all events are expected for this ptracee. */ if (WIFSTOPPED(event)) { switch ((event & 0xfff00) >> 8) { case SIGTRAP | 0x80: if (PTRACEE.ignore_syscalls || PTRACEE.ignore_loader_syscalls) return false; if ((PTRACEE.options & PTRACE_O_TRACESYSGOOD) == 0) event &= ~(0x80 << 8); handled_by_proot_first = IS_IN_SYSEXIT(ptracee); break; #define PTRACE_EVENT_VFORKDONE PTRACE_EVENT_VFORK_DONE #define CASE_FILTER_EVENT(name) \ case SIGTRAP | PTRACE_EVENT_ ##name << 8: \ if ((PTRACEE.options & PTRACE_O_TRACE ##name) == 0) \ return false; \ PTRACEE.tracing_started = true; \ handled_by_proot_first = true; \ break; CASE_FILTER_EVENT(FORK); CASE_FILTER_EVENT(VFORK); CASE_FILTER_EVENT(VFORKDONE); CASE_FILTER_EVENT(CLONE); CASE_FILTER_EVENT(EXIT); CASE_FILTER_EVENT(EXEC); /* Never reached. */ assert(0); case SIGTRAP | PTRACE_EVENT_SECCOMP2 << 8: case SIGTRAP | PTRACE_EVENT_SECCOMP << 8: /* These events are not supported [yet?] under * ptrace emulation. */ return false; default: PTRACEE.tracing_started = true; break; } } /* In these cases, the ptracee isn't really alive anymore. To * ensure it will not be in limbo, PRoot restarts it whether * its ptracer is waiting for it or not. */ else if (WIFEXITED(event) || WIFSIGNALED(event)) { PTRACEE.tracing_started = true; keep_stopped = false; } /* A process is not traced right from the TRACEME request; it * is traced from the first received signal, whether it was * raised by the process itself (implicitly or explicitly), or * it was induced by a PTRACE_EVENT_*. */ if (!PTRACEE.tracing_started) return false; /* Under some circumstances, the event must be handled by * PRoot first. */ if (handled_by_proot_first) { int signal; signal = handle_tracee_event(ptracee, PTRACEE.event4.proot.value); PTRACEE.event4.proot.value = signal; /* The computed signal is always 0 since we can come * in this block only on sysexit and special events * (as for now). */ assert(signal == 0); } /* Remember what the new event is, this will be required by the ptracer in translate_ptrace_exit() in order to restart this ptracee. */ PTRACEE.event4.ptracer.value = event; PTRACEE.event4.ptracer.pending = true; /* Notify asynchronously the ptracer about this event, as the * kernel does. */ kill(ptracer->pid, SIGCHLD); /* Note: wait_pid is set in translate_wait_exit() if no * ptracee event was pending when the ptracer started to * wait. */ if ( (PTRACER.wait_pid == -1 || PTRACER.wait_pid == ptracee->pid) && EXPECTED_WAIT_CLONE(PTRACER.wait_options, ptracee)) { bool restarted; int status; status = update_wait_status(ptracer, ptracee); if (status != 0) poke_reg(ptracer, SYSARG_RESULT, (word_t) status); /* Write ptracer's register cache back. */ (void) push_regs(ptracer); /* Restart the ptracer. */ PTRACER.wait_pid = 0; restarted = restart_tracee(ptracer, 0); if (!restarted) keep_stopped = false; return keep_stopped; } return keep_stopped; } proot-5.4.0/src/ptrace/wait.h000066400000000000000000000033361442763353300160640ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef PTRACE_WAIT_H #define PTRACE_WAIT_H #include "tracee/tracee.h" extern int translate_wait_enter(Tracee *ptracer); extern int translate_wait_exit(Tracee *ptracer, bool *set_result); extern bool handle_ptracee_event(Tracee *ptracee, int wait_status); /* __WCLONE: Wait for "clone" children only. If omitted then wait for * "non-clone" children only. (A "clone" child is one which delivers * no signal, or a signal other than SIGCHLD to its parent upon * termination.) This option is ignored if __WALL is also specified. * * __WALL: Wait for all children, regardless of type ("clone" or * "non-clone"). * * -- wait(2) man-page */ #define EXPECTED_WAIT_CLONE(wait_options, tracee) \ ((((wait_options) & __WALL) != 0) \ || ((((wait_options) & __WCLONE) != 0) && (tracee)->clone) \ || ((((wait_options) & __WCLONE) == 0) && !(tracee)->clone)) #endif /* PTRACE_WAIT_H */ proot-5.4.0/src/syscall/000077500000000000000000000000001442763353300151365ustar00rootroot00000000000000proot-5.4.0/src/syscall/chain.c000066400000000000000000000122051442763353300163640ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* talloc*, */ #include /* STAILQ_*, */ #include /* E*, */ #include /* assert(3), */ #include "syscall/chain.h" #include "syscall/sysnum.h" #include "tracee/tracee.h" #include "tracee/reg.h" #include "arch.h" struct chained_syscall { Sysnum sysnum; word_t sysargs[6]; STAILQ_ENTRY(chained_syscall) link; }; STAILQ_HEAD(chained_syscalls, chained_syscall); /** * Append a new syscall (@sysnum, @sysarg_*) to the list of * "unrequested" syscalls for the given @tracee. These new syscalls * will be triggered in order once the current syscall is done. The * caller is free to force the last result of this syscall chain in * @tracee->chain.final_result. This function returns -errno if an * error occurred, otherwise 0. */ int register_chained_syscall(Tracee *tracee, Sysnum sysnum, word_t sysarg_1, word_t sysarg_2, word_t sysarg_3, word_t sysarg_4, word_t sysarg_5, word_t sysarg_6) { struct chained_syscall *syscall; if (tracee->chain.syscalls == NULL) { tracee->chain.syscalls = talloc_zero(tracee, struct chained_syscalls); if (tracee->chain.syscalls == NULL) return -ENOMEM; STAILQ_INIT(tracee->chain.syscalls); } syscall = talloc_zero(tracee->chain.syscalls, struct chained_syscall); if (syscall == NULL) return -ENOMEM; syscall->sysnum = sysnum; syscall->sysargs[0] = sysarg_1; syscall->sysargs[1] = sysarg_2; syscall->sysargs[2] = sysarg_3; syscall->sysargs[3] = sysarg_4; syscall->sysargs[4] = sysarg_5; syscall->sysargs[5] = sysarg_6; STAILQ_INSERT_TAIL(tracee->chain.syscalls, syscall, link); return 0; } /** * Use/remove the first element of @tracee->chain.syscalls to forge a * new syscall. This function should be called only at the end of in * the sysexit stage. */ void chain_next_syscall(Tracee *tracee) { struct chained_syscall *syscall; word_t instr_pointer; word_t sysnum; assert(tracee->chain.syscalls != NULL); /* No more chained syscalls: force the result of the initial * syscall (the one explicitly requested by the tracee). */ if (STAILQ_EMPTY(tracee->chain.syscalls)) { TALLOC_FREE(tracee->chain.syscalls); if (tracee->chain.force_final_result) poke_reg(tracee, SYSARG_RESULT, tracee->chain.final_result); tracee->chain.force_final_result = false; tracee->chain.final_result = 0; return; } /* Original register values will be restored right after the * last chained syscall. */ tracee->restore_original_regs = false; /* The list of chained syscalls is a FIFO. */ syscall = STAILQ_FIRST(tracee->chain.syscalls); STAILQ_REMOVE_HEAD(tracee->chain.syscalls, link); poke_reg(tracee, SYSARG_1, syscall->sysargs[0]); poke_reg(tracee, SYSARG_2, syscall->sysargs[1]); poke_reg(tracee, SYSARG_3, syscall->sysargs[2]); poke_reg(tracee, SYSARG_4, syscall->sysargs[3]); poke_reg(tracee, SYSARG_5, syscall->sysargs[4]); poke_reg(tracee, SYSARG_6, syscall->sysargs[5]); sysnum = detranslate_sysnum(get_abi(tracee), syscall->sysnum); poke_reg(tracee, SYSTRAP_NUM, sysnum); /* Move the instruction pointer back to the original trap. */ instr_pointer = peek_reg(tracee, CURRENT, INSTR_POINTER); poke_reg(tracee, INSTR_POINTER, instr_pointer - SYSTRAP_SIZE); } /** * Force the last result of the @tracee's current syscall chain to be * @forced_result. */ void force_chain_final_result(Tracee *tracee, word_t forced_result) { tracee->chain.force_final_result = true; tracee->chain.final_result = forced_result; } /** * Restart the original syscall of the given @tracee. The result of * the current syscall will be overwritten. This function returns the * same status as register_chained_syscall(). */ int restart_original_syscall(Tracee *tracee) { poke_reg(tracee, SYSARG_1, peek_reg(tracee, ORIGINAL, SYSARG_1)); poke_reg(tracee, SYSARG_2, peek_reg(tracee, ORIGINAL, SYSARG_2)); poke_reg(tracee, SYSARG_3, peek_reg(tracee, ORIGINAL, SYSARG_3)); poke_reg(tracee, SYSARG_4, peek_reg(tracee, ORIGINAL, SYSARG_4)); poke_reg(tracee, SYSARG_5, peek_reg(tracee, ORIGINAL, SYSARG_5)); poke_reg(tracee, SYSARG_6, peek_reg(tracee, ORIGINAL, SYSARG_6)); poke_reg(tracee, SYSTRAP_NUM, peek_reg(tracee, ORIGINAL, SYSARG_NUM)); /* Move the instruction pointer back to the original trap. */ poke_reg(tracee, INSTR_POINTER, peek_reg(tracee, CURRENT, INSTR_POINTER) - SYSTRAP_SIZE); return 0; } proot-5.4.0/src/syscall/chain.h000066400000000000000000000024751442763353300164010ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef CHAIN_H #define CHAIN_H #include "tracee/tracee.h" #include "syscall/sysnum.h" #include "arch.h" extern int register_chained_syscall(Tracee *tracee, Sysnum sysnum, word_t sysarg_1, word_t sysarg_2, word_t sysarg_3, word_t sysarg_4, word_t sysarg_5, word_t sysarg_6); extern void force_chain_final_result(Tracee *tracee, word_t forced_result); extern int restart_original_syscall(Tracee *tracee); extern void chain_next_syscall(Tracee *tracee); #endif /* CHAIN_H */ proot-5.4.0/src/syscall/enter.c000066400000000000000000000340141442763353300164210ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* errno(3), E* */ #include /* talloc_*, */ #include /* struct sockaddr_un, */ #include /* SYS_*, */ #include /* AT_FDCWD, */ #include /* PATH_MAX, */ #include /* strcpy */ #include /* PR_SET_DUMPABLE */ #include "syscall/syscall.h" #include "syscall/sysnum.h" #include "syscall/socket.h" #include "ptrace/ptrace.h" #include "ptrace/wait.h" #include "syscall/heap.h" #include "extension/extension.h" #include "execve/execve.h" #include "tracee/tracee.h" #include "tracee/reg.h" #include "tracee/mem.h" #include "tracee/abi.h" #include "path/path.h" #include "path/canon.h" #include "arch.h" /** * Translate @path and put the result in the @tracee's memory address * space pointed to by the @reg argument of the current syscall. See * the documentation of translate_path() about the meaning of * @type. This function returns -errno if an error occured, otherwise * 0. */ static int translate_path2(Tracee *tracee, int dir_fd, char path[PATH_MAX], Reg reg, Type type) { char new_path[PATH_MAX]; int status; /* Special case where the argument was NULL. */ if (path[0] == '\0') return 0; /* Translate the original path. */ status = translate_path(tracee, new_path, dir_fd, path, type != SYMLINK); if (status < 0) return status; return set_sysarg_path(tracee, new_path, reg); } /** * A helper, see the comment of the function above. */ static int translate_sysarg(Tracee *tracee, Reg reg, Type type) { char old_path[PATH_MAX]; int status; /* Extract the original path. */ status = get_sysarg_path(tracee, old_path, reg); if (status < 0) return status; return translate_path2(tracee, AT_FDCWD, old_path, reg, type); } /** * Translate the input arguments of the current @tracee's syscall in the * @tracee->pid process area. This function sets @tracee->status to * -errno if an error occured from the tracee's point-of-view (EFAULT * for instance), otherwise 0. */ int translate_syscall_enter(Tracee *tracee) { int flags; int dirfd; int olddirfd; int newdirfd; int status; int status2; char path[PATH_MAX]; char oldpath[PATH_MAX]; char newpath[PATH_MAX]; word_t syscall_number; bool special = false; status = notify_extensions(tracee, SYSCALL_ENTER_START, 0, 0); if (status < 0) goto end; if (status > 0) return 0; /* Translate input arguments. */ syscall_number = get_sysnum(tracee, ORIGINAL); switch (syscall_number) { default: /* Nothing to do. */ status = 0; break; case PR_execve: status = translate_execve_enter(tracee); break; case PR_ptrace: status = translate_ptrace_enter(tracee); break; case PR_wait4: case PR_waitpid: status = translate_wait_enter(tracee); break; case PR_brk: translate_brk_enter(tracee); status = 0; break; case PR_getcwd: set_sysnum(tracee, PR_void); status = 0; break; case PR_fchdir: case PR_chdir: { struct stat statl; char *tmp; /* The ending "." ensures an error will be reported if * path does not exist or if it is not a directory. */ if (syscall_number == PR_chdir) { status = get_sysarg_path(tracee, path, SYSARG_1); if (status < 0) break; status = join_paths(2, oldpath, path, "."); if (status < 0) break; dirfd = AT_FDCWD; } else { strcpy(oldpath, "."); dirfd = peek_reg(tracee, CURRENT, SYSARG_1); } status = translate_path(tracee, path, dirfd, oldpath, true); if (status < 0) break; status = lstat(path, &statl); if (status < 0) break; /* Check this directory is accessible. */ if ((statl.st_mode & S_IXUSR) == 0) return -EACCES; /* Sadly this method doesn't detranslate statefully, * this means that there's an ambiguity when several * bindings are from the same host path: * * $ proot -m /tmp:/a -m /tmp:/b fchdir_getcwd /a * /b * * $ proot -m /tmp:/b -m /tmp:/a fchdir_getcwd /a * /a * * A solution would be to follow each file descriptor * just like it is done for cwd. */ status = detranslate_path(tracee, path, NULL); if (status < 0) break; /* Remove the trailing "/" or "/.". */ chop_finality(path); tmp = talloc_strdup(tracee->fs, path); if (tmp == NULL) { status = -ENOMEM; break; } TALLOC_FREE(tracee->fs->cwd); tracee->fs->cwd = tmp; talloc_set_name_const(tracee->fs->cwd, "$cwd"); set_sysnum(tracee, PR_void); status = 0; break; } case PR_bind: case PR_connect: { word_t address; word_t size; address = peek_reg(tracee, CURRENT, SYSARG_2); size = peek_reg(tracee, CURRENT, SYSARG_3); status = translate_socketcall_enter(tracee, &address, size); if (status <= 0) break; poke_reg(tracee, SYSARG_2, address); poke_reg(tracee, SYSARG_3, sizeof(struct sockaddr_un)); status = 0; break; } #define SYSARG_ADDR(n) (args_addr + ((n) - 1) * sizeof_word(tracee)) #define PEEK_WORD(addr, forced_errno) \ peek_word(tracee, addr); \ if (errno != 0) { \ status = forced_errno ?: -errno; \ break; \ } #define POKE_WORD(addr, value) \ poke_word(tracee, addr, value); \ if (errno != 0) { \ status = -errno; \ break; \ } case PR_accept: case PR_accept4: /* Nothing special to do if no sockaddr was specified. */ if (peek_reg(tracee, ORIGINAL, SYSARG_2) == 0) { status = 0; break; } special = true; /* Fall through. */ case PR_getsockname: case PR_getpeername:{ int size; /* Remember: PEEK_WORD puts -errno in status and breaks if an * error occured. */ size = (int) PEEK_WORD(peek_reg(tracee, ORIGINAL, SYSARG_3), special ? -EINVAL : 0); /* The "size" argument is both used as an input parameter * (max. size) and as an output parameter (actual size). The * exit stage needs to know the max. size to not overwrite * anything, that's why it is copied in the 6th argument * (unused) before the kernel updates it. */ poke_reg(tracee, SYSARG_6, size); status = 0; break; } case PR_socketcall: { word_t args_addr; word_t sock_addr_saved; word_t sock_addr; word_t size_addr; word_t size; args_addr = peek_reg(tracee, CURRENT, SYSARG_2); switch (peek_reg(tracee, CURRENT, SYSARG_1)) { case SYS_BIND: case SYS_CONNECT: /* Handle these cases below. */ status = 1; break; case SYS_ACCEPT: case SYS_ACCEPT4: /* Nothing special to do if no sockaddr was specified. */ sock_addr = PEEK_WORD(SYSARG_ADDR(2), 0); if (sock_addr == 0) { status = 0; break; } special = true; /* Fall through. */ case SYS_GETSOCKNAME: case SYS_GETPEERNAME: /* Remember: PEEK_WORD puts -errno in status and breaks * if an error occured. */ size_addr = PEEK_WORD(SYSARG_ADDR(3), 0); size = (int) PEEK_WORD(size_addr, special ? -EINVAL : 0); /* See case PR_accept for explanation. */ poke_reg(tracee, SYSARG_6, size); status = 0; break; default: status = 0; break; } /* An error occured or there's nothing else to do. */ if (status <= 0) break; /* Remember: PEEK_WORD puts -errno in status and breaks if an * error occured. */ sock_addr = PEEK_WORD(SYSARG_ADDR(2), 0); size = PEEK_WORD(SYSARG_ADDR(3), 0); sock_addr_saved = sock_addr; status = translate_socketcall_enter(tracee, &sock_addr, size); if (status <= 0) break; /* These parameters are used/restored at the exit stage. */ poke_reg(tracee, SYSARG_5, sock_addr_saved); poke_reg(tracee, SYSARG_6, size); /* Remember: POKE_WORD puts -errno in status and breaks if an * error occured. */ POKE_WORD(SYSARG_ADDR(2), sock_addr); POKE_WORD(SYSARG_ADDR(3), sizeof(struct sockaddr_un)); status = 0; break; } #undef SYSARG_ADDR #undef PEEK_WORD #undef POKE_WORD case PR_access: case PR_acct: case PR_chmod: case PR_chown: case PR_chown32: case PR_chroot: case PR_getxattr: case PR_listxattr: case PR_mknod: case PR_oldstat: case PR_creat: case PR_removexattr: case PR_setxattr: case PR_stat: case PR_stat64: case PR_statfs: case PR_statfs64: case PR_swapoff: case PR_swapon: case PR_truncate: case PR_truncate64: case PR_umount: case PR_umount2: case PR_uselib: case PR_utime: case PR_utimes: status = translate_sysarg(tracee, SYSARG_1, REGULAR); break; case PR_open: flags = peek_reg(tracee, CURRENT, SYSARG_2); if ( ((flags & O_NOFOLLOW) != 0) || ((flags & O_EXCL) != 0 && (flags & O_CREAT) != 0)) status = translate_sysarg(tracee, SYSARG_1, SYMLINK); else status = translate_sysarg(tracee, SYSARG_1, REGULAR); break; case PR_fchownat: case PR_fstatat64: case PR_newfstatat: case PR_statx: case PR_utimensat: case PR_utimensat_time64: case PR_name_to_handle_at: dirfd = peek_reg(tracee, CURRENT, SYSARG_1); status = get_sysarg_path(tracee, path, SYSARG_2); if (status < 0) break; flags = ( syscall_number == PR_fchownat || syscall_number == PR_name_to_handle_at) ? peek_reg(tracee, CURRENT, SYSARG_5) : ((syscall_number == PR_statx) ? peek_reg(tracee, CURRENT, SYSARG_3) : peek_reg(tracee, CURRENT, SYSARG_4)); if ((flags & AT_SYMLINK_NOFOLLOW) != 0) status = translate_path2(tracee, dirfd, path, SYSARG_2, SYMLINK); else status = translate_path2(tracee, dirfd, path, SYSARG_2, REGULAR); break; case PR_fchmodat: case PR_faccessat: case PR_faccessat2: case PR_futimesat: case PR_mknodat: dirfd = peek_reg(tracee, CURRENT, SYSARG_1); status = get_sysarg_path(tracee, path, SYSARG_2); if (status < 0) break; status = translate_path2(tracee, dirfd, path, SYSARG_2, REGULAR); break; case PR_inotify_add_watch: flags = peek_reg(tracee, CURRENT, SYSARG_3); if ((flags & IN_DONT_FOLLOW) != 0) status = translate_sysarg(tracee, SYSARG_2, SYMLINK); else status = translate_sysarg(tracee, SYSARG_2, REGULAR); break; case PR_readlink: case PR_lchown: case PR_lchown32: case PR_lgetxattr: case PR_llistxattr: case PR_lremovexattr: case PR_lsetxattr: case PR_lstat: case PR_lstat64: case PR_oldlstat: case PR_unlink: case PR_rmdir: case PR_mkdir: status = translate_sysarg(tracee, SYSARG_1, SYMLINK); break; case PR_pivot_root: status = translate_sysarg(tracee, SYSARG_1, REGULAR); if (status < 0) break; status = translate_sysarg(tracee, SYSARG_2, REGULAR); break; case PR_linkat: olddirfd = peek_reg(tracee, CURRENT, SYSARG_1); newdirfd = peek_reg(tracee, CURRENT, SYSARG_3); flags = peek_reg(tracee, CURRENT, SYSARG_5); status = get_sysarg_path(tracee, oldpath, SYSARG_2); if (status < 0) break; status = get_sysarg_path(tracee, newpath, SYSARG_4); if (status < 0) break; if ((flags & AT_SYMLINK_FOLLOW) != 0) status = translate_path2(tracee, olddirfd, oldpath, SYSARG_2, REGULAR); else status = translate_path2(tracee, olddirfd, oldpath, SYSARG_2, SYMLINK); if (status < 0) break; status = translate_path2(tracee, newdirfd, newpath, SYSARG_4, SYMLINK); break; case PR_mount: status = get_sysarg_path(tracee, path, SYSARG_1); if (status < 0) break; /* The following check covers only 90% of the cases. */ if (path[0] == '/' || path[0] == '.') { status = translate_path2(tracee, AT_FDCWD, path, SYSARG_1, REGULAR); if (status < 0) break; } status = translate_sysarg(tracee, SYSARG_2, REGULAR); break; case PR_openat: dirfd = peek_reg(tracee, CURRENT, SYSARG_1); flags = peek_reg(tracee, CURRENT, SYSARG_3); status = get_sysarg_path(tracee, path, SYSARG_2); if (status < 0) break; if ( ((flags & O_NOFOLLOW) != 0) || ((flags & O_EXCL) != 0 && (flags & O_CREAT) != 0)) status = translate_path2(tracee, dirfd, path, SYSARG_2, SYMLINK); else status = translate_path2(tracee, dirfd, path, SYSARG_2, REGULAR); break; case PR_readlinkat: case PR_unlinkat: case PR_mkdirat: dirfd = peek_reg(tracee, CURRENT, SYSARG_1); status = get_sysarg_path(tracee, path, SYSARG_2); if (status < 0) break; status = translate_path2(tracee, dirfd, path, SYSARG_2, SYMLINK); break; case PR_link: case PR_rename: status = translate_sysarg(tracee, SYSARG_1, SYMLINK); if (status < 0) break; status = translate_sysarg(tracee, SYSARG_2, SYMLINK); break; case PR_renameat: case PR_renameat2: olddirfd = peek_reg(tracee, CURRENT, SYSARG_1); newdirfd = peek_reg(tracee, CURRENT, SYSARG_3); status = get_sysarg_path(tracee, oldpath, SYSARG_2); if (status < 0) break; status = get_sysarg_path(tracee, newpath, SYSARG_4); if (status < 0) break; status = translate_path2(tracee, olddirfd, oldpath, SYSARG_2, SYMLINK); if (status < 0) break; status = translate_path2(tracee, newdirfd, newpath, SYSARG_4, SYMLINK); break; case PR_symlink: status = translate_sysarg(tracee, SYSARG_2, SYMLINK); break; case PR_symlinkat: newdirfd = peek_reg(tracee, CURRENT, SYSARG_2); status = get_sysarg_path(tracee, newpath, SYSARG_3); if (status < 0) break; status = translate_path2(tracee, newdirfd, newpath, SYSARG_3, SYMLINK); break; case PR_prctl: /* Prevent tracees from setting dumpable flag. * (Otherwise it could break tracee memory access) */ if (peek_reg(tracee, CURRENT, SYSARG_1) == PR_SET_DUMPABLE) { set_sysnum(tracee, PR_void); status = 0; } break; } end: status2 = notify_extensions(tracee, SYSCALL_ENTER_END, status, 0); if (status2 < 0) status = status2; return status; } proot-5.4.0/src/syscall/exit.c000066400000000000000000000265701442763353300162650ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* errno(3), E* */ #include /* struct utsname, */ #include /* SYS_*, */ #include /* strlen(3), */ #include "syscall/syscall.h" #include "syscall/sysnum.h" #include "syscall/socket.h" #include "syscall/chain.h" #include "syscall/heap.h" #include "syscall/rlimit.h" #include "execve/execve.h" #include "tracee/tracee.h" #include "tracee/reg.h" #include "tracee/mem.h" #include "tracee/abi.h" #include "path/path.h" #include "ptrace/ptrace.h" #include "ptrace/wait.h" #include "extension/extension.h" #include "arch.h" /** * Translate the output arguments of the current @tracee's syscall in * the @tracee->pid process area. This function sets the result of * this syscall to @tracee->status if an error occured previously * during the translation, that is, if @tracee->status is less than 0. */ void translate_syscall_exit(Tracee *tracee) { word_t syscall_number; word_t syscall_result; int status; status = notify_extensions(tracee, SYSCALL_EXIT_START, 0, 0); if (status < 0) { poke_reg(tracee, SYSARG_RESULT, (word_t) status); goto end; } if (status > 0) return; /* Set the tracee's errno if an error occured previously during * the translation. */ if (tracee->status < 0) { poke_reg(tracee, SYSARG_RESULT, (word_t) tracee->status); goto end; } /* Translate output arguments: * - break: update the syscall result register with "status" * - goto end: nothing else to do. */ syscall_number = get_sysnum(tracee, ORIGINAL); syscall_result = peek_reg(tracee, CURRENT, SYSARG_RESULT); switch (syscall_number) { case PR_brk: translate_brk_exit(tracee); goto end; case PR_getcwd: { char path[PATH_MAX]; size_t new_size; size_t size; word_t output; size = (size_t) peek_reg(tracee, ORIGINAL, SYSARG_2); if (size == 0) { status = -EINVAL; break; } /* Ensure cwd still exists. */ status = translate_path(tracee, path, AT_FDCWD, ".", false); if (status < 0) break; new_size = strlen(tracee->fs->cwd) + 1; if (size < new_size) { status = -ERANGE; break; } /* Overwrite the path. */ output = peek_reg(tracee, ORIGINAL, SYSARG_1); status = write_data(tracee, output, tracee->fs->cwd, new_size); if (status < 0) break; /* The value of "status" is used to update the returned value * in translate_syscall_exit(). */ status = new_size; break; } case PR_accept: case PR_accept4: /* Nothing special to do if no sockaddr was specified. */ if (peek_reg(tracee, ORIGINAL, SYSARG_2) == 0) goto end; /* Fall through. */ case PR_getsockname: case PR_getpeername: { word_t sock_addr; word_t size_addr; word_t max_size; /* Error reported by the kernel. */ if ((int) syscall_result < 0) goto end; sock_addr = peek_reg(tracee, ORIGINAL, SYSARG_2); size_addr = peek_reg(tracee, MODIFIED, SYSARG_3); max_size = peek_reg(tracee, MODIFIED, SYSARG_6); status = translate_socketcall_exit(tracee, sock_addr, size_addr, max_size); if (status < 0) break; /* Don't overwrite the syscall result. */ goto end; } #define SYSARG_ADDR(n) (args_addr + ((n) - 1) * sizeof_word(tracee)) #define POKE_WORD(addr, value) \ poke_word(tracee, addr, value); \ if (errno != 0) { \ status = -errno; \ break; \ } #define PEEK_WORD(addr) \ peek_word(tracee, addr); \ if (errno != 0) { \ status = -errno; \ break; \ } case PR_socketcall: { word_t args_addr; word_t sock_addr; word_t size_addr; word_t max_size; args_addr = peek_reg(tracee, ORIGINAL, SYSARG_2); switch (peek_reg(tracee, ORIGINAL, SYSARG_1)) { case SYS_ACCEPT: case SYS_ACCEPT4: /* Nothing special to do if no sockaddr was specified. */ sock_addr = PEEK_WORD(SYSARG_ADDR(2)); if (sock_addr == 0) goto end; /* Fall through. */ case SYS_GETSOCKNAME: case SYS_GETPEERNAME: /* Handle these cases below. */ status = 1; break; case SYS_BIND: case SYS_CONNECT: /* Restore the initial parameters: this memory was * overwritten at the enter stage. Remember: POKE_WORD * puts -errno in status and breaks if an error * occured. */ POKE_WORD(SYSARG_ADDR(2), peek_reg(tracee, MODIFIED, SYSARG_5)); POKE_WORD(SYSARG_ADDR(3), peek_reg(tracee, MODIFIED, SYSARG_6)); status = 0; break; default: status = 0; break; } /* Error reported by the kernel or there's nothing else to do. */ if ((int) syscall_result < 0 || status == 0) goto end; /* An error occured in SYS_BIND or SYS_CONNECT. */ if (status < 0) break; /* Remember: PEEK_WORD puts -errno in status and breaks if an * error occured. */ sock_addr = PEEK_WORD(SYSARG_ADDR(2)); size_addr = PEEK_WORD(SYSARG_ADDR(3)); max_size = peek_reg(tracee, MODIFIED, SYSARG_6); status = translate_socketcall_exit(tracee, sock_addr, size_addr, max_size); if (status < 0) break; /* Don't overwrite the syscall result. */ goto end; } #undef SYSARG_ADDR #undef PEEK_WORD #undef POKE_WORD case PR_fchdir: case PR_chdir: /* These syscalls are fully emulated, see enter.c for details * (like errors). */ status = 0; break; case PR_rename: case PR_renameat: case PR_renameat2: { char old_path[PATH_MAX]; char new_path[PATH_MAX]; ssize_t old_length; ssize_t new_length; Comparison comparison; Reg old_reg; Reg new_reg; char *tmp; /* Error reported by the kernel. */ if ((int) syscall_result < 0) goto end; if (syscall_number == PR_rename) { old_reg = SYSARG_1; new_reg = SYSARG_2; } else { old_reg = SYSARG_2; new_reg = SYSARG_4; } /* Get the old path, then convert it to the same * "point-of-view" as tracee->fs->cwd (guest). */ status = read_path(tracee, old_path, peek_reg(tracee, MODIFIED, old_reg)); if (status < 0) break; status = detranslate_path(tracee, old_path, NULL); if (status < 0) break; old_length = (status > 0 ? status - 1 : (ssize_t) strlen(old_path)); /* Nothing special to do if the moved path is not the * current working directory. */ comparison = compare_paths(old_path, tracee->fs->cwd); if (comparison != PATH1_IS_PREFIX && comparison != PATHS_ARE_EQUAL) { status = 0; break; } /* Get the new path, then convert it to the same * "point-of-view" as tracee->fs->cwd (guest). */ status = read_path(tracee, new_path, peek_reg(tracee, MODIFIED, new_reg)); if (status < 0) break; status = detranslate_path(tracee, new_path, NULL); if (status < 0) break; new_length = (status > 0 ? status - 1 : (ssize_t) strlen(new_path)); /* Sanity check. */ if (strlen(tracee->fs->cwd) >= PATH_MAX) { status = 0; break; } strcpy(old_path, tracee->fs->cwd); /* Update the virtual current working directory. */ substitute_path_prefix(old_path, old_length, new_path, new_length); tmp = talloc_strdup(tracee->fs, old_path); if (tmp == NULL) { status = -ENOMEM; break; } TALLOC_FREE(tracee->fs->cwd); tracee->fs->cwd = tmp; status = 0; break; } case PR_readlink: case PR_readlinkat: { char referee[PATH_MAX]; char referer[PATH_MAX]; size_t old_size; size_t new_size; size_t max_size; word_t input; word_t output; /* Error reported by the kernel. */ if ((int) syscall_result < 0) goto end; old_size = syscall_result; if (syscall_number == PR_readlink) { output = peek_reg(tracee, ORIGINAL, SYSARG_2); max_size = peek_reg(tracee, ORIGINAL, SYSARG_3); input = peek_reg(tracee, MODIFIED, SYSARG_1); } else { output = peek_reg(tracee, ORIGINAL, SYSARG_3); max_size = peek_reg(tracee, ORIGINAL, SYSARG_4); input = peek_reg(tracee, MODIFIED, SYSARG_2); } if (max_size > PATH_MAX) max_size = PATH_MAX; if (max_size == 0) { status = -EINVAL; break; } /* The kernel does NOT put the NULL terminating byte for * readlink(2). */ status = read_data(tracee, referee, output, old_size); if (status < 0) break; referee[old_size] = '\0'; /* Not optimal but safe (path is fully translated). */ status = read_path(tracee, referer, input); if (status < 0) break; if (status >= PATH_MAX) { status = -ENAMETOOLONG; break; } status = detranslate_path(tracee, referee, referer); if (status < 0) break; /* The original path doesn't require any transformation, i.e * it is a symetric binding. */ if (status == 0) goto end; /* Overwrite the path. Note: the output buffer might be * initialized with zeros but it was updated with the kernel * result, and then with the detranslated result. This later * might be shorter than the former, so it's safier to add a * NULL terminating byte when possible. This problem was * exposed by IDA Demo 6.3. */ if ((size_t) status < max_size) { new_size = status - 1; status = write_data(tracee, output, referee, status); } else { new_size = max_size; status = write_data(tracee, output, referee, max_size); } if (status < 0) break; /* The value of "status" is used to update the returned value * in translate_syscall_exit(). */ status = new_size; break; } #if defined(ARCH_X86_64) case PR_uname: { struct utsname utsname; word_t address; size_t size; if (get_abi(tracee) != ABI_2) goto end; /* Error reported by the kernel. */ if ((int) syscall_result < 0) goto end; address = peek_reg(tracee, ORIGINAL, SYSARG_1); status = read_data(tracee, &utsname, address, sizeof(utsname)); if (status < 0) break; /* Some 32-bit programs like package managers can be * confused when the kernel reports "x86_64". */ size = sizeof(utsname.machine); strncpy(utsname.machine, "i686", size); utsname.machine[size - 1] = '\0'; status = write_data(tracee, address, &utsname, sizeof(utsname)); if (status < 0) break; status = 0; break; } #endif case PR_execve: translate_execve_exit(tracee); goto end; case PR_ptrace: status = translate_ptrace_exit(tracee); break; case PR_wait4: case PR_waitpid: { bool set_result = true; if (tracee->as_ptracer.waits_in != WAITS_IN_PROOT) goto end; status = translate_wait_exit(tracee, &set_result); if (!set_result) goto end; break; } case PR_setrlimit: case PR_prlimit64: /* Error reported by the kernel. */ if ((int) syscall_result < 0) goto end; status = translate_setrlimit_exit(tracee, syscall_number == PR_prlimit64); if (status < 0) break; /* Don't overwrite the syscall result. */ goto end; default: goto end; } poke_reg(tracee, SYSARG_RESULT, (word_t) status); end: status = notify_extensions(tracee, SYSCALL_EXIT_END, 0, 0); if (status < 0) poke_reg(tracee, SYSARG_RESULT, (word_t) status); } proot-5.4.0/src/syscall/heap.c000066400000000000000000000153361442763353300162270ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* PROT_*, MAP_*, */ #include /* assert(3), */ #include /* strerror(3), */ #include /* sysconf(3), */ #include /* MIN(), MAX(), */ #include "tracee/tracee.h" #include "tracee/reg.h" #include "tracee/mem.h" #include "syscall/sysnum.h" #include "execve/execve.h" #include "cli/note.h" #include "compat.h" #define DEBUG_BRK(...) /* fprintf(stderr, __VA_ARGS__) */ /* The size of the heap can be zero, unlike the size of a memory * mapping. As a consequence, the first page of the "heap" memory * mapping is discarded in order to emulate an empty heap. */ static word_t heap_offset = 0; /** * Put @tracee's heap to a reliable location. By default the Linux * kernel puts it near loader's BSS, but this default location is not * reliable since the kernel might put another memory mapping right * after it (ie. continuously). In this case, @tracee's heap can't * grow anymore and some programs like Bash will abort. This issue * can be reproduced when using a Ubuntu 12.04 x86_64 rootfs on RHEL 5 * x86_64. */ void translate_brk_enter(Tracee *tracee) { word_t new_brk_address; size_t old_heap_size; size_t new_heap_size; if (tracee->heap->disabled) return; if (heap_offset == 0) { heap_offset = sysconf(_SC_PAGE_SIZE); if ((int) heap_offset <= 0) heap_offset = 0x1000; } new_brk_address = peek_reg(tracee, CURRENT, SYSARG_1); DEBUG_BRK("brk(0x%lx)\n", new_brk_address); /* Allocate a new mapping for the emulated heap. */ if (tracee->heap->base == 0) { Sysnum sysnum; Mapping *mappings; Mapping *bss; /* From PRoot's point-of-view this is the first time this * tracee calls brk(2), although an address was specified. * This is not supposed to happen the first time. It is * likely because this tracee is the very first child of PRoot * but the first execve(2) didn't happen yet (so this is not * its first call to brk(2)). For instance, the installation * of seccomp filters is made after this very first process is * traced, and might call malloc(3) before the first * execve(2). */ if (new_brk_address != 0) { if (tracee->verbose > 0) note(tracee, WARNING, INTERNAL, "process %d is doing suspicious brk()", tracee->pid); return; } /* Put the heap as close to the BSS as possible since * some programs -- like dump-emacs -- assume the gap * between the end of the BSS and the start of the * heap is relatively small (ie. < 1MB) even if ALSR * is enabled. Note that bss->addr + bss->length is * naturally aligned to a page boundary according to * add_mapping() in execve/enter.c, ie. no need to * align new_brk_address again. Now, the gap between * the BSS and the heap is only "heap_offset" bytes * long. To emulate ADDR_NO_RANDOMIZE personality, * this gap should be removed (not yet supported). */ mappings = tracee->load_info->mappings; bss = &mappings[talloc_array_length(mappings) - 1]; new_brk_address = bss->addr + bss->length; /* I don't understand yet why mmap(2) fails (EFAULT) * on architectures that also have mmap2(2). Maybe * this former implies MAP_FIXED in such cases. */ sysnum = detranslate_sysnum(get_abi(tracee), PR_mmap2) != SYSCALL_AVOIDER ? PR_mmap2 : PR_mmap; set_sysnum(tracee, sysnum); poke_reg(tracee, SYSARG_1 /* address */, new_brk_address); poke_reg(tracee, SYSARG_2 /* length */, heap_offset); poke_reg(tracee, SYSARG_3 /* prot */, PROT_READ | PROT_WRITE); poke_reg(tracee, SYSARG_4 /* flags */, MAP_PRIVATE | MAP_ANONYMOUS); poke_reg(tracee, SYSARG_5 /* fd */, -1); poke_reg(tracee, SYSARG_6 /* offset */, 0); return; } /* The size of the heap can't be negative. */ if (new_brk_address < tracee->heap->base) { set_sysnum(tracee, PR_void); return; } new_heap_size = new_brk_address - tracee->heap->base; old_heap_size = tracee->heap->size; /* Actually resizing. */ set_sysnum(tracee, PR_mremap); poke_reg(tracee, SYSARG_1 /* old_address */, tracee->heap->base - heap_offset); poke_reg(tracee, SYSARG_2 /* old_size */, old_heap_size + heap_offset); poke_reg(tracee, SYSARG_3 /* new_size */, new_heap_size + heap_offset); poke_reg(tracee, SYSARG_4 /* flags */, 0); poke_reg(tracee, SYSARG_5 /* new_address */, 0); return; } /** * c.f. function above. */ void translate_brk_exit(Tracee *tracee) { word_t result; word_t sysnum; int tracee_errno; if (tracee->heap->disabled) return; assert(heap_offset > 0); sysnum = get_sysnum(tracee, MODIFIED); result = peek_reg(tracee, CURRENT, SYSARG_RESULT); tracee_errno = (int) result; switch (sysnum) { case PR_void: poke_reg(tracee, SYSARG_RESULT, tracee->heap->base + tracee->heap->size); break; case PR_mmap: case PR_mmap2: /* On error, mmap(2) returns -errno (the last 4k is * reserved for this), whereas brk(2) returns the * previous value. */ if (tracee_errno < 0 && tracee_errno > -4096) { poke_reg(tracee, SYSARG_RESULT, 0); break; } tracee->heap->base = result + heap_offset; tracee->heap->size = 0; poke_reg(tracee, SYSARG_RESULT, tracee->heap->base + tracee->heap->size); break; case PR_mremap: /* On error, mremap(2) returns -errno (the last 4k is * reserved this), whereas brk(2) returns the previous * value. */ if ( (tracee_errno < 0 && tracee_errno > -4096) || (tracee->heap->base != result + heap_offset)) { poke_reg(tracee, SYSARG_RESULT, tracee->heap->base + tracee->heap->size); break; } tracee->heap->size = peek_reg(tracee, MODIFIED, SYSARG_3) - heap_offset; poke_reg(tracee, SYSARG_RESULT, tracee->heap->base + tracee->heap->size); break; case PR_brk: /* Is it confirmed that this suspicious call to brk(2) * is actually legit? */ if (result == peek_reg(tracee, ORIGINAL, SYSARG_1)) tracee->heap->disabled = true; break; default: assert(0); } DEBUG_BRK("brk() = 0x%lx\n", peek_reg(tracee, CURRENT, SYSARG_RESULT)); } proot-5.4.0/src/syscall/heap.h000066400000000000000000000020101442763353300162150ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef HEAP_H #define HEAP_H #include "tracee/tracee.h" extern void translate_brk_enter(Tracee *tracee); extern void translate_brk_exit(Tracee *tracee); #endif /* HEAP_H */ proot-5.4.0/src/syscall/rlimit.c000066400000000000000000000073161442763353300166110ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* bool, */ #include /* prlimit(2), */ #include /* prlimit(2), */ #include "tracee/tracee.h" #include "tracee/reg.h" #include "tracee/mem.h" #include "tracee/abi.h" #include "cli/note.h" /** * Set PRoot's stack soft limit to @tracee's one if this latter is * greater. This allows to workaround a Linux kernel bug that * prevents a tracer to access a tracee's stack beyond its last mapped * page, as it might by the case under PRoot. This function returns * -errno if an error occurred, otherwise 0. * * Details: when a tracer tries to access a tracee's stack beyond its * last mapped page, the Linux kernel should be able to increase * tracee's stack up to its soft limit. Unfortunately the Linux * kernel checks the limit of the tracer instead the limit of the * tracee. This bug was exposed using UMEQ under PRoot. * * Ref.: https://bugzilla.kernel.org/show_bug.cgi?id=91791 * * Three strategies were possible: * * - set PRoot's stack soft limit to the hard limit; this might make * the system collapse if PRoot starts to recurses indefinitely. * * - as it's done here; this appears to be a good compromise between * the strategy above and the one below. * * - as it's done here + reduce PRoot's stack soft limit as soon as * it's possible; this would be overly complicated. */ int translate_setrlimit_exit(const Tracee *tracee, bool is_prlimit) { struct rlimit proot_stack; word_t resource; word_t address; word_t tracee_stack_limit; Reg sysarg; int status; sysarg = (is_prlimit ? SYSARG_2 : SYSARG_1); resource = peek_reg(tracee, ORIGINAL, sysarg); address = peek_reg(tracee, ORIGINAL, sysarg + 1); /* Not the resource we're looking for? */ if (resource != RLIMIT_STACK) return 0; /* Retrieve new tracee's stack limit. */ if (is_prlimit) { /* Not the prlimit usage we're looking for? */ if (address == 0) return 0; tracee_stack_limit = peek_uint64(tracee, address); } else { tracee_stack_limit = peek_word(tracee, address); /* Convert this special value from 32-bit to 64-bit, * if needed. */ if (is_32on64_mode(tracee) && tracee_stack_limit == (uint32_t) -1) tracee_stack_limit = RLIM_INFINITY; } if (errno != 0) return -errno; /* Get current PRoot's stack limit. */ status = prlimit(0, RLIMIT_STACK, NULL, &proot_stack); if (status < 0) { VERBOSE(tracee, 1, "can't get stack limit."); return 0; /* Not fatal. */ } /* No need to increase current PRoot's stack limit? */ if (proot_stack.rlim_cur >= tracee_stack_limit) return 0; proot_stack.rlim_cur = tracee_stack_limit; /* Increase current PRoot's stack limit. */ status = prlimit(0, RLIMIT_STACK, &proot_stack, NULL); if (status < 0) VERBOSE(tracee, 1, "can't set stack limit."); return 0; /* Not fatal. */ VERBOSE(tracee, 1, "stack soft limit increased to %ld bytes", proot_stack.rlim_cur); return 0; } proot-5.4.0/src/syscall/rlimit.h000066400000000000000000000020161442763353300166060ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef RLIMIT_H #define RLIMIT_H #include #include "tracee/tracee.h" extern int translate_setrlimit_exit(const Tracee *tracee, bool is_prlimit); #endif /* RLIMIT_H */ proot-5.4.0/src/syscall/seccomp.c000066400000000000000000000334551442763353300167450ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include "build.h" #include "arch.h" #if defined(HAVE_SECCOMP_FILTER) #include /* prctl(2), PR_* */ #include /* struct sock_*, */ #include /* SECCOMP_MODE_FILTER, */ #include /* struct sock_*, */ #include /* AUDIT_, */ #include /* LIST_FOREACH, */ #include /* size_t, */ #include /* talloc_*, */ #include /* E*, */ #include /* memcpy(3), */ #include /* offsetof(3), */ #include /* uint*_t, UINT*_MAX, */ #include /* assert(3), */ #include "syscall/seccomp.h" #include "tracee/tracee.h" #include "syscall/syscall.h" #include "syscall/sysnum.h" #include "extension/extension.h" #include "cli/note.h" #include "compat.h" #include "attribute.h" #define DEBUG_FILTER(...) /* fprintf(stderr, __VA_ARGS__) */ /** * Allocate an empty @program->filter. This function returns -errno * if an error occurred, otherwise 0. */ static int new_program_filter(struct sock_fprog *program) { program->filter = talloc_array(NULL, struct sock_filter, 0); if (program->filter == NULL) return -ENOMEM; program->len = 0; return 0; } /** * Append to @program->filter the given @statements (@nb_statements * items). This function returns -errno if an error occurred, * otherwise 0. */ static int add_statements(struct sock_fprog *program, size_t nb_statements, struct sock_filter *statements) { size_t length; void *tmp; size_t i; length = talloc_array_length(program->filter); tmp = talloc_realloc(NULL, program->filter, struct sock_filter, length + nb_statements); if (tmp == NULL) return -ENOMEM; program->filter = tmp; for (i = 0; i < nb_statements; i++, length++) memcpy(&program->filter[length], &statements[i], sizeof(struct sock_filter)); return 0; } /** * Append to @program->filter the statements required to notify PRoot * about the given @syscall made by a tracee, with the given @flag. * This function returns -errno if an error occurred, otherwise 0. */ static int add_trace_syscall(struct sock_fprog *program, word_t syscall, int flag) { int status; /* Sanity check. */ if (syscall > UINT32_MAX) return -ERANGE; #define LENGTH_TRACE_SYSCALL 2 struct sock_filter statements[LENGTH_TRACE_SYSCALL] = { /* Compare the accumulator with the expected syscall: * skip the next statement if not equal. */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, syscall, 0, 1), /* Notify the tracer. */ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE + flag) }; DEBUG_FILTER("FILTER: trace if syscall == %ld\n", syscall); status = add_statements(program, LENGTH_TRACE_SYSCALL, statements); if (status < 0) return status; return 0; } /** * Append to @program->filter the statements that allow anything (if * unfiltered). Note that @nb_traced_syscalls is used to make a * sanity check. This function returns -errno if an error occurred, * otherwise 0. */ static int end_arch_section(struct sock_fprog *program, size_t nb_traced_syscalls) { int status; #define LENGTH_END_SECTION 1 struct sock_filter statements[LENGTH_END_SECTION] = { BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) }; DEBUG_FILTER("FILTER: allow\n"); status = add_statements(program, LENGTH_END_SECTION, statements); if (status < 0) return status; /* Sanity check, see start_arch_section(). */ if ( talloc_array_length(program->filter) - program->len != LENGTH_END_SECTION + nb_traced_syscalls * LENGTH_TRACE_SYSCALL) return -ERANGE; return 0; } /** * Append to @program->filter the statements that check the current * @architecture. Note that @nb_traced_syscalls is used to make a * sanity check. This function returns -errno if an error occurred, * otherwise 0. */ static int start_arch_section(struct sock_fprog *program, uint32_t arch, size_t nb_traced_syscalls) { const size_t arch_offset = offsetof(struct seccomp_data, arch); const size_t syscall_offset = offsetof(struct seccomp_data, nr); const size_t section_length = LENGTH_END_SECTION + nb_traced_syscalls * LENGTH_TRACE_SYSCALL; int status; /* Sanity checks. */ if ( arch_offset > UINT32_MAX || syscall_offset > UINT32_MAX || section_length > UINT32_MAX - 1) return -ERANGE; #define LENGTH_START_SECTION 4 struct sock_filter statements[LENGTH_START_SECTION] = { /* Load the current architecture into the * accumulator. */ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, arch_offset), /* Compare the accumulator with the expected * architecture: skip the following statement if * equal. */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 1, 0), /* This is not the expected architecture, so jump * unconditionally to the end of this section. */ BPF_STMT(BPF_JMP + BPF_JA + BPF_K, section_length + 1), /* This is the expected architecture, so load the * current syscall into the accumulator. */ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_offset) }; DEBUG_FILTER("FILTER: if arch == %ld, up to %zdth statement\n", arch, nb_traced_syscalls); status = add_statements(program, LENGTH_START_SECTION, statements); if (status < 0) return status; /* See the sanity check in end_arch_section(). */ program->len = talloc_array_length(program->filter); return 0; } /** * Append to @program->filter the statements that forbid anything (if * unfiltered) and update @program->len. This function returns -errno * if an error occurred, otherwise 0. */ static int finalize_program_filter(struct sock_fprog *program) { int status; #define LENGTH_FINALIZE 1 struct sock_filter statements[LENGTH_FINALIZE] = { BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL) }; DEBUG_FILTER("FILTER: kill\n"); status = add_statements(program, LENGTH_FINALIZE, statements); if (status < 0) return status; program->len = talloc_array_length(program->filter); return 0; } /** * Free @program->filter and set @program->len to 0. */ static void free_program_filter(struct sock_fprog *program) { TALLOC_FREE(program->filter); program->len = 0; } /** * Convert the given @sysnums into BPF filters according to the * following pseudo-code, then enabled them for the given @tracee and * all of its future children: * * for each handled architectures * for each filtered syscall * trace * allow * kill * * This function returns -errno if an error occurred, otherwise 0. */ static int set_seccomp_filters(const FilteredSysnum *sysnums) { SeccompArch seccomp_archs[] = SECCOMP_ARCHS; size_t nb_archs = sizeof(seccomp_archs) / sizeof(SeccompArch); struct sock_fprog program = { .len = 0, .filter = NULL }; size_t nb_traced_syscalls; size_t i, j, k; int status; status = new_program_filter(&program); if (status < 0) goto end; /* For each handled architectures */ for (i = 0; i < nb_archs; i++) { word_t syscall; nb_traced_syscalls = 0; /* Pre-compute the number of traced syscalls for this architecture. */ for (j = 0; j < seccomp_archs[i].nb_abis; j++) { for (k = 0; sysnums[k].value != PR_void; k++) { syscall = detranslate_sysnum(seccomp_archs[i].abis[j], sysnums[k].value); if (syscall != SYSCALL_AVOIDER) nb_traced_syscalls++; } } /* Filter: if handled architecture */ status = start_arch_section(&program, seccomp_archs[i].value, nb_traced_syscalls); if (status < 0) goto end; for (j = 0; j < seccomp_archs[i].nb_abis; j++) { for (k = 0; sysnums[k].value != PR_void; k++) { /* Get the architecture specific syscall number. */ syscall = detranslate_sysnum(seccomp_archs[i].abis[j], sysnums[k].value); if (syscall == SYSCALL_AVOIDER) continue; /* Filter: trace if handled syscall */ status = add_trace_syscall(&program, syscall, sysnums[k].flags); if (status < 0) goto end; } } /* Filter: allow untraced syscalls for this architecture */ status = end_arch_section(&program, nb_traced_syscalls); if (status < 0) goto end; } status = finalize_program_filter(&program); if (status < 0) goto end; status = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); if (status < 0) goto end; /* To output this BPF program for debug purpose: * * write(2, program.filter, program.len * sizeof(struct sock_filter)); */ status = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program); if (status < 0) goto end; status = 0; end: free_program_filter(&program); return status; } /* List of sysnums handled by PRoot. */ static FilteredSysnum proot_sysnums[] = { { PR_accept, FILTER_SYSEXIT }, { PR_accept4, FILTER_SYSEXIT }, { PR_access, 0 }, { PR_acct, 0 }, { PR_bind, 0 }, { PR_brk, FILTER_SYSEXIT }, { PR_chdir, FILTER_SYSEXIT }, { PR_chmod, 0 }, { PR_chown, 0 }, { PR_chown32, 0 }, { PR_chroot, 0 }, { PR_connect, 0 }, { PR_creat, 0 }, { PR_execve, FILTER_SYSEXIT }, { PR_faccessat, 0 }, { PR_fchdir, FILTER_SYSEXIT }, { PR_fchmodat, 0 }, { PR_fchownat, 0 }, { PR_fstatat64, 0 }, { PR_futimesat, 0 }, { PR_getcwd, FILTER_SYSEXIT }, { PR_getpeername, FILTER_SYSEXIT }, { PR_getsockname, FILTER_SYSEXIT }, { PR_getxattr, 0 }, { PR_inotify_add_watch, 0 }, { PR_lchown, 0 }, { PR_lchown32, 0 }, { PR_lgetxattr, 0 }, { PR_link, 0 }, { PR_linkat, 0 }, { PR_listxattr, 0 }, { PR_llistxattr, 0 }, { PR_lremovexattr, 0 }, { PR_lsetxattr, 0 }, { PR_lstat, 0 }, { PR_lstat64, 0 }, { PR_mkdir, 0 }, { PR_mkdirat, 0 }, { PR_mknod, 0 }, { PR_mknodat, 0 }, { PR_mount, 0 }, { PR_name_to_handle_at, 0 }, { PR_newfstatat, 0 }, { PR_oldlstat, 0 }, { PR_oldstat, 0 }, { PR_open, 0 }, { PR_openat, 0 }, { PR_pivot_root, 0 }, { PR_prctl, 0 }, { PR_prlimit64, FILTER_SYSEXIT }, { PR_ptrace, FILTER_SYSEXIT }, { PR_readlink, FILTER_SYSEXIT }, { PR_readlinkat, FILTER_SYSEXIT }, { PR_removexattr, 0 }, { PR_rename, FILTER_SYSEXIT }, { PR_renameat, FILTER_SYSEXIT }, { PR_renameat2, FILTER_SYSEXIT }, { PR_rmdir, 0 }, { PR_setrlimit, FILTER_SYSEXIT }, { PR_setxattr, 0 }, { PR_socketcall, FILTER_SYSEXIT }, { PR_stat, 0 }, { PR_statx, 0 }, { PR_faccessat2, 0 }, { PR_stat64, 0 }, { PR_statfs, 0 }, { PR_statfs64, 0 }, { PR_swapoff, 0 }, { PR_swapon, 0 }, { PR_symlink, 0 }, { PR_symlinkat, 0 }, { PR_truncate, 0 }, { PR_truncate64, 0 }, { PR_umount, 0 }, { PR_umount2, 0 }, { PR_uname, FILTER_SYSEXIT }, { PR_unlink, 0 }, { PR_unlinkat, 0 }, { PR_uselib, 0 }, { PR_utime, 0 }, { PR_utimensat, 0 }, { PR_utimensat_time64, 0 }, { PR_utimes, 0 }, { PR_wait4, FILTER_SYSEXIT }, { PR_waitpid, FILTER_SYSEXIT }, FILTERED_SYSNUM_END, }; /** * Add the @new_sysnums to the list of filtered @sysnums, using the * given Talloc @context. This function returns -errno if an error * occurred, otherwise 0. */ static int merge_filtered_sysnums(TALLOC_CTX *context, FilteredSysnum **sysnums, const FilteredSysnum *new_sysnums) { size_t i, j; assert(sysnums != NULL); if (*sysnums == NULL) { /* Start with no sysnums but the terminator. */ *sysnums = talloc_array(context, FilteredSysnum, 1); if (*sysnums == NULL) return -ENOMEM; (*sysnums)[0].value = PR_void; } for (i = 0; new_sysnums[i].value != PR_void; i++) { /* Search for the given sysnum. */ for (j = 0; (*sysnums)[j].value != PR_void && (*sysnums)[j].value != new_sysnums[i].value; j++) ; if ((*sysnums)[j].value == PR_void) { /* No such sysnum, allocate a new entry. */ (*sysnums) = talloc_realloc(context, (*sysnums), FilteredSysnum, j + 2); if ((*sysnums) == NULL) return -ENOMEM; (*sysnums)[j] = new_sysnums[i]; /* The last item is the terminator. */ (*sysnums)[j + 1].value = PR_void; } else /* The sysnum is already filtered, merge the * flags. */ (*sysnums)[j].flags |= new_sysnums[i].flags; } return 0; } /** * Tell the kernel to trace only syscalls handled by PRoot and its * extensions. This filter will be enabled for the given @tracee and * all of its future children. This function returns -errno if an * error occurred, otherwise 0. */ int enable_syscall_filtering(const Tracee *tracee) { FilteredSysnum *filtered_sysnums = NULL; Extension *extension; int status; assert(tracee != NULL && tracee->ctx != NULL); /* Add the sysnums required by PRoot to the list of filtered * sysnums. TODO: only if path translation is required. */ status = merge_filtered_sysnums(tracee->ctx, &filtered_sysnums, proot_sysnums); if (status < 0) return status; /* Merge the sysnums required by the extensions to the list * of filtered sysnums. */ if (tracee->extensions != NULL) { LIST_FOREACH(extension, tracee->extensions, link) { if (extension->filtered_sysnums == NULL) continue; status = merge_filtered_sysnums(tracee->ctx, &filtered_sysnums, extension->filtered_sysnums); if (status < 0) return status; } } status = set_seccomp_filters(filtered_sysnums); if (status < 0) return status; return 0; } #else #include "tracee/tracee.h" #include "attribute.h" int enable_syscall_filtering(const Tracee *tracee UNUSED) { return 0; } #endif /* defined(HAVE_SECCOMP_FILTER) */ proot-5.4.0/src/syscall/seccomp.h000066400000000000000000000024321442763353300167410ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef SECCOMP_H #define SECCOMP_H #include "syscall/sysnum.h" #include "tracee/tracee.h" #include "attribute.h" #include "arch.h" typedef struct { Sysnum value; word_t flags; } FilteredSysnum; typedef struct { unsigned int value; size_t nb_abis; Abi abis[NB_MAX_ABIS]; } SeccompArch; #define FILTERED_SYSNUM_END { PR_void, 0 } #define FILTER_SYSEXIT 0x1 extern int enable_syscall_filtering(const Tracee *tracee); #endif /* SECCOMP_H */ proot-5.4.0/src/syscall/socket.c000066400000000000000000000153641442763353300166030ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* offsetof(3), */ #include /* bzero(3), */ #include /* strncpy(3), strlen(3), */ #include /* assert(3), */ #include /* E*, */ #include /* struct sockaddr_un, AF_UNIX, */ #include /* struct sockaddr_un, */ #include /* MIN(), MAX(), */ #include "syscall/socket.h" #include "tracee/tracee.h" #include "tracee/mem.h" #include "path/binding.h" #include "path/temp.h" #include "path/path.h" #include "arch.h" #include "compat.h" /* The sockaddr_un structure has exactly the same layout on all * architectures. */ static const off_t offsetof_path = offsetof(struct sockaddr_un, sun_path); extern struct sockaddr_un sockaddr_un__; static const size_t sizeof_path = sizeof(sockaddr_un__.sun_path); /** * Copy in @sockaddr the struct sockaddr_un stored in the @tracee * memory at the given @address. Also, its pathname is copied to the * null-terminated @path. Only @size bytes are read from the @tracee * memory (should be <= @max_size <= sizeof(struct sockaddr_un)). * This function returns -errno if an error occurred, 0 if the * structure was not found (not a sockaddr_un or @size > @max_size), * otherwise 1. */ static int read_sockaddr_un(Tracee *tracee, struct sockaddr_un *sockaddr, word_t max_size, char path[PATH_MAX], word_t address, int size) { int status; assert(max_size <= sizeof(struct sockaddr_un)); /* Nothing to do if the sockaddr has an unexpected size. */ if (size <= offsetof_path || (word_t) size > max_size) return 0; bzero(sockaddr, sizeof(struct sockaddr_un)); status = read_data(tracee, sockaddr, address, size); if (status < 0) return status; /* Nothing to do if it's not a named Unix domain socket. */ if ((sockaddr->sun_family != AF_UNIX) || sockaddr->sun_path[0] == '\0') return 0; /* Be careful: sun_path doesn't have to be null-terminated. */ assert(sizeof_path < PATH_MAX - 1); strncpy(path, sockaddr->sun_path, sizeof_path); path[sizeof_path] = '\0'; return 1; } /** * Translate the pathname of the struct sockaddr_un currently stored * in the @tracee memory at the given @address. See the documentation * of read_sockaddr_un() for the meaning of the @size parameter. * Also, the new address of the translated sockaddr_un is put in the * @address parameter. This function returns -errno if an error * occurred, otherwise 0. */ int translate_socketcall_enter(Tracee *tracee, word_t *address, int size) { struct sockaddr_un sockaddr; char user_path[PATH_MAX]; char host_path[PATH_MAX]; int status; if (*address == 0) return 0; status = read_sockaddr_un(tracee, &sockaddr, sizeof(sockaddr), user_path, *address, size); if (status <= 0) return status; status = translate_path(tracee, host_path, AT_FDCWD, user_path, true); if (status < 0) return status; /* Be careful: sun_path doesn't have to be null-terminated. */ if (strlen(host_path) > sizeof_path) { const char *shorter_host_dir; const char *shorter_host_path; Binding *binding; /* Ensure the guest path of this new binding is * canonicalized, as it is always assumed. */ strcpy(user_path, host_path); status = detranslate_path(tracee, user_path, NULL); if (status < 0) return -EINVAL; /* The translated path is too long to fit the sun_path * array, so let's bind it to a shorter path. */ shorter_host_dir = create_temp_directory(tracee->ctx, "proot"); if (shorter_host_dir == NULL) return -EINVAL; shorter_host_path = talloc_asprintf(tracee->ctx, "%s/s", shorter_host_dir); if (strlen(shorter_host_path) > sizeof_path) return -EINVAL; /* Bing the guest path to a shorter host path. */ binding = insort_binding3(tracee, tracee->ctx, shorter_host_path, user_path); if (binding == NULL) return -EINVAL; /* This temporary file (shorter_host_path) will be removed once the * binding is destroyed. */ talloc_reparent(tracee->ctx, binding, shorter_host_dir); talloc_reparent(tracee->ctx, binding, shorter_host_path); /* Let's use this shorter path now. */ strcpy(host_path, shorter_host_path); } strncpy(sockaddr.sun_path, host_path, sizeof_path); /* Push the updated sockaddr to a newly allocated space. */ *address = alloc_mem(tracee, sizeof(sockaddr)); if (*address == 0) return -EFAULT; status = write_data(tracee, *address, &sockaddr, sizeof(sockaddr)); if (status < 0) return status; return 1; } /** * Detranslate the pathname of the struct sockaddr_un currently stored * in the @tracee memory at the given @sock_addr. See the * documentation of read_sockaddr_un() for the meaning of the * @size_addr and @max_size parameters. This function returns -errno * if an error occurred, otherwise 0. */ int translate_socketcall_exit(Tracee *tracee, word_t sock_addr, word_t size_addr, word_t max_size) { struct sockaddr_un sockaddr; bool is_truncated = false; char path[PATH_MAX]; int status; int size; if (sock_addr == 0) return 0; size = peek_int32(tracee, size_addr); if (errno != 0) return -errno; max_size = MIN(max_size, sizeof(sockaddr)); status = read_sockaddr_un(tracee, &sockaddr, max_size, path, sock_addr, size); if (status <= 0) return status; status = detranslate_path(tracee, path, NULL); if (status < 0) return status; /* Be careful: sun_path doesn't have to be null-terminated. */ size = offsetof_path + strlen(path) + 1; if (size < 0 || (word_t) size > max_size) { size = max_size; is_truncated = true; } strncpy(sockaddr.sun_path, path, sizeof_path); /* Overwrite the sockaddr and socklen parameters. */ status = write_data(tracee, sock_addr, &sockaddr, size); if (status < 0) return status; /* If sockaddr is truncated (because the buffer provided is * too small), addrlen will return a value greater than was * supplied to the call. See man 2 accept. */ if (is_truncated) size = max_size + 1; poke_int32(tracee, size_addr, size); if (errno != 0) return -errno; return 0; } proot-5.4.0/src/syscall/socket.h000066400000000000000000000021751442763353300166040ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef SOCKET_H #define SOCKET_H #include "arch.h" /* word_t */ #include "tracee/tracee.h" int translate_socketcall_enter(Tracee *tracee, word_t *sock_addr, int size); int translate_socketcall_exit(Tracee *tracee, word_t sock_addr, word_t size_addr, word_t max_size); #endif /* SOCKET_H */ proot-5.4.0/src/syscall/syscall.c000066400000000000000000000123271442763353300167610ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* assert(3), */ #include /* PATH_MAX, */ #include /* strlen(3), */ #include /* errno(3), E* */ #include "syscall/syscall.h" #include "syscall/chain.h" #include "extension/extension.h" #include "tracee/tracee.h" #include "tracee/reg.h" #include "tracee/mem.h" /** * Copy in @path a C string (PATH_MAX bytes max.) from the @tracee's * memory address space pointed to by the @reg argument of the * current syscall. This function returns -errno if an error occured, * otherwise it returns the size in bytes put into the @path. */ int get_sysarg_path(const Tracee *tracee, char path[PATH_MAX], Reg reg) { int size; word_t src; src = peek_reg(tracee, CURRENT, reg); /* Check if the parameter is not NULL. Technically we should * not return an -EFAULT for this special value since it is * allowed for some syscall, utimensat(2) for instance. */ if (src == 0) { path[0] = '\0'; return 0; } /* Get the path from the tracee's memory space. */ size = read_path(tracee, path, src); if (size < 0) return size; path[size] = '\0'; return size; } /** * Copy @size bytes of the data pointed to by @tracer_ptr into a * @tracee's memory block and make the @reg argument of the current * syscall points to this new block. This function returns -errno if * an error occured, otherwise 0. */ static int set_sysarg_data(Tracee *tracee, const void *tracer_ptr, word_t size, Reg reg) { word_t tracee_ptr; int status; /* Allocate space into the tracee's memory to host the new data. */ tracee_ptr = alloc_mem(tracee, size); if (tracee_ptr == 0) return -EFAULT; /* Copy the new data into the previously allocated space. */ status = write_data(tracee, tracee_ptr, tracer_ptr, size); if (status < 0) return status; /* Make this argument point to the new data. */ poke_reg(tracee, reg, tracee_ptr); return 0; } /** * Copy @path to a @tracee's memory block and make the @reg argument * of the current syscall points to this new block. This function * returns -errno if an error occured, otherwise 0. */ int set_sysarg_path(Tracee *tracee, const char path[PATH_MAX], Reg reg) { return set_sysarg_data(tracee, path, strlen(path) + 1, reg); } void translate_syscall(Tracee *tracee) { const bool is_enter_stage = IS_IN_SYSENTER(tracee); int status; assert(tracee->exe != NULL); status = fetch_regs(tracee); if (status < 0) return; if (is_enter_stage) { /* Never restore original register values at the end * of this stage. */ tracee->restore_original_regs = false; print_current_regs(tracee, 3, "sysenter start"); /* Translate the syscall only if it was actually * requested by the tracee, it is not a syscall * chained by PRoot. */ if (tracee->chain.syscalls == NULL) { save_current_regs(tracee, ORIGINAL); status = translate_syscall_enter(tracee); save_current_regs(tracee, MODIFIED); } else { status = notify_extensions(tracee, SYSCALL_CHAINED_ENTER, 0, 0); tracee->restart_how = PTRACE_SYSCALL; } /* Remember the tracee status for the "exit" stage and * avoid the actual syscall if an error was reported * by the translation/extension. */ if (status < 0) { set_sysnum(tracee, PR_void); poke_reg(tracee, SYSARG_RESULT, (word_t) status); tracee->status = status; } else tracee->status = 1; /* Restore tracee's stack pointer now if it won't hit * the sysexit stage (i.e. when seccomp is enabled and * there's nothing else to do). */ if (tracee->restart_how == PTRACE_CONT) { tracee->status = 0; poke_reg(tracee, STACK_POINTER, peek_reg(tracee, ORIGINAL, STACK_POINTER)); } } else { /* By default, restore original register values at the * end of this stage. */ tracee->restore_original_regs = true; print_current_regs(tracee, 5, "sysexit start"); /* Translate the syscall only if it was actually * requested by the tracee, it is not a syscall * chained by PRoot. */ if (tracee->chain.syscalls == NULL) translate_syscall_exit(tracee); else (void) notify_extensions(tracee, SYSCALL_CHAINED_EXIT, 0, 0); /* Reset the tracee's status. */ tracee->status = 0; /* Insert the next chained syscall, if any. */ if (tracee->chain.syscalls != NULL) chain_next_syscall(tracee); } (void) push_regs(tracee); if (is_enter_stage) print_current_regs(tracee, 5, "sysenter end" ); else print_current_regs(tracee, 4, "sysexit end"); } proot-5.4.0/src/syscall/syscall.h000066400000000000000000000024521442763353300167640ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef SYSCALL_H #define SYSCALL_H #include /* PATH_MAX, */ #include "tracee/tracee.h" #include "tracee/reg.h" extern int get_sysarg_path(const Tracee *tracee, char path[PATH_MAX], Reg reg); extern int set_sysarg_path(Tracee *tracee, const char path[PATH_MAX], Reg reg); extern void translate_syscall(Tracee *tracee); extern int translate_syscall_enter(Tracee *tracee); extern void translate_syscall_exit(Tracee *tracee); #endif /* SYSCALL_H */ proot-5.4.0/src/syscall/sysnum.c000066400000000000000000000067641442763353300166550ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include #include "syscall/sysnum.h" #include "tracee/tracee.h" #include "tracee/abi.h" #include "tracee/reg.h" #include "arch.h" #include "cli/note.h" #include SYSNUMS_HEADER1 #ifdef SYSNUMS_HEADER2 #include SYSNUMS_HEADER2 #endif #ifdef SYSNUMS_HEADER3 #include SYSNUMS_HEADER3 #endif typedef struct { const Sysnum *table; word_t offset; word_t length; } Sysnums; /** * Update @sysnums' fields with the sysnum table for the given @abi. */ static void get_sysnums(Abi abi, Sysnums *sysnums) { switch (abi) { case ABI_DEFAULT: sysnums->table = SYSNUMS_ABI1; sysnums->length = sizeof(SYSNUMS_ABI1) / sizeof(Sysnum); sysnums->offset = 0; return; #ifdef SYSNUMS_ABI2 case ABI_2: sysnums->table = SYSNUMS_ABI2; sysnums->length = sizeof(SYSNUMS_ABI2) / sizeof(Sysnum); sysnums->offset = 0; return; #endif #ifdef SYSNUMS_ABI3 case ABI_3: sysnums->table = SYSNUMS_ABI3; sysnums->length = sizeof(SYSNUMS_ABI3) / sizeof(Sysnum); sysnums->offset = 0x40000000; /* x32 */ return; #endif default: assert(0); } } /** * Return the neutral value of @sysnum from the given @abi. */ static Sysnum translate_sysnum(Abi abi, word_t sysnum) { Sysnums sysnums; word_t index; get_sysnums(abi, &sysnums); /* Sanity checks. */ if (sysnum < sysnums.offset) return PR_void; index = sysnum - sysnums.offset; /* Sanity checks. */ if (index > sysnums.length) return PR_void; return sysnums.table[index]; } /** * Return the architecture value of @sysnum for the given @abi. */ word_t detranslate_sysnum(Abi abi, Sysnum sysnum) { Sysnums sysnums; size_t i; /* Very special case. */ if (sysnum == PR_void) return SYSCALL_AVOIDER; get_sysnums(abi, &sysnums); for (i = 0; i < sysnums.length; i++) { if (sysnums.table[i] != sysnum) continue; return i + sysnums.offset; } return SYSCALL_AVOIDER; } /** * Return the neutral value of the @tracee's current syscall number. */ Sysnum get_sysnum(const Tracee *tracee, RegVersion version) { return translate_sysnum(get_abi(tracee), peek_reg(tracee, version, SYSARG_NUM)); } /** * Overwrite the @tracee's current syscall number with @sysnum. Note: * this neutral value is automatically converted into the architecture * value. */ void set_sysnum(Tracee *tracee, Sysnum sysnum) { poke_reg(tracee, SYSARG_NUM, detranslate_sysnum(get_abi(tracee), sysnum)); } /** * Return the human readable name of @sysnum. */ const char *stringify_sysnum(Sysnum sysnum) { #define SYSNUM(item) [ PR_ ## item ] = #item, static const char *names[] = { #include "syscall/sysnums.list" }; #undef SYSNUM if (sysnum == 0) return "void"; if (sysnum >= PR_NB_SYSNUM) return ""; return names[sysnum]; } proot-5.4.0/src/syscall/sysnum.h000066400000000000000000000025431442763353300166510ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef SYSNUM_H #define SYSNUM_H #include #include "tracee/tracee.h" #include "tracee/abi.h" #include "tracee/reg.h" #define SYSNUM(item) PR_ ## item, typedef enum { PR_void = 0, #include "syscall/sysnums.list" PR_NB_SYSNUM } Sysnum; #undef SYSNUM extern Sysnum get_sysnum(const Tracee *tracee, RegVersion version); extern void set_sysnum(Tracee *tracee, Sysnum sysnum); extern word_t detranslate_sysnum(Abi abi, Sysnum sysnum); extern const char *stringify_sysnum(Sysnum sysnum); #endif /* SYSNUM_H */ proot-5.4.0/src/syscall/sysnums-arm.h000066400000000000000000000203311442763353300176040ustar00rootroot00000000000000#include "syscall/sysnum.h" static const Sysnum sysnums_arm[] = { [ 0 ] = PR_restart_syscall, [ 1 ] = PR_exit, [ 2 ] = PR_fork, [ 3 ] = PR_read, [ 4 ] = PR_write, [ 5 ] = PR_open, [ 6 ] = PR_close, [ 8 ] = PR_creat, [ 9 ] = PR_link, [ 10 ] = PR_unlink, [ 11 ] = PR_execve, [ 12 ] = PR_chdir, [ 14 ] = PR_mknod, [ 15 ] = PR_chmod, [ 16 ] = PR_lchown, [ 19 ] = PR_lseek, [ 20 ] = PR_getpid, [ 21 ] = PR_mount, [ 23 ] = PR_setuid, [ 24 ] = PR_getuid, [ 26 ] = PR_ptrace, [ 29 ] = PR_pause, [ 33 ] = PR_access, [ 34 ] = PR_nice, [ 36 ] = PR_sync, [ 37 ] = PR_kill, [ 38 ] = PR_rename, [ 39 ] = PR_mkdir, [ 40 ] = PR_rmdir, [ 41 ] = PR_dup, [ 42 ] = PR_pipe, [ 43 ] = PR_times, [ 45 ] = PR_brk, [ 46 ] = PR_setgid, [ 47 ] = PR_getgid, [ 49 ] = PR_geteuid, [ 50 ] = PR_getegid, [ 51 ] = PR_acct, [ 52 ] = PR_umount2, [ 54 ] = PR_ioctl, [ 55 ] = PR_fcntl, [ 57 ] = PR_setpgid, [ 60 ] = PR_umask, [ 61 ] = PR_chroot, [ 62 ] = PR_ustat, [ 63 ] = PR_dup2, [ 64 ] = PR_getppid, [ 65 ] = PR_getpgrp, [ 66 ] = PR_setsid, [ 67 ] = PR_sigaction, [ 70 ] = PR_setreuid, [ 71 ] = PR_setregid, [ 72 ] = PR_sigsuspend, [ 73 ] = PR_sigpending, [ 74 ] = PR_sethostname, [ 75 ] = PR_setrlimit, [ 77 ] = PR_getrusage, [ 78 ] = PR_gettimeofday, [ 79 ] = PR_settimeofday, [ 80 ] = PR_getgroups, [ 81 ] = PR_setgroups, [ 83 ] = PR_symlink, [ 85 ] = PR_readlink, [ 86 ] = PR_uselib, [ 87 ] = PR_swapon, [ 88 ] = PR_reboot, [ 91 ] = PR_munmap, [ 92 ] = PR_truncate, [ 93 ] = PR_ftruncate, [ 94 ] = PR_fchmod, [ 95 ] = PR_fchown, [ 96 ] = PR_getpriority, [ 97 ] = PR_setpriority, [ 99 ] = PR_statfs, [ 100 ] = PR_fstatfs, [ 103 ] = PR_syslog, [ 104 ] = PR_setitimer, [ 105 ] = PR_getitimer, [ 106 ] = PR_stat, [ 107 ] = PR_lstat, [ 108 ] = PR_fstat, [ 111 ] = PR_vhangup, [ 114 ] = PR_wait4, [ 115 ] = PR_swapoff, [ 116 ] = PR_sysinfo, [ 118 ] = PR_fsync, [ 119 ] = PR_sigreturn, [ 120 ] = PR_clone, [ 121 ] = PR_setdomainname, [ 122 ] = PR_uname, [ 124 ] = PR_adjtimex, [ 125 ] = PR_mprotect, [ 126 ] = PR_sigprocmask, [ 128 ] = PR_init_module, [ 129 ] = PR_delete_module, [ 131 ] = PR_quotactl, [ 132 ] = PR_getpgid, [ 133 ] = PR_fchdir, [ 134 ] = PR_bdflush, [ 135 ] = PR_sysfs, [ 136 ] = PR_personality, [ 138 ] = PR_setfsuid, [ 139 ] = PR_setfsgid, [ 140 ] = PR__llseek, [ 141 ] = PR_getdents, [ 142 ] = PR__newselect, [ 143 ] = PR_flock, [ 144 ] = PR_msync, [ 145 ] = PR_readv, [ 146 ] = PR_writev, [ 147 ] = PR_getsid, [ 148 ] = PR_fdatasync, [ 149 ] = PR__sysctl, [ 150 ] = PR_mlock, [ 151 ] = PR_munlock, [ 152 ] = PR_mlockall, [ 153 ] = PR_munlockall, [ 154 ] = PR_sched_setparam, [ 155 ] = PR_sched_getparam, [ 156 ] = PR_sched_setscheduler, [ 157 ] = PR_sched_getscheduler, [ 158 ] = PR_sched_yield, [ 159 ] = PR_sched_get_priority_max, [ 160 ] = PR_sched_get_priority_min, [ 161 ] = PR_sched_rr_get_interval, [ 162 ] = PR_nanosleep, [ 163 ] = PR_mremap, [ 164 ] = PR_setresuid, [ 165 ] = PR_getresuid, [ 168 ] = PR_poll, [ 169 ] = PR_nfsservctl, [ 170 ] = PR_setresgid, [ 171 ] = PR_getresgid, [ 172 ] = PR_prctl, [ 173 ] = PR_rt_sigreturn, [ 174 ] = PR_rt_sigaction, [ 175 ] = PR_rt_sigprocmask, [ 176 ] = PR_rt_sigpending, [ 177 ] = PR_rt_sigtimedwait, [ 178 ] = PR_rt_sigqueueinfo, [ 179 ] = PR_rt_sigsuspend, [ 180 ] = PR_pread64, [ 181 ] = PR_pwrite64, [ 182 ] = PR_chown, [ 183 ] = PR_getcwd, [ 184 ] = PR_capget, [ 185 ] = PR_capset, [ 186 ] = PR_sigaltstack, [ 187 ] = PR_sendfile, [ 190 ] = PR_vfork, [ 191 ] = PR_ugetrlimit, [ 192 ] = PR_mmap2, [ 193 ] = PR_truncate64, [ 194 ] = PR_ftruncate64, [ 195 ] = PR_stat64, [ 196 ] = PR_lstat64, [ 197 ] = PR_fstat64, [ 198 ] = PR_lchown32, [ 199 ] = PR_getuid32, [ 200 ] = PR_getgid32, [ 201 ] = PR_geteuid32, [ 202 ] = PR_getegid32, [ 203 ] = PR_setreuid32, [ 204 ] = PR_setregid32, [ 205 ] = PR_getgroups32, [ 206 ] = PR_setgroups32, [ 207 ] = PR_fchown32, [ 208 ] = PR_setresuid32, [ 209 ] = PR_getresuid32, [ 210 ] = PR_setresgid32, [ 211 ] = PR_getresgid32, [ 212 ] = PR_chown32, [ 213 ] = PR_setuid32, [ 214 ] = PR_setgid32, [ 215 ] = PR_setfsuid32, [ 216 ] = PR_setfsgid32, [ 217 ] = PR_getdents64, [ 218 ] = PR_pivot_root, [ 219 ] = PR_mincore, [ 220 ] = PR_madvise, [ 221 ] = PR_fcntl64, [ 224 ] = PR_gettid, [ 225 ] = PR_readahead, [ 226 ] = PR_setxattr, [ 227 ] = PR_lsetxattr, [ 228 ] = PR_fsetxattr, [ 229 ] = PR_getxattr, [ 230 ] = PR_lgetxattr, [ 231 ] = PR_fgetxattr, [ 232 ] = PR_listxattr, [ 233 ] = PR_llistxattr, [ 234 ] = PR_flistxattr, [ 235 ] = PR_removexattr, [ 236 ] = PR_lremovexattr, [ 237 ] = PR_fremovexattr, [ 238 ] = PR_tkill, [ 239 ] = PR_sendfile64, [ 240 ] = PR_futex, [ 241 ] = PR_sched_setaffinity, [ 242 ] = PR_sched_getaffinity, [ 243 ] = PR_io_setup, [ 244 ] = PR_io_destroy, [ 245 ] = PR_io_getevents, [ 246 ] = PR_io_submit, [ 247 ] = PR_io_cancel, [ 248 ] = PR_exit_group, [ 249 ] = PR_lookup_dcookie, [ 250 ] = PR_epoll_create, [ 251 ] = PR_epoll_ctl, [ 252 ] = PR_epoll_wait, [ 253 ] = PR_remap_file_pages, [ 256 ] = PR_set_tid_address, [ 257 ] = PR_timer_create, [ 258 ] = PR_timer_settime, [ 259 ] = PR_timer_gettime, [ 260 ] = PR_timer_getoverrun, [ 261 ] = PR_timer_delete, [ 262 ] = PR_clock_settime, [ 263 ] = PR_clock_gettime, [ 264 ] = PR_clock_getres, [ 265 ] = PR_clock_nanosleep, [ 266 ] = PR_statfs64, [ 267 ] = PR_fstatfs64, [ 268 ] = PR_tgkill, [ 269 ] = PR_utimes, [ 270 ] = PR_arm_fadvise64_64, [ 271 ] = PR_pciconfig_iobase, [ 272 ] = PR_pciconfig_read, [ 273 ] = PR_pciconfig_write, [ 274 ] = PR_mq_open, [ 275 ] = PR_mq_unlink, [ 276 ] = PR_mq_timedsend, [ 277 ] = PR_mq_timedreceive, [ 278 ] = PR_mq_notify, [ 279 ] = PR_mq_getsetattr, [ 280 ] = PR_waitid, [ 281 ] = PR_socket, [ 282 ] = PR_bind, [ 283 ] = PR_connect, [ 284 ] = PR_listen, [ 285 ] = PR_accept, [ 286 ] = PR_getsockname, [ 287 ] = PR_getpeername, [ 288 ] = PR_socketpair, [ 289 ] = PR_send, [ 290 ] = PR_sendto, [ 291 ] = PR_recv, [ 292 ] = PR_recvfrom, [ 293 ] = PR_shutdown, [ 294 ] = PR_setsockopt, [ 295 ] = PR_getsockopt, [ 296 ] = PR_sendmsg, [ 297 ] = PR_recvmsg, [ 298 ] = PR_semop, [ 299 ] = PR_semget, [ 300 ] = PR_semctl, [ 301 ] = PR_msgsnd, [ 302 ] = PR_msgrcv, [ 303 ] = PR_msgget, [ 304 ] = PR_msgctl, [ 305 ] = PR_shmat, [ 306 ] = PR_shmdt, [ 307 ] = PR_shmget, [ 308 ] = PR_shmctl, [ 309 ] = PR_add_key, [ 310 ] = PR_request_key, [ 311 ] = PR_keyctl, [ 312 ] = PR_semtimedop, [ 313 ] = PR_vserver, [ 314 ] = PR_ioprio_set, [ 315 ] = PR_ioprio_get, [ 316 ] = PR_inotify_init, [ 317 ] = PR_inotify_add_watch, [ 318 ] = PR_inotify_rm_watch, [ 319 ] = PR_mbind, [ 320 ] = PR_get_mempolicy, [ 321 ] = PR_set_mempolicy, [ 322 ] = PR_openat, [ 323 ] = PR_mkdirat, [ 324 ] = PR_mknodat, [ 325 ] = PR_fchownat, [ 326 ] = PR_futimesat, [ 327 ] = PR_fstatat64, [ 328 ] = PR_unlinkat, [ 329 ] = PR_renameat, [ 330 ] = PR_linkat, [ 331 ] = PR_symlinkat, [ 332 ] = PR_readlinkat, [ 333 ] = PR_fchmodat, [ 334 ] = PR_faccessat, [ 335 ] = PR_pselect6, [ 336 ] = PR_ppoll, [ 337 ] = PR_unshare, [ 338 ] = PR_set_robust_list, [ 339 ] = PR_get_robust_list, [ 340 ] = PR_splice, [ 341 ] = PR_arm_sync_file_range, [ 342 ] = PR_tee, [ 343 ] = PR_vmsplice, [ 344 ] = PR_move_pages, [ 345 ] = PR_getcpu, [ 346 ] = PR_epoll_pwait, [ 347 ] = PR_kexec_load, [ 348 ] = PR_utimensat, [ 349 ] = PR_signalfd, [ 350 ] = PR_timerfd_create, [ 351 ] = PR_eventfd, [ 352 ] = PR_fallocate, [ 353 ] = PR_timerfd_settime, [ 354 ] = PR_timerfd_gettime, [ 355 ] = PR_signalfd4, [ 356 ] = PR_eventfd2, [ 357 ] = PR_epoll_create1, [ 358 ] = PR_dup3, [ 359 ] = PR_pipe2, [ 360 ] = PR_inotify_init1, [ 361 ] = PR_preadv, [ 362 ] = PR_pwritev, [ 363 ] = PR_rt_tgsigqueueinfo, [ 364 ] = PR_perf_event_open, [ 365 ] = PR_recvmmsg, [ 366 ] = PR_accept4, [ 367 ] = PR_fanotify_init, [ 368 ] = PR_fanotify_mark, [ 369 ] = PR_prlimit64, [ 370 ] = PR_name_to_handle_at, [ 371 ] = PR_open_by_handle_at, [ 372 ] = PR_clock_adjtime, [ 373 ] = PR_syncfs, [ 374 ] = PR_sendmmsg, [ 375 ] = PR_setns, [ 376 ] = PR_process_vm_readv, [ 377 ] = PR_process_vm_writev, [ 378 ] = PR_kcmp, [ 379 ] = PR_finit_module, [ 380 ] = PR_sched_setattr, [ 381 ] = PR_sched_getattr, [ 382 ] = PR_renameat2, [ 397 ] = PR_statx, [ 412 ] = PR_utimensat_time64, }; proot-5.4.0/src/syscall/sysnums-arm64.h000066400000000000000000000146301442763353300177630ustar00rootroot00000000000000#include "syscall/sysnum.h" static const Sysnum sysnums_arm64[] = { [ 0 ] = PR_io_setup, [ 1 ] = PR_io_destroy, [ 2 ] = PR_io_submit, [ 3 ] = PR_io_cancel, [ 4 ] = PR_io_getevents, [ 5 ] = PR_setxattr, [ 6 ] = PR_lsetxattr, [ 7 ] = PR_fsetxattr, [ 8 ] = PR_getxattr, [ 9 ] = PR_lgetxattr, [ 10 ] = PR_fgetxattr, [ 11 ] = PR_listxattr, [ 12 ] = PR_llistxattr, [ 13 ] = PR_flistxattr, [ 14 ] = PR_removexattr, [ 15 ] = PR_lremovexattr, [ 16 ] = PR_fremovexattr, [ 17 ] = PR_getcwd, [ 18 ] = PR_lookup_dcookie, [ 19 ] = PR_eventfd2, [ 20 ] = PR_epoll_create1, [ 21 ] = PR_epoll_ctl, [ 22 ] = PR_epoll_pwait, [ 23 ] = PR_dup, [ 24 ] = PR_dup3, [ 25 ] = PR_fcntl, [ 26 ] = PR_inotify_init1, [ 27 ] = PR_inotify_add_watch, [ 28 ] = PR_inotify_rm_watch, [ 29 ] = PR_ioctl, [ 30 ] = PR_ioprio_set, [ 31 ] = PR_ioprio_get, [ 32 ] = PR_flock, [ 33 ] = PR_mknodat, [ 34 ] = PR_mkdirat, [ 35 ] = PR_unlinkat, [ 36 ] = PR_symlinkat, [ 37 ] = PR_linkat, [ 38 ] = PR_renameat, [ 39 ] = PR_umount2, [ 40 ] = PR_mount, [ 41 ] = PR_pivot_root, [ 42 ] = PR_nfsservctl, [ 43 ] = PR_statfs, [ 44 ] = PR_fstatfs, [ 45 ] = PR_truncate, [ 46 ] = PR_ftruncate, [ 47 ] = PR_fallocate, [ 48 ] = PR_faccessat, [ 49 ] = PR_chdir, [ 50 ] = PR_fchdir, [ 51 ] = PR_chroot, [ 52 ] = PR_fchmod, [ 53 ] = PR_fchmodat, [ 54 ] = PR_fchownat, [ 55 ] = PR_fchown, [ 56 ] = PR_openat, [ 57 ] = PR_close, [ 58 ] = PR_vhangup, [ 59 ] = PR_pipe2, [ 60 ] = PR_quotactl, [ 61 ] = PR_getdents64, [ 62 ] = PR_lseek, [ 63 ] = PR_read, [ 64 ] = PR_write, [ 65 ] = PR_readv, [ 66 ] = PR_writev, [ 67 ] = PR_pread64, [ 68 ] = PR_pwrite64, [ 69 ] = PR_preadv, [ 70 ] = PR_pwritev, [ 71 ] = PR_sendfile, [ 72 ] = PR_pselect6, [ 73 ] = PR_ppoll, [ 74 ] = PR_signalfd4, [ 75 ] = PR_vmsplice, [ 76 ] = PR_splice, [ 77 ] = PR_tee, [ 78 ] = PR_readlinkat, [ 79 ] = PR_fstatat64, [ 80 ] = PR_fstat, [ 81 ] = PR_sync, [ 82 ] = PR_fsync, [ 83 ] = PR_fdatasync, [ 84 ] = PR_sync_file_range, [ 85 ] = PR_timerfd_create, [ 86 ] = PR_timerfd_settime, [ 87 ] = PR_timerfd_gettime, [ 88 ] = PR_utimensat, [ 89 ] = PR_acct, [ 90 ] = PR_capget, [ 91 ] = PR_capset, [ 92 ] = PR_personality, [ 93 ] = PR_exit, [ 94 ] = PR_exit_group, [ 95 ] = PR_waitid, [ 96 ] = PR_set_tid_address, [ 97 ] = PR_unshare, [ 98 ] = PR_futex, [ 99 ] = PR_set_robust_list, [ 100 ] = PR_get_robust_list, [ 101 ] = PR_nanosleep, [ 102 ] = PR_getitimer, [ 103 ] = PR_setitimer, [ 104 ] = PR_kexec_load, [ 105 ] = PR_init_module, [ 106 ] = PR_delete_module, [ 107 ] = PR_timer_create, [ 108 ] = PR_timer_gettime, [ 109 ] = PR_timer_getoverrun, [ 110 ] = PR_timer_settime, [ 111 ] = PR_timer_delete, [ 112 ] = PR_clock_settime, [ 113 ] = PR_clock_gettime, [ 114 ] = PR_clock_getres, [ 115 ] = PR_clock_nanosleep, [ 116 ] = PR_syslog, [ 117 ] = PR_ptrace, [ 118 ] = PR_sched_setparam, [ 119 ] = PR_sched_setscheduler, [ 120 ] = PR_sched_getscheduler, [ 121 ] = PR_sched_getparam, [ 122 ] = PR_sched_setaffinity, [ 123 ] = PR_sched_getaffinity, [ 124 ] = PR_sched_yield, [ 125 ] = PR_sched_get_priority_max, [ 126 ] = PR_sched_get_priority_min, [ 127 ] = PR_sched_rr_get_interval, [ 128 ] = PR_restart_syscall, [ 129 ] = PR_kill, [ 130 ] = PR_tkill, [ 131 ] = PR_tgkill, [ 132 ] = PR_sigaltstack, [ 133 ] = PR_rt_sigsuspend, [ 134 ] = PR_rt_sigaction, [ 135 ] = PR_rt_sigprocmask, [ 136 ] = PR_rt_sigpending, [ 137 ] = PR_rt_sigtimedwait, [ 138 ] = PR_rt_sigqueueinfo, [ 139 ] = PR_rt_sigreturn, [ 140 ] = PR_setpriority, [ 141 ] = PR_getpriority, [ 142 ] = PR_reboot, [ 143 ] = PR_setregid, [ 144 ] = PR_setgid, [ 145 ] = PR_setreuid, [ 146 ] = PR_setuid, [ 147 ] = PR_setresuid, [ 148 ] = PR_getresuid, [ 149 ] = PR_setresgid, [ 150 ] = PR_getresgid, [ 151 ] = PR_setfsuid, [ 152 ] = PR_setfsgid, [ 153 ] = PR_times, [ 154 ] = PR_setpgid, [ 155 ] = PR_getpgid, [ 156 ] = PR_getsid, [ 157 ] = PR_setsid, [ 158 ] = PR_getgroups, [ 159 ] = PR_setgroups, [ 160 ] = PR_uname, [ 161 ] = PR_sethostname, [ 162 ] = PR_setdomainname, [ 163 ] = PR_getrlimit, [ 164 ] = PR_setrlimit, [ 165 ] = PR_getrusage, [ 166 ] = PR_umask, [ 167 ] = PR_prctl, [ 168 ] = PR_getcpu, [ 169 ] = PR_gettimeofday, [ 170 ] = PR_settimeofday, [ 171 ] = PR_adjtimex, [ 172 ] = PR_getpid, [ 173 ] = PR_getppid, [ 174 ] = PR_getuid, [ 175 ] = PR_geteuid, [ 176 ] = PR_getgid, [ 177 ] = PR_getegid, [ 178 ] = PR_gettid, [ 179 ] = PR_sysinfo, [ 180 ] = PR_mq_open, [ 181 ] = PR_mq_unlink, [ 182 ] = PR_mq_timedsend, [ 183 ] = PR_mq_timedreceive, [ 184 ] = PR_mq_notify, [ 185 ] = PR_mq_getsetattr, [ 186 ] = PR_msgget, [ 187 ] = PR_msgctl, [ 188 ] = PR_msgrcv, [ 189 ] = PR_msgsnd, [ 190 ] = PR_semget, [ 191 ] = PR_semctl, [ 192 ] = PR_semtimedop, [ 193 ] = PR_semop, [ 194 ] = PR_shmget, [ 195 ] = PR_shmctl, [ 196 ] = PR_shmat, [ 197 ] = PR_shmdt, [ 198 ] = PR_socket, [ 199 ] = PR_socketpair, [ 200 ] = PR_bind, [ 201 ] = PR_listen, [ 202 ] = PR_accept, [ 203 ] = PR_connect, [ 204 ] = PR_getsockname, [ 205 ] = PR_getpeername, [ 206 ] = PR_sendto, [ 207 ] = PR_recvfrom, [ 208 ] = PR_setsockopt, [ 209 ] = PR_getsockopt, [ 210 ] = PR_shutdown, [ 211 ] = PR_sendmsg, [ 212 ] = PR_recvmsg, [ 213 ] = PR_readahead, [ 214 ] = PR_brk, [ 215 ] = PR_munmap, [ 216 ] = PR_mremap, [ 217 ] = PR_add_key, [ 218 ] = PR_request_key, [ 219 ] = PR_keyctl, [ 220 ] = PR_clone, [ 221 ] = PR_execve, [ 222 ] = PR_mmap, [ 223 ] = PR_fadvise64, [ 224 ] = PR_swapon, [ 225 ] = PR_swapoff, [ 226 ] = PR_mprotect, [ 227 ] = PR_msync, [ 228 ] = PR_mlock, [ 229 ] = PR_munlock, [ 230 ] = PR_mlockall, [ 231 ] = PR_munlockall, [ 232 ] = PR_mincore, [ 233 ] = PR_madvise, [ 234 ] = PR_remap_file_pages, [ 235 ] = PR_mbind, [ 236 ] = PR_get_mempolicy, [ 237 ] = PR_set_mempolicy, [ 238 ] = PR_migrate_pages, [ 239 ] = PR_move_pages, [ 240 ] = PR_rt_tgsigqueueinfo, [ 241 ] = PR_perf_event_open, [ 242 ] = PR_accept4, [ 243 ] = PR_recvmmsg, [ 244 ] = PR_arch_specific_syscall, [ 260 ] = PR_wait4, [ 261 ] = PR_prlimit64, [ 262 ] = PR_fanotify_init, [ 263 ] = PR_fanotify_mark, [ 264 ] = PR_name_to_handle_at, [ 265 ] = PR_open_by_handle_at, [ 266 ] = PR_clock_adjtime, [ 267 ] = PR_syncfs, [ 268 ] = PR_setns, [ 269 ] = PR_sendmmsg, [ 270 ] = PR_process_vm_readv, [ 271 ] = PR_process_vm_writev, [ 272 ] = PR_kcmp, [ 273 ] = PR_finit_module, [ 274 ] = PR_sched_setattr, [ 275 ] = PR_sched_getattr, [ 276 ] = PR_renameat2, [ 291 ] = PR_statx, }; proot-5.4.0/src/syscall/sysnums-i386.h000066400000000000000000000207051442763353300175230ustar00rootroot00000000000000#include "syscall/sysnum.h" static const Sysnum sysnums_i386[] = { [ 0 ] = PR_restart_syscall, [ 1 ] = PR_exit, [ 2 ] = PR_fork, [ 3 ] = PR_read, [ 4 ] = PR_write, [ 5 ] = PR_open, [ 6 ] = PR_close, [ 7 ] = PR_waitpid, [ 8 ] = PR_creat, [ 9 ] = PR_link, [ 10 ] = PR_unlink, [ 11 ] = PR_execve, [ 12 ] = PR_chdir, [ 13 ] = PR_time, [ 14 ] = PR_mknod, [ 15 ] = PR_chmod, [ 16 ] = PR_lchown, [ 17 ] = PR_break, [ 18 ] = PR_oldstat, [ 19 ] = PR_lseek, [ 20 ] = PR_getpid, [ 21 ] = PR_mount, [ 22 ] = PR_umount, [ 23 ] = PR_setuid, [ 24 ] = PR_getuid, [ 25 ] = PR_stime, [ 26 ] = PR_ptrace, [ 27 ] = PR_alarm, [ 28 ] = PR_oldfstat, [ 29 ] = PR_pause, [ 30 ] = PR_utime, [ 31 ] = PR_stty, [ 32 ] = PR_gtty, [ 33 ] = PR_access, [ 34 ] = PR_nice, [ 35 ] = PR_ftime, [ 36 ] = PR_sync, [ 37 ] = PR_kill, [ 38 ] = PR_rename, [ 39 ] = PR_mkdir, [ 40 ] = PR_rmdir, [ 41 ] = PR_dup, [ 42 ] = PR_pipe, [ 43 ] = PR_times, [ 44 ] = PR_prof, [ 45 ] = PR_brk, [ 46 ] = PR_setgid, [ 47 ] = PR_getgid, [ 48 ] = PR_signal, [ 49 ] = PR_geteuid, [ 50 ] = PR_getegid, [ 51 ] = PR_acct, [ 52 ] = PR_umount2, [ 53 ] = PR_lock, [ 54 ] = PR_ioctl, [ 55 ] = PR_fcntl, [ 56 ] = PR_mpx, [ 57 ] = PR_setpgid, [ 58 ] = PR_ulimit, [ 59 ] = PR_oldolduname, [ 60 ] = PR_umask, [ 61 ] = PR_chroot, [ 62 ] = PR_ustat, [ 63 ] = PR_dup2, [ 64 ] = PR_getppid, [ 65 ] = PR_getpgrp, [ 66 ] = PR_setsid, [ 67 ] = PR_sigaction, [ 68 ] = PR_sgetmask, [ 69 ] = PR_ssetmask, [ 70 ] = PR_setreuid, [ 71 ] = PR_setregid, [ 72 ] = PR_sigsuspend, [ 73 ] = PR_sigpending, [ 74 ] = PR_sethostname, [ 75 ] = PR_setrlimit, [ 76 ] = PR_getrlimit, [ 77 ] = PR_getrusage, [ 78 ] = PR_gettimeofday, [ 79 ] = PR_settimeofday, [ 80 ] = PR_getgroups, [ 81 ] = PR_setgroups, [ 82 ] = PR_select, [ 83 ] = PR_symlink, [ 84 ] = PR_oldlstat, [ 85 ] = PR_readlink, [ 86 ] = PR_uselib, [ 87 ] = PR_swapon, [ 88 ] = PR_reboot, [ 89 ] = PR_readdir, [ 90 ] = PR_mmap, [ 91 ] = PR_munmap, [ 92 ] = PR_truncate, [ 93 ] = PR_ftruncate, [ 94 ] = PR_fchmod, [ 95 ] = PR_fchown, [ 96 ] = PR_getpriority, [ 97 ] = PR_setpriority, [ 98 ] = PR_profil, [ 99 ] = PR_statfs, [ 100 ] = PR_fstatfs, [ 101 ] = PR_ioperm, [ 102 ] = PR_socketcall, [ 103 ] = PR_syslog, [ 104 ] = PR_setitimer, [ 105 ] = PR_getitimer, [ 106 ] = PR_stat, [ 107 ] = PR_lstat, [ 108 ] = PR_fstat, [ 109 ] = PR_olduname, [ 110 ] = PR_iopl, [ 111 ] = PR_vhangup, [ 112 ] = PR_idle, [ 113 ] = PR_vm86old, [ 114 ] = PR_wait4, [ 115 ] = PR_swapoff, [ 116 ] = PR_sysinfo, [ 117 ] = PR_ipc, [ 118 ] = PR_fsync, [ 119 ] = PR_sigreturn, [ 120 ] = PR_clone, [ 121 ] = PR_setdomainname, [ 122 ] = PR_uname, [ 123 ] = PR_modify_ldt, [ 124 ] = PR_adjtimex, [ 125 ] = PR_mprotect, [ 126 ] = PR_sigprocmask, [ 127 ] = PR_create_module, [ 128 ] = PR_init_module, [ 129 ] = PR_delete_module, [ 130 ] = PR_get_kernel_syms, [ 131 ] = PR_quotactl, [ 132 ] = PR_getpgid, [ 133 ] = PR_fchdir, [ 134 ] = PR_bdflush, [ 135 ] = PR_sysfs, [ 136 ] = PR_personality, [ 137 ] = PR_afs_syscall, [ 138 ] = PR_setfsuid, [ 139 ] = PR_setfsgid, [ 140 ] = PR__llseek, [ 141 ] = PR_getdents, [ 142 ] = PR__newselect, [ 143 ] = PR_flock, [ 144 ] = PR_msync, [ 145 ] = PR_readv, [ 146 ] = PR_writev, [ 147 ] = PR_getsid, [ 148 ] = PR_fdatasync, [ 149 ] = PR__sysctl, [ 150 ] = PR_mlock, [ 151 ] = PR_munlock, [ 152 ] = PR_mlockall, [ 153 ] = PR_munlockall, [ 154 ] = PR_sched_setparam, [ 155 ] = PR_sched_getparam, [ 156 ] = PR_sched_setscheduler, [ 157 ] = PR_sched_getscheduler, [ 158 ] = PR_sched_yield, [ 159 ] = PR_sched_get_priority_max, [ 160 ] = PR_sched_get_priority_min, [ 161 ] = PR_sched_rr_get_interval, [ 162 ] = PR_nanosleep, [ 163 ] = PR_mremap, [ 164 ] = PR_setresuid, [ 165 ] = PR_getresuid, [ 166 ] = PR_vm86, [ 167 ] = PR_query_module, [ 168 ] = PR_poll, [ 169 ] = PR_nfsservctl, [ 170 ] = PR_setresgid, [ 171 ] = PR_getresgid, [ 172 ] = PR_prctl, [ 173 ] = PR_rt_sigreturn, [ 174 ] = PR_rt_sigaction, [ 175 ] = PR_rt_sigprocmask, [ 176 ] = PR_rt_sigpending, [ 177 ] = PR_rt_sigtimedwait, [ 178 ] = PR_rt_sigqueueinfo, [ 179 ] = PR_rt_sigsuspend, [ 180 ] = PR_pread64, [ 181 ] = PR_pwrite64, [ 182 ] = PR_chown, [ 183 ] = PR_getcwd, [ 184 ] = PR_capget, [ 185 ] = PR_capset, [ 186 ] = PR_sigaltstack, [ 187 ] = PR_sendfile, [ 188 ] = PR_getpmsg, [ 189 ] = PR_putpmsg, [ 190 ] = PR_vfork, [ 191 ] = PR_ugetrlimit, [ 192 ] = PR_mmap2, [ 193 ] = PR_truncate64, [ 194 ] = PR_ftruncate64, [ 195 ] = PR_stat64, [ 196 ] = PR_lstat64, [ 197 ] = PR_fstat64, [ 198 ] = PR_lchown32, [ 199 ] = PR_getuid32, [ 200 ] = PR_getgid32, [ 201 ] = PR_geteuid32, [ 202 ] = PR_getegid32, [ 203 ] = PR_setreuid32, [ 204 ] = PR_setregid32, [ 205 ] = PR_getgroups32, [ 206 ] = PR_setgroups32, [ 207 ] = PR_fchown32, [ 208 ] = PR_setresuid32, [ 209 ] = PR_getresuid32, [ 210 ] = PR_setresgid32, [ 211 ] = PR_getresgid32, [ 212 ] = PR_chown32, [ 213 ] = PR_setuid32, [ 214 ] = PR_setgid32, [ 215 ] = PR_setfsuid32, [ 216 ] = PR_setfsgid32, [ 217 ] = PR_pivot_root, [ 218 ] = PR_mincore, [ 219 ] = PR_madvise, [ 220 ] = PR_getdents64, [ 221 ] = PR_fcntl64, [ 224 ] = PR_gettid, [ 225 ] = PR_readahead, [ 226 ] = PR_setxattr, [ 227 ] = PR_lsetxattr, [ 228 ] = PR_fsetxattr, [ 229 ] = PR_getxattr, [ 230 ] = PR_lgetxattr, [ 231 ] = PR_fgetxattr, [ 232 ] = PR_listxattr, [ 233 ] = PR_llistxattr, [ 234 ] = PR_flistxattr, [ 235 ] = PR_removexattr, [ 236 ] = PR_lremovexattr, [ 237 ] = PR_fremovexattr, [ 238 ] = PR_tkill, [ 239 ] = PR_sendfile64, [ 240 ] = PR_futex, [ 241 ] = PR_sched_setaffinity, [ 242 ] = PR_sched_getaffinity, [ 243 ] = PR_set_thread_area, [ 244 ] = PR_get_thread_area, [ 245 ] = PR_io_setup, [ 246 ] = PR_io_destroy, [ 247 ] = PR_io_getevents, [ 248 ] = PR_io_submit, [ 249 ] = PR_io_cancel, [ 250 ] = PR_fadvise64, [ 252 ] = PR_exit_group, [ 253 ] = PR_lookup_dcookie, [ 254 ] = PR_epoll_create, [ 255 ] = PR_epoll_ctl, [ 256 ] = PR_epoll_wait, [ 257 ] = PR_remap_file_pages, [ 258 ] = PR_set_tid_address, [ 259 ] = PR_timer_create, [ 260 ] = PR_timer_settime, [ 261 ] = PR_timer_gettime, [ 262 ] = PR_timer_getoverrun, [ 263 ] = PR_timer_delete, [ 264 ] = PR_clock_settime, [ 265 ] = PR_clock_gettime, [ 266 ] = PR_clock_getres, [ 267 ] = PR_clock_nanosleep, [ 268 ] = PR_statfs64, [ 269 ] = PR_fstatfs64, [ 270 ] = PR_tgkill, [ 271 ] = PR_utimes, [ 272 ] = PR_fadvise64_64, [ 273 ] = PR_vserver, [ 274 ] = PR_mbind, [ 275 ] = PR_get_mempolicy, [ 276 ] = PR_set_mempolicy, [ 277 ] = PR_mq_open, [ 278 ] = PR_mq_unlink, [ 279 ] = PR_mq_timedsend, [ 280 ] = PR_mq_timedreceive, [ 281 ] = PR_mq_notify, [ 282 ] = PR_mq_getsetattr, [ 283 ] = PR_kexec_load, [ 284 ] = PR_waitid, [ 286 ] = PR_add_key, [ 287 ] = PR_request_key, [ 288 ] = PR_keyctl, [ 289 ] = PR_ioprio_set, [ 290 ] = PR_ioprio_get, [ 291 ] = PR_inotify_init, [ 292 ] = PR_inotify_add_watch, [ 293 ] = PR_inotify_rm_watch, [ 294 ] = PR_migrate_pages, [ 295 ] = PR_openat, [ 296 ] = PR_mkdirat, [ 297 ] = PR_mknodat, [ 298 ] = PR_fchownat, [ 299 ] = PR_futimesat, [ 300 ] = PR_fstatat64, [ 301 ] = PR_unlinkat, [ 302 ] = PR_renameat, [ 303 ] = PR_linkat, [ 304 ] = PR_symlinkat, [ 305 ] = PR_readlinkat, [ 306 ] = PR_fchmodat, [ 307 ] = PR_faccessat, [ 308 ] = PR_pselect6, [ 309 ] = PR_ppoll, [ 310 ] = PR_unshare, [ 311 ] = PR_set_robust_list, [ 312 ] = PR_get_robust_list, [ 313 ] = PR_splice, [ 314 ] = PR_sync_file_range, [ 315 ] = PR_tee, [ 316 ] = PR_vmsplice, [ 317 ] = PR_move_pages, [ 318 ] = PR_getcpu, [ 319 ] = PR_epoll_pwait, [ 320 ] = PR_utimensat, [ 321 ] = PR_signalfd, [ 322 ] = PR_timerfd_create, [ 323 ] = PR_eventfd, [ 324 ] = PR_fallocate, [ 325 ] = PR_timerfd_settime, [ 326 ] = PR_timerfd_gettime, [ 327 ] = PR_signalfd4, [ 328 ] = PR_eventfd2, [ 329 ] = PR_epoll_create1, [ 330 ] = PR_dup3, [ 331 ] = PR_pipe2, [ 332 ] = PR_inotify_init1, [ 333 ] = PR_preadv, [ 334 ] = PR_pwritev, [ 335 ] = PR_rt_tgsigqueueinfo, [ 336 ] = PR_perf_event_open, [ 337 ] = PR_recvmmsg, [ 338 ] = PR_fanotify_init, [ 339 ] = PR_fanotify_mark, [ 340 ] = PR_prlimit64, [ 341 ] = PR_name_to_handle_at, [ 342 ] = PR_open_by_handle_at, [ 343 ] = PR_clock_adjtime, [ 344 ] = PR_syncfs, [ 345 ] = PR_sendmmsg, [ 346 ] = PR_setns, [ 347 ] = PR_process_vm_readv, [ 348 ] = PR_process_vm_writev, [ 349 ] = PR_kcmp, [ 350 ] = PR_finit_module, [ 351 ] = PR_sched_setattr, [ 352 ] = PR_sched_getattr, [ 353 ] = PR_renameat2, [ 383 ] = PR_statx, [ 412 ] = PR_utimensat_time64, }; proot-5.4.0/src/syscall/sysnums-sh4.h000066400000000000000000000203651442763353300175320ustar00rootroot00000000000000#include "syscall/sysnum.h" static const Sysnum sysnums_sh4[] = { [ 0 ] = PR_restart_syscall, [ 1 ] = PR_exit, [ 2 ] = PR_fork, [ 3 ] = PR_read, [ 4 ] = PR_write, [ 5 ] = PR_open, [ 6 ] = PR_close, [ 7 ] = PR_waitpid, [ 8 ] = PR_creat, [ 9 ] = PR_link, [ 10 ] = PR_unlink, [ 11 ] = PR_execve, [ 12 ] = PR_chdir, [ 13 ] = PR_time, [ 14 ] = PR_mknod, [ 15 ] = PR_chmod, [ 16 ] = PR_lchown, [ 18 ] = PR_oldstat, [ 19 ] = PR_lseek, [ 20 ] = PR_getpid, [ 21 ] = PR_mount, [ 22 ] = PR_umount, [ 23 ] = PR_setuid, [ 24 ] = PR_getuid, [ 25 ] = PR_stime, [ 26 ] = PR_ptrace, [ 27 ] = PR_alarm, [ 28 ] = PR_oldfstat, [ 29 ] = PR_pause, [ 30 ] = PR_utime, [ 33 ] = PR_access, [ 34 ] = PR_nice, [ 36 ] = PR_sync, [ 37 ] = PR_kill, [ 38 ] = PR_rename, [ 39 ] = PR_mkdir, [ 40 ] = PR_rmdir, [ 41 ] = PR_dup, [ 42 ] = PR_pipe, [ 43 ] = PR_times, [ 45 ] = PR_brk, [ 46 ] = PR_setgid, [ 47 ] = PR_getgid, [ 48 ] = PR_signal, [ 49 ] = PR_geteuid, [ 50 ] = PR_getegid, [ 51 ] = PR_acct, [ 52 ] = PR_umount2, [ 54 ] = PR_ioctl, [ 55 ] = PR_fcntl, [ 57 ] = PR_setpgid, [ 60 ] = PR_umask, [ 61 ] = PR_chroot, [ 62 ] = PR_ustat, [ 63 ] = PR_dup2, [ 64 ] = PR_getppid, [ 65 ] = PR_getpgrp, [ 66 ] = PR_setsid, [ 67 ] = PR_sigaction, [ 68 ] = PR_sgetmask, [ 69 ] = PR_ssetmask, [ 70 ] = PR_setreuid, [ 71 ] = PR_setregid, [ 72 ] = PR_sigsuspend, [ 73 ] = PR_sigpending, [ 74 ] = PR_sethostname, [ 75 ] = PR_setrlimit, [ 76 ] = PR_getrlimit, [ 77 ] = PR_getrusage, [ 78 ] = PR_gettimeofday, [ 79 ] = PR_settimeofday, [ 80 ] = PR_getgroups, [ 81 ] = PR_setgroups, [ 83 ] = PR_symlink, [ 84 ] = PR_oldlstat, [ 85 ] = PR_readlink, [ 86 ] = PR_uselib, [ 87 ] = PR_swapon, [ 88 ] = PR_reboot, [ 89 ] = PR_readdir, [ 90 ] = PR_mmap, [ 91 ] = PR_munmap, [ 92 ] = PR_truncate, [ 93 ] = PR_ftruncate, [ 94 ] = PR_fchmod, [ 95 ] = PR_fchown, [ 96 ] = PR_getpriority, [ 97 ] = PR_setpriority, [ 99 ] = PR_statfs, [ 100 ] = PR_fstatfs, [ 102 ] = PR_socketcall, [ 103 ] = PR_syslog, [ 104 ] = PR_setitimer, [ 105 ] = PR_getitimer, [ 106 ] = PR_stat, [ 107 ] = PR_lstat, [ 108 ] = PR_fstat, [ 109 ] = PR_olduname, [ 111 ] = PR_vhangup, [ 114 ] = PR_wait4, [ 115 ] = PR_swapoff, [ 116 ] = PR_sysinfo, [ 117 ] = PR_ipc, [ 118 ] = PR_fsync, [ 119 ] = PR_sigreturn, [ 120 ] = PR_clone, [ 121 ] = PR_setdomainname, [ 122 ] = PR_uname, [ 123 ] = PR_cacheflush, [ 124 ] = PR_adjtimex, [ 125 ] = PR_mprotect, [ 126 ] = PR_sigprocmask, [ 128 ] = PR_init_module, [ 129 ] = PR_delete_module, [ 131 ] = PR_quotactl, [ 132 ] = PR_getpgid, [ 133 ] = PR_fchdir, [ 134 ] = PR_bdflush, [ 135 ] = PR_sysfs, [ 136 ] = PR_personality, [ 138 ] = PR_setfsuid, [ 139 ] = PR_setfsgid, [ 140 ] = PR__llseek, [ 141 ] = PR_getdents, [ 142 ] = PR__newselect, [ 143 ] = PR_flock, [ 144 ] = PR_msync, [ 145 ] = PR_readv, [ 146 ] = PR_writev, [ 147 ] = PR_getsid, [ 148 ] = PR_fdatasync, [ 149 ] = PR__sysctl, [ 150 ] = PR_mlock, [ 151 ] = PR_munlock, [ 152 ] = PR_mlockall, [ 153 ] = PR_munlockall, [ 154 ] = PR_sched_setparam, [ 155 ] = PR_sched_getparam, [ 156 ] = PR_sched_setscheduler, [ 157 ] = PR_sched_getscheduler, [ 158 ] = PR_sched_yield, [ 159 ] = PR_sched_get_priority_max, [ 160 ] = PR_sched_get_priority_min, [ 161 ] = PR_sched_rr_get_interval, [ 162 ] = PR_nanosleep, [ 163 ] = PR_mremap, [ 164 ] = PR_setresuid, [ 165 ] = PR_getresuid, [ 168 ] = PR_poll, [ 169 ] = PR_nfsservctl, [ 170 ] = PR_setresgid, [ 171 ] = PR_getresgid, [ 172 ] = PR_prctl, [ 173 ] = PR_rt_sigreturn, [ 174 ] = PR_rt_sigaction, [ 175 ] = PR_rt_sigprocmask, [ 176 ] = PR_rt_sigpending, [ 177 ] = PR_rt_sigtimedwait, [ 178 ] = PR_rt_sigqueueinfo, [ 179 ] = PR_rt_sigsuspend, [ 180 ] = PR_pread64, [ 181 ] = PR_pwrite64, [ 182 ] = PR_chown, [ 183 ] = PR_getcwd, [ 184 ] = PR_capget, [ 185 ] = PR_capset, [ 186 ] = PR_sigaltstack, [ 187 ] = PR_sendfile, [ 190 ] = PR_vfork, [ 191 ] = PR_ugetrlimit, [ 192 ] = PR_mmap2, [ 193 ] = PR_truncate64, [ 194 ] = PR_ftruncate64, [ 195 ] = PR_stat64, [ 196 ] = PR_lstat64, [ 197 ] = PR_fstat64, [ 198 ] = PR_lchown32, [ 199 ] = PR_getuid32, [ 200 ] = PR_getgid32, [ 201 ] = PR_geteuid32, [ 202 ] = PR_getegid32, [ 203 ] = PR_setreuid32, [ 204 ] = PR_setregid32, [ 205 ] = PR_getgroups32, [ 206 ] = PR_setgroups32, [ 207 ] = PR_fchown32, [ 208 ] = PR_setresuid32, [ 209 ] = PR_getresuid32, [ 210 ] = PR_setresgid32, [ 211 ] = PR_getresgid32, [ 212 ] = PR_chown32, [ 213 ] = PR_setuid32, [ 214 ] = PR_setgid32, [ 215 ] = PR_setfsuid32, [ 216 ] = PR_setfsgid32, [ 217 ] = PR_pivot_root, [ 218 ] = PR_mincore, [ 219 ] = PR_madvise, [ 220 ] = PR_getdents64, [ 221 ] = PR_fcntl64, [ 224 ] = PR_gettid, [ 225 ] = PR_readahead, [ 226 ] = PR_setxattr, [ 227 ] = PR_lsetxattr, [ 228 ] = PR_fsetxattr, [ 229 ] = PR_getxattr, [ 230 ] = PR_lgetxattr, [ 231 ] = PR_fgetxattr, [ 232 ] = PR_listxattr, [ 233 ] = PR_llistxattr, [ 234 ] = PR_flistxattr, [ 235 ] = PR_removexattr, [ 236 ] = PR_lremovexattr, [ 237 ] = PR_fremovexattr, [ 238 ] = PR_tkill, [ 239 ] = PR_sendfile64, [ 240 ] = PR_futex, [ 241 ] = PR_sched_setaffinity, [ 242 ] = PR_sched_getaffinity, [ 245 ] = PR_io_setup, [ 246 ] = PR_io_destroy, [ 247 ] = PR_io_getevents, [ 248 ] = PR_io_submit, [ 249 ] = PR_io_cancel, [ 250 ] = PR_fadvise64, [ 252 ] = PR_exit_group, [ 253 ] = PR_lookup_dcookie, [ 254 ] = PR_epoll_create, [ 255 ] = PR_epoll_ctl, [ 256 ] = PR_epoll_wait, [ 257 ] = PR_remap_file_pages, [ 258 ] = PR_set_tid_address, [ 259 ] = PR_timer_create, [ 260 ] = PR_timer_settime, [ 261 ] = PR_timer_gettime, [ 262 ] = PR_timer_getoverrun, [ 263 ] = PR_timer_delete, [ 264 ] = PR_clock_settime, [ 265 ] = PR_clock_gettime, [ 266 ] = PR_clock_getres, [ 267 ] = PR_clock_nanosleep, [ 268 ] = PR_statfs64, [ 269 ] = PR_fstatfs64, [ 270 ] = PR_tgkill, [ 271 ] = PR_utimes, [ 272 ] = PR_fadvise64_64, [ 274 ] = PR_mbind, [ 275 ] = PR_get_mempolicy, [ 276 ] = PR_set_mempolicy, [ 277 ] = PR_mq_open, [ 278 ] = PR_mq_unlink, [ 279 ] = PR_mq_timedsend, [ 280 ] = PR_mq_timedreceive, [ 281 ] = PR_mq_notify, [ 282 ] = PR_mq_getsetattr, [ 283 ] = PR_kexec_load, [ 284 ] = PR_waitid, [ 285 ] = PR_add_key, [ 286 ] = PR_request_key, [ 287 ] = PR_keyctl, [ 288 ] = PR_ioprio_set, [ 289 ] = PR_ioprio_get, [ 290 ] = PR_inotify_init, [ 291 ] = PR_inotify_add_watch, [ 292 ] = PR_inotify_rm_watch, [ 294 ] = PR_migrate_pages, [ 295 ] = PR_openat, [ 296 ] = PR_mkdirat, [ 297 ] = PR_mknodat, [ 298 ] = PR_fchownat, [ 299 ] = PR_futimesat, [ 300 ] = PR_fstatat64, [ 301 ] = PR_unlinkat, [ 302 ] = PR_renameat, [ 303 ] = PR_linkat, [ 304 ] = PR_symlinkat, [ 305 ] = PR_readlinkat, [ 306 ] = PR_fchmodat, [ 307 ] = PR_faccessat, [ 308 ] = PR_pselect6, [ 309 ] = PR_ppoll, [ 310 ] = PR_unshare, [ 311 ] = PR_set_robust_list, [ 312 ] = PR_get_robust_list, [ 313 ] = PR_splice, [ 314 ] = PR_sync_file_range, [ 315 ] = PR_tee, [ 316 ] = PR_vmsplice, [ 317 ] = PR_move_pages, [ 318 ] = PR_getcpu, [ 319 ] = PR_epoll_pwait, [ 320 ] = PR_utimensat, [ 321 ] = PR_signalfd, [ 322 ] = PR_timerfd_create, [ 323 ] = PR_eventfd, [ 324 ] = PR_fallocate, [ 325 ] = PR_timerfd_settime, [ 326 ] = PR_timerfd_gettime, [ 327 ] = PR_signalfd4, [ 328 ] = PR_eventfd2, [ 329 ] = PR_epoll_create1, [ 330 ] = PR_dup3, [ 331 ] = PR_pipe2, [ 332 ] = PR_inotify_init1, [ 333 ] = PR_preadv, [ 334 ] = PR_pwritev, [ 335 ] = PR_rt_tgsigqueueinfo, [ 336 ] = PR_perf_event_open, [ 337 ] = PR_fanotify_init, [ 338 ] = PR_fanotify_mark, [ 339 ] = PR_prlimit64, [ 340 ] = PR_socket, [ 341 ] = PR_bind, [ 342 ] = PR_connect, [ 343 ] = PR_listen, [ 344 ] = PR_accept, [ 345 ] = PR_getsockname, [ 346 ] = PR_getpeername, [ 347 ] = PR_socketpair, [ 348 ] = PR_send, [ 349 ] = PR_sendto, [ 350 ] = PR_recv, [ 351 ] = PR_recvfrom, [ 352 ] = PR_shutdown, [ 353 ] = PR_setsockopt, [ 354 ] = PR_getsockopt, [ 355 ] = PR_sendmsg, [ 356 ] = PR_recvmsg, [ 357 ] = PR_recvmmsg, [ 358 ] = PR_accept4, [ 359 ] = PR_name_to_handle_at, [ 360 ] = PR_open_by_handle_at, [ 361 ] = PR_clock_adjtime, [ 362 ] = PR_syncfs, [ 363 ] = PR_sendmmsg, [ 364 ] = PR_setns, [ 365 ] = PR_process_vm_readv, [ 366 ] = PR_process_vm_writev, [ 367 ] = PR_kcmp, [ 368 ] = PR_finit_module, [ 369 ] = PR_sched_setattr, [ 370 ] = PR_sched_getattr, [ 371 ] = PR_renameat2, }; proot-5.4.0/src/syscall/sysnums-x32.h000066400000000000000000000166001442763353300174450ustar00rootroot00000000000000#include "syscall/sysnum.h" static const Sysnum sysnums_x32[] = { [ 0 ] = PR_read, [ 1 ] = PR_write, [ 2 ] = PR_open, [ 3 ] = PR_close, [ 4 ] = PR_stat, [ 5 ] = PR_fstat, [ 6 ] = PR_lstat, [ 7 ] = PR_poll, [ 8 ] = PR_lseek, [ 9 ] = PR_mmap, [ 10 ] = PR_mprotect, [ 11 ] = PR_munmap, [ 12 ] = PR_brk, [ 14 ] = PR_rt_sigprocmask, [ 17 ] = PR_pread64, [ 18 ] = PR_pwrite64, [ 21 ] = PR_access, [ 22 ] = PR_pipe, [ 23 ] = PR_select, [ 24 ] = PR_sched_yield, [ 25 ] = PR_mremap, [ 26 ] = PR_msync, [ 27 ] = PR_mincore, [ 28 ] = PR_madvise, [ 29 ] = PR_shmget, [ 30 ] = PR_shmat, [ 31 ] = PR_shmctl, [ 32 ] = PR_dup, [ 33 ] = PR_dup2, [ 34 ] = PR_pause, [ 35 ] = PR_nanosleep, [ 36 ] = PR_getitimer, [ 37 ] = PR_alarm, [ 38 ] = PR_setitimer, [ 39 ] = PR_getpid, [ 40 ] = PR_sendfile, [ 41 ] = PR_socket, [ 42 ] = PR_connect, [ 43 ] = PR_accept, [ 44 ] = PR_sendto, [ 48 ] = PR_shutdown, [ 49 ] = PR_bind, [ 50 ] = PR_listen, [ 51 ] = PR_getsockname, [ 52 ] = PR_getpeername, [ 53 ] = PR_socketpair, [ 56 ] = PR_clone, [ 57 ] = PR_fork, [ 58 ] = PR_vfork, [ 60 ] = PR_exit, [ 61 ] = PR_wait4, [ 62 ] = PR_kill, [ 63 ] = PR_uname, [ 64 ] = PR_semget, [ 65 ] = PR_semop, [ 66 ] = PR_semctl, [ 67 ] = PR_shmdt, [ 68 ] = PR_msgget, [ 69 ] = PR_msgsnd, [ 70 ] = PR_msgrcv, [ 71 ] = PR_msgctl, [ 72 ] = PR_fcntl, [ 73 ] = PR_flock, [ 74 ] = PR_fsync, [ 75 ] = PR_fdatasync, [ 76 ] = PR_truncate, [ 77 ] = PR_ftruncate, [ 78 ] = PR_getdents, [ 79 ] = PR_getcwd, [ 80 ] = PR_chdir, [ 81 ] = PR_fchdir, [ 82 ] = PR_rename, [ 83 ] = PR_mkdir, [ 84 ] = PR_rmdir, [ 85 ] = PR_creat, [ 86 ] = PR_link, [ 87 ] = PR_unlink, [ 88 ] = PR_symlink, [ 89 ] = PR_readlink, [ 90 ] = PR_chmod, [ 91 ] = PR_fchmod, [ 92 ] = PR_chown, [ 93 ] = PR_fchown, [ 94 ] = PR_lchown, [ 95 ] = PR_umask, [ 96 ] = PR_gettimeofday, [ 97 ] = PR_getrlimit, [ 98 ] = PR_getrusage, [ 99 ] = PR_sysinfo, [ 100 ] = PR_times, [ 102 ] = PR_getuid, [ 103 ] = PR_syslog, [ 104 ] = PR_getgid, [ 105 ] = PR_setuid, [ 106 ] = PR_setgid, [ 107 ] = PR_geteuid, [ 108 ] = PR_getegid, [ 109 ] = PR_setpgid, [ 110 ] = PR_getppid, [ 111 ] = PR_getpgrp, [ 112 ] = PR_setsid, [ 113 ] = PR_setreuid, [ 114 ] = PR_setregid, [ 115 ] = PR_getgroups, [ 116 ] = PR_setgroups, [ 117 ] = PR_setresuid, [ 118 ] = PR_getresuid, [ 119 ] = PR_setresgid, [ 120 ] = PR_getresgid, [ 121 ] = PR_getpgid, [ 122 ] = PR_setfsuid, [ 123 ] = PR_setfsgid, [ 124 ] = PR_getsid, [ 125 ] = PR_capget, [ 126 ] = PR_capset, [ 130 ] = PR_rt_sigsuspend, [ 132 ] = PR_utime, [ 133 ] = PR_mknod, [ 135 ] = PR_personality, [ 136 ] = PR_ustat, [ 137 ] = PR_statfs, [ 138 ] = PR_fstatfs, [ 139 ] = PR_sysfs, [ 140 ] = PR_getpriority, [ 141 ] = PR_setpriority, [ 142 ] = PR_sched_setparam, [ 143 ] = PR_sched_getparam, [ 144 ] = PR_sched_setscheduler, [ 145 ] = PR_sched_getscheduler, [ 146 ] = PR_sched_get_priority_max, [ 147 ] = PR_sched_get_priority_min, [ 148 ] = PR_sched_rr_get_interval, [ 149 ] = PR_mlock, [ 150 ] = PR_munlock, [ 151 ] = PR_mlockall, [ 152 ] = PR_munlockall, [ 153 ] = PR_vhangup, [ 154 ] = PR_modify_ldt, [ 155 ] = PR_pivot_root, [ 157 ] = PR_prctl, [ 158 ] = PR_arch_prctl, [ 159 ] = PR_adjtimex, [ 160 ] = PR_setrlimit, [ 161 ] = PR_chroot, [ 162 ] = PR_sync, [ 163 ] = PR_acct, [ 164 ] = PR_settimeofday, [ 165 ] = PR_mount, [ 166 ] = PR_umount2, [ 167 ] = PR_swapon, [ 168 ] = PR_swapoff, [ 169 ] = PR_reboot, [ 170 ] = PR_sethostname, [ 171 ] = PR_setdomainname, [ 172 ] = PR_iopl, [ 173 ] = PR_ioperm, [ 175 ] = PR_init_module, [ 176 ] = PR_delete_module, [ 179 ] = PR_quotactl, [ 181 ] = PR_getpmsg, [ 182 ] = PR_putpmsg, [ 183 ] = PR_afs_syscall, [ 184 ] = PR_tuxcall, [ 185 ] = PR_security, [ 186 ] = PR_gettid, [ 187 ] = PR_readahead, [ 188 ] = PR_setxattr, [ 189 ] = PR_lsetxattr, [ 190 ] = PR_fsetxattr, [ 191 ] = PR_getxattr, [ 192 ] = PR_lgetxattr, [ 193 ] = PR_fgetxattr, [ 194 ] = PR_listxattr, [ 195 ] = PR_llistxattr, [ 196 ] = PR_flistxattr, [ 197 ] = PR_removexattr, [ 198 ] = PR_lremovexattr, [ 199 ] = PR_fremovexattr, [ 200 ] = PR_tkill, [ 201 ] = PR_time, [ 202 ] = PR_futex, [ 203 ] = PR_sched_setaffinity, [ 204 ] = PR_sched_getaffinity, [ 206 ] = PR_io_setup, [ 207 ] = PR_io_destroy, [ 208 ] = PR_io_getevents, [ 209 ] = PR_io_submit, [ 210 ] = PR_io_cancel, [ 212 ] = PR_lookup_dcookie, [ 213 ] = PR_epoll_create, [ 216 ] = PR_remap_file_pages, [ 217 ] = PR_getdents64, [ 218 ] = PR_set_tid_address, [ 219 ] = PR_restart_syscall, [ 220 ] = PR_semtimedop, [ 221 ] = PR_fadvise64, [ 223 ] = PR_timer_settime, [ 224 ] = PR_timer_gettime, [ 225 ] = PR_timer_getoverrun, [ 226 ] = PR_timer_delete, [ 227 ] = PR_clock_settime, [ 228 ] = PR_clock_gettime, [ 229 ] = PR_clock_getres, [ 230 ] = PR_clock_nanosleep, [ 231 ] = PR_exit_group, [ 232 ] = PR_epoll_wait, [ 233 ] = PR_epoll_ctl, [ 234 ] = PR_tgkill, [ 235 ] = PR_utimes, [ 237 ] = PR_mbind, [ 238 ] = PR_set_mempolicy, [ 239 ] = PR_get_mempolicy, [ 240 ] = PR_mq_open, [ 241 ] = PR_mq_unlink, [ 242 ] = PR_mq_timedsend, [ 243 ] = PR_mq_timedreceive, [ 245 ] = PR_mq_getsetattr, [ 248 ] = PR_add_key, [ 249 ] = PR_request_key, [ 250 ] = PR_keyctl, [ 251 ] = PR_ioprio_set, [ 252 ] = PR_ioprio_get, [ 253 ] = PR_inotify_init, [ 254 ] = PR_inotify_add_watch, [ 255 ] = PR_inotify_rm_watch, [ 256 ] = PR_migrate_pages, [ 257 ] = PR_openat, [ 258 ] = PR_mkdirat, [ 259 ] = PR_mknodat, [ 260 ] = PR_fchownat, [ 261 ] = PR_futimesat, [ 262 ] = PR_newfstatat, [ 263 ] = PR_unlinkat, [ 264 ] = PR_renameat, [ 265 ] = PR_linkat, [ 266 ] = PR_symlinkat, [ 267 ] = PR_readlinkat, [ 268 ] = PR_fchmodat, [ 269 ] = PR_faccessat, [ 270 ] = PR_pselect6, [ 271 ] = PR_ppoll, [ 272 ] = PR_unshare, [ 275 ] = PR_splice, [ 276 ] = PR_tee, [ 277 ] = PR_sync_file_range, [ 280 ] = PR_utimensat, [ 281 ] = PR_epoll_pwait, [ 282 ] = PR_signalfd, [ 283 ] = PR_timerfd_create, [ 284 ] = PR_eventfd, [ 285 ] = PR_fallocate, [ 286 ] = PR_timerfd_settime, [ 287 ] = PR_timerfd_gettime, [ 288 ] = PR_accept4, [ 289 ] = PR_signalfd4, [ 290 ] = PR_eventfd2, [ 291 ] = PR_epoll_create1, [ 292 ] = PR_dup3, [ 293 ] = PR_pipe2, [ 294 ] = PR_inotify_init1, [ 298 ] = PR_perf_event_open, [ 300 ] = PR_fanotify_init, [ 301 ] = PR_fanotify_mark, [ 302 ] = PR_prlimit64, [ 303 ] = PR_name_to_handle_at, [ 304 ] = PR_open_by_handle_at, [ 305 ] = PR_clock_adjtime, [ 306 ] = PR_syncfs, [ 308 ] = PR_setns, [ 309 ] = PR_getcpu, [ 312 ] = PR_kcmp, [ 313 ] = PR_finit_module, [ 314 ] = PR_sched_setattr, [ 315 ] = PR_sched_getattr, [ 316 ] = PR_renameat2, [ 332 ] = PR_statx, [ 439 ] = PR_faccessat2, [ 512 ] = PR_rt_sigaction, [ 513 ] = PR_rt_sigreturn, [ 514 ] = PR_ioctl, [ 515 ] = PR_readv, [ 516 ] = PR_writev, [ 517 ] = PR_recvfrom, [ 518 ] = PR_sendmsg, [ 519 ] = PR_recvmsg, [ 520 ] = PR_execve, [ 521 ] = PR_ptrace, [ 522 ] = PR_rt_sigpending, [ 523 ] = PR_rt_sigtimedwait, [ 524 ] = PR_rt_sigqueueinfo, [ 525 ] = PR_sigaltstack, [ 526 ] = PR_timer_create, [ 527 ] = PR_mq_notify, [ 528 ] = PR_kexec_load, [ 529 ] = PR_waitid, [ 530 ] = PR_set_robust_list, [ 531 ] = PR_get_robust_list, [ 532 ] = PR_vmsplice, [ 533 ] = PR_move_pages, [ 534 ] = PR_preadv, [ 535 ] = PR_pwritev, [ 536 ] = PR_rt_tgsigqueueinfo, [ 537 ] = PR_recvmmsg, [ 538 ] = PR_sendmmsg, [ 539 ] = PR_process_vm_readv, [ 540 ] = PR_process_vm_writev, [ 541 ] = PR_setsockopt, [ 542 ] = PR_getsockopt, }; proot-5.4.0/src/syscall/sysnums-x86_64.h000066400000000000000000000172471442763353300177770ustar00rootroot00000000000000#include "syscall/sysnum.h" static const Sysnum sysnums_x86_64[] = { [ 0 ] = PR_read, [ 1 ] = PR_write, [ 2 ] = PR_open, [ 3 ] = PR_close, [ 4 ] = PR_stat, [ 5 ] = PR_fstat, [ 6 ] = PR_lstat, [ 7 ] = PR_poll, [ 8 ] = PR_lseek, [ 9 ] = PR_mmap, [ 10 ] = PR_mprotect, [ 11 ] = PR_munmap, [ 12 ] = PR_brk, [ 13 ] = PR_rt_sigaction, [ 14 ] = PR_rt_sigprocmask, [ 15 ] = PR_rt_sigreturn, [ 16 ] = PR_ioctl, [ 17 ] = PR_pread64, [ 18 ] = PR_pwrite64, [ 19 ] = PR_readv, [ 20 ] = PR_writev, [ 21 ] = PR_access, [ 22 ] = PR_pipe, [ 23 ] = PR_select, [ 24 ] = PR_sched_yield, [ 25 ] = PR_mremap, [ 26 ] = PR_msync, [ 27 ] = PR_mincore, [ 28 ] = PR_madvise, [ 29 ] = PR_shmget, [ 30 ] = PR_shmat, [ 31 ] = PR_shmctl, [ 32 ] = PR_dup, [ 33 ] = PR_dup2, [ 34 ] = PR_pause, [ 35 ] = PR_nanosleep, [ 36 ] = PR_getitimer, [ 37 ] = PR_alarm, [ 38 ] = PR_setitimer, [ 39 ] = PR_getpid, [ 40 ] = PR_sendfile, [ 41 ] = PR_socket, [ 42 ] = PR_connect, [ 43 ] = PR_accept, [ 44 ] = PR_sendto, [ 45 ] = PR_recvfrom, [ 46 ] = PR_sendmsg, [ 47 ] = PR_recvmsg, [ 48 ] = PR_shutdown, [ 49 ] = PR_bind, [ 50 ] = PR_listen, [ 51 ] = PR_getsockname, [ 52 ] = PR_getpeername, [ 53 ] = PR_socketpair, [ 54 ] = PR_setsockopt, [ 55 ] = PR_getsockopt, [ 56 ] = PR_clone, [ 57 ] = PR_fork, [ 58 ] = PR_vfork, [ 59 ] = PR_execve, [ 60 ] = PR_exit, [ 61 ] = PR_wait4, [ 62 ] = PR_kill, [ 63 ] = PR_uname, [ 64 ] = PR_semget, [ 65 ] = PR_semop, [ 66 ] = PR_semctl, [ 67 ] = PR_shmdt, [ 68 ] = PR_msgget, [ 69 ] = PR_msgsnd, [ 70 ] = PR_msgrcv, [ 71 ] = PR_msgctl, [ 72 ] = PR_fcntl, [ 73 ] = PR_flock, [ 74 ] = PR_fsync, [ 75 ] = PR_fdatasync, [ 76 ] = PR_truncate, [ 77 ] = PR_ftruncate, [ 78 ] = PR_getdents, [ 79 ] = PR_getcwd, [ 80 ] = PR_chdir, [ 81 ] = PR_fchdir, [ 82 ] = PR_rename, [ 83 ] = PR_mkdir, [ 84 ] = PR_rmdir, [ 85 ] = PR_creat, [ 86 ] = PR_link, [ 87 ] = PR_unlink, [ 88 ] = PR_symlink, [ 89 ] = PR_readlink, [ 90 ] = PR_chmod, [ 91 ] = PR_fchmod, [ 92 ] = PR_chown, [ 93 ] = PR_fchown, [ 94 ] = PR_lchown, [ 95 ] = PR_umask, [ 96 ] = PR_gettimeofday, [ 97 ] = PR_getrlimit, [ 98 ] = PR_getrusage, [ 99 ] = PR_sysinfo, [ 100 ] = PR_times, [ 101 ] = PR_ptrace, [ 102 ] = PR_getuid, [ 103 ] = PR_syslog, [ 104 ] = PR_getgid, [ 105 ] = PR_setuid, [ 106 ] = PR_setgid, [ 107 ] = PR_geteuid, [ 108 ] = PR_getegid, [ 109 ] = PR_setpgid, [ 110 ] = PR_getppid, [ 111 ] = PR_getpgrp, [ 112 ] = PR_setsid, [ 113 ] = PR_setreuid, [ 114 ] = PR_setregid, [ 115 ] = PR_getgroups, [ 116 ] = PR_setgroups, [ 117 ] = PR_setresuid, [ 118 ] = PR_getresuid, [ 119 ] = PR_setresgid, [ 120 ] = PR_getresgid, [ 121 ] = PR_getpgid, [ 122 ] = PR_setfsuid, [ 123 ] = PR_setfsgid, [ 124 ] = PR_getsid, [ 125 ] = PR_capget, [ 126 ] = PR_capset, [ 127 ] = PR_rt_sigpending, [ 128 ] = PR_rt_sigtimedwait, [ 129 ] = PR_rt_sigqueueinfo, [ 130 ] = PR_rt_sigsuspend, [ 131 ] = PR_sigaltstack, [ 132 ] = PR_utime, [ 133 ] = PR_mknod, [ 134 ] = PR_uselib, [ 135 ] = PR_personality, [ 136 ] = PR_ustat, [ 137 ] = PR_statfs, [ 138 ] = PR_fstatfs, [ 139 ] = PR_sysfs, [ 140 ] = PR_getpriority, [ 141 ] = PR_setpriority, [ 142 ] = PR_sched_setparam, [ 143 ] = PR_sched_getparam, [ 144 ] = PR_sched_setscheduler, [ 145 ] = PR_sched_getscheduler, [ 146 ] = PR_sched_get_priority_max, [ 147 ] = PR_sched_get_priority_min, [ 148 ] = PR_sched_rr_get_interval, [ 149 ] = PR_mlock, [ 150 ] = PR_munlock, [ 151 ] = PR_mlockall, [ 152 ] = PR_munlockall, [ 153 ] = PR_vhangup, [ 154 ] = PR_modify_ldt, [ 155 ] = PR_pivot_root, [ 156 ] = PR__sysctl, [ 157 ] = PR_prctl, [ 158 ] = PR_arch_prctl, [ 159 ] = PR_adjtimex, [ 160 ] = PR_setrlimit, [ 161 ] = PR_chroot, [ 162 ] = PR_sync, [ 163 ] = PR_acct, [ 164 ] = PR_settimeofday, [ 165 ] = PR_mount, [ 166 ] = PR_umount2, [ 167 ] = PR_swapon, [ 168 ] = PR_swapoff, [ 169 ] = PR_reboot, [ 170 ] = PR_sethostname, [ 171 ] = PR_setdomainname, [ 172 ] = PR_iopl, [ 173 ] = PR_ioperm, [ 174 ] = PR_create_module, [ 175 ] = PR_init_module, [ 176 ] = PR_delete_module, [ 177 ] = PR_get_kernel_syms, [ 178 ] = PR_query_module, [ 179 ] = PR_quotactl, [ 180 ] = PR_nfsservctl, [ 181 ] = PR_getpmsg, [ 182 ] = PR_putpmsg, [ 183 ] = PR_afs_syscall, [ 184 ] = PR_tuxcall, [ 185 ] = PR_security, [ 186 ] = PR_gettid, [ 187 ] = PR_readahead, [ 188 ] = PR_setxattr, [ 189 ] = PR_lsetxattr, [ 190 ] = PR_fsetxattr, [ 191 ] = PR_getxattr, [ 192 ] = PR_lgetxattr, [ 193 ] = PR_fgetxattr, [ 194 ] = PR_listxattr, [ 195 ] = PR_llistxattr, [ 196 ] = PR_flistxattr, [ 197 ] = PR_removexattr, [ 198 ] = PR_lremovexattr, [ 199 ] = PR_fremovexattr, [ 200 ] = PR_tkill, [ 201 ] = PR_time, [ 202 ] = PR_futex, [ 203 ] = PR_sched_setaffinity, [ 204 ] = PR_sched_getaffinity, [ 205 ] = PR_set_thread_area, [ 206 ] = PR_io_setup, [ 207 ] = PR_io_destroy, [ 208 ] = PR_io_getevents, [ 209 ] = PR_io_submit, [ 210 ] = PR_io_cancel, [ 211 ] = PR_get_thread_area, [ 212 ] = PR_lookup_dcookie, [ 213 ] = PR_epoll_create, [ 214 ] = PR_epoll_ctl_old, [ 215 ] = PR_epoll_wait_old, [ 216 ] = PR_remap_file_pages, [ 217 ] = PR_getdents64, [ 218 ] = PR_set_tid_address, [ 219 ] = PR_restart_syscall, [ 220 ] = PR_semtimedop, [ 221 ] = PR_fadvise64, [ 222 ] = PR_timer_create, [ 223 ] = PR_timer_settime, [ 224 ] = PR_timer_gettime, [ 225 ] = PR_timer_getoverrun, [ 226 ] = PR_timer_delete, [ 227 ] = PR_clock_settime, [ 228 ] = PR_clock_gettime, [ 229 ] = PR_clock_getres, [ 230 ] = PR_clock_nanosleep, [ 231 ] = PR_exit_group, [ 232 ] = PR_epoll_wait, [ 233 ] = PR_epoll_ctl, [ 234 ] = PR_tgkill, [ 235 ] = PR_utimes, [ 236 ] = PR_vserver, [ 237 ] = PR_mbind, [ 238 ] = PR_set_mempolicy, [ 239 ] = PR_get_mempolicy, [ 240 ] = PR_mq_open, [ 241 ] = PR_mq_unlink, [ 242 ] = PR_mq_timedsend, [ 243 ] = PR_mq_timedreceive, [ 244 ] = PR_mq_notify, [ 245 ] = PR_mq_getsetattr, [ 246 ] = PR_kexec_load, [ 247 ] = PR_waitid, [ 248 ] = PR_add_key, [ 249 ] = PR_request_key, [ 250 ] = PR_keyctl, [ 251 ] = PR_ioprio_set, [ 252 ] = PR_ioprio_get, [ 253 ] = PR_inotify_init, [ 254 ] = PR_inotify_add_watch, [ 255 ] = PR_inotify_rm_watch, [ 256 ] = PR_migrate_pages, [ 257 ] = PR_openat, [ 258 ] = PR_mkdirat, [ 259 ] = PR_mknodat, [ 260 ] = PR_fchownat, [ 261 ] = PR_futimesat, [ 262 ] = PR_newfstatat, [ 263 ] = PR_unlinkat, [ 264 ] = PR_renameat, [ 265 ] = PR_linkat, [ 266 ] = PR_symlinkat, [ 267 ] = PR_readlinkat, [ 268 ] = PR_fchmodat, [ 269 ] = PR_faccessat, [ 270 ] = PR_pselect6, [ 271 ] = PR_ppoll, [ 272 ] = PR_unshare, [ 273 ] = PR_set_robust_list, [ 274 ] = PR_get_robust_list, [ 275 ] = PR_splice, [ 276 ] = PR_tee, [ 277 ] = PR_sync_file_range, [ 278 ] = PR_vmsplice, [ 279 ] = PR_move_pages, [ 280 ] = PR_utimensat, [ 281 ] = PR_epoll_pwait, [ 282 ] = PR_signalfd, [ 283 ] = PR_timerfd_create, [ 284 ] = PR_eventfd, [ 285 ] = PR_fallocate, [ 286 ] = PR_timerfd_settime, [ 287 ] = PR_timerfd_gettime, [ 288 ] = PR_accept4, [ 289 ] = PR_signalfd4, [ 290 ] = PR_eventfd2, [ 291 ] = PR_epoll_create1, [ 292 ] = PR_dup3, [ 293 ] = PR_pipe2, [ 294 ] = PR_inotify_init1, [ 295 ] = PR_preadv, [ 296 ] = PR_pwritev, [ 297 ] = PR_rt_tgsigqueueinfo, [ 298 ] = PR_perf_event_open, [ 299 ] = PR_recvmmsg, [ 300 ] = PR_fanotify_init, [ 301 ] = PR_fanotify_mark, [ 302 ] = PR_prlimit64, [ 303 ] = PR_name_to_handle_at, [ 304 ] = PR_open_by_handle_at, [ 305 ] = PR_clock_adjtime, [ 306 ] = PR_syncfs, [ 307 ] = PR_sendmmsg, [ 308 ] = PR_setns, [ 309 ] = PR_getcpu, [ 310 ] = PR_process_vm_readv, [ 311 ] = PR_process_vm_writev, [ 312 ] = PR_kcmp, [ 313 ] = PR_finit_module, [ 314 ] = PR_sched_setattr, [ 315 ] = PR_sched_getattr, [ 316 ] = PR_renameat2, [ 332 ] = PR_statx, [ 439 ] = PR_faccessat2, }; proot-5.4.0/src/syscall/sysnums.list000066400000000000000000000172411442763353300175610ustar00rootroot00000000000000SYSNUM(ARM_BASE) SYSNUM(ARM_breakpoint) SYSNUM(ARM_cacheflush) SYSNUM(ARM_set_tls) SYSNUM(ARM_usr26) SYSNUM(ARM_usr32) SYSNUM(X32_SYSCALL_BIT) SYSNUM(_llseek) SYSNUM(_newselect) SYSNUM(_sysctl) SYSNUM(accept) SYSNUM(accept4) SYSNUM(access) SYSNUM(acct) SYSNUM(add_key) SYSNUM(adjtimex) SYSNUM(afs_syscall) SYSNUM(alarm) SYSNUM(arch_prctl) SYSNUM(arch_specific_syscall) SYSNUM(arm_fadvise64_64) SYSNUM(arm_sync_file_range) SYSNUM(bdflush) SYSNUM(bind) SYSNUM(break) SYSNUM(brk) SYSNUM(cacheflush) SYSNUM(capget) SYSNUM(capset) SYSNUM(chdir) SYSNUM(chmod) SYSNUM(chown) SYSNUM(chown32) SYSNUM(chroot) SYSNUM(clock_adjtime) SYSNUM(clock_getres) SYSNUM(clock_gettime) SYSNUM(clock_nanosleep) SYSNUM(clock_settime) SYSNUM(clone) SYSNUM(close) SYSNUM(connect) SYSNUM(creat) SYSNUM(create_module) SYSNUM(delete_module) SYSNUM(dup) SYSNUM(dup2) SYSNUM(dup3) SYSNUM(epoll_create) SYSNUM(epoll_create1) SYSNUM(epoll_ctl) SYSNUM(epoll_ctl_old) SYSNUM(epoll_pwait) SYSNUM(epoll_wait) SYSNUM(epoll_wait_old) SYSNUM(eventfd) SYSNUM(eventfd2) SYSNUM(execve) SYSNUM(exit) SYSNUM(exit_group) SYSNUM(faccessat) SYSNUM(faccessat2) SYSNUM(fadvise64) SYSNUM(fadvise64_64) SYSNUM(fallocate) SYSNUM(fanotify_init) SYSNUM(fanotify_mark) SYSNUM(fchdir) SYSNUM(fchmod) SYSNUM(fchmodat) SYSNUM(fchown) SYSNUM(fchown32) SYSNUM(fchownat) SYSNUM(fcntl) SYSNUM(fcntl64) SYSNUM(fdatasync) SYSNUM(fgetxattr) SYSNUM(finit_module) SYSNUM(flistxattr) SYSNUM(flock) SYSNUM(fork) SYSNUM(fremovexattr) SYSNUM(fsetxattr) SYSNUM(fstat) SYSNUM(fstat64) SYSNUM(fstatat64) SYSNUM(fstatfs) SYSNUM(fstatfs64) SYSNUM(fsync) SYSNUM(ftime) SYSNUM(ftruncate) SYSNUM(ftruncate64) SYSNUM(futex) SYSNUM(futimesat) SYSNUM(get_kernel_syms) SYSNUM(get_mempolicy) SYSNUM(get_robust_list) SYSNUM(get_thread_area) SYSNUM(getcpu) SYSNUM(getcwd) SYSNUM(getdents) SYSNUM(getdents64) SYSNUM(getegid) SYSNUM(getegid32) SYSNUM(geteuid) SYSNUM(geteuid32) SYSNUM(getgid) SYSNUM(getgid32) SYSNUM(getgroups) SYSNUM(getgroups32) SYSNUM(getitimer) SYSNUM(getpeername) SYSNUM(getpgid) SYSNUM(getpgrp) SYSNUM(getpid) SYSNUM(getpmsg) SYSNUM(getppid) SYSNUM(getpriority) SYSNUM(getresgid) SYSNUM(getresgid32) SYSNUM(getresuid) SYSNUM(getresuid32) SYSNUM(getrlimit) SYSNUM(getrusage) SYSNUM(getsid) SYSNUM(getsockname) SYSNUM(getsockopt) SYSNUM(gettid) SYSNUM(gettimeofday) SYSNUM(getuid) SYSNUM(getuid32) SYSNUM(getxattr) SYSNUM(gtty) SYSNUM(idle) SYSNUM(init_module) SYSNUM(inotify_add_watch) SYSNUM(inotify_init) SYSNUM(inotify_init1) SYSNUM(inotify_rm_watch) SYSNUM(io_cancel) SYSNUM(io_destroy) SYSNUM(io_getevents) SYSNUM(io_setup) SYSNUM(io_submit) SYSNUM(ioctl) SYSNUM(ioperm) SYSNUM(iopl) SYSNUM(ioprio_get) SYSNUM(ioprio_set) SYSNUM(ipc) SYSNUM(kcmp) SYSNUM(kexec_load) SYSNUM(keyctl) SYSNUM(kill) SYSNUM(lchown) SYSNUM(lchown32) SYSNUM(lgetxattr) SYSNUM(link) SYSNUM(linkat) SYSNUM(listen) SYSNUM(listxattr) SYSNUM(llistxattr) SYSNUM(lock) SYSNUM(lookup_dcookie) SYSNUM(lremovexattr) SYSNUM(lseek) SYSNUM(lsetxattr) SYSNUM(lstat) SYSNUM(lstat64) SYSNUM(madvise) SYSNUM(mbind) SYSNUM(migrate_pages) SYSNUM(mincore) SYSNUM(mkdir) SYSNUM(mkdirat) SYSNUM(mknod) SYSNUM(mknodat) SYSNUM(mlock) SYSNUM(mlockall) SYSNUM(mmap) SYSNUM(mmap2) SYSNUM(modify_ldt) SYSNUM(mount) SYSNUM(move_pages) SYSNUM(mprotect) SYSNUM(mpx) SYSNUM(mq_getsetattr) SYSNUM(mq_notify) SYSNUM(mq_open) SYSNUM(mq_timedreceive) SYSNUM(mq_timedsend) SYSNUM(mq_unlink) SYSNUM(mremap) SYSNUM(msgctl) SYSNUM(msgget) SYSNUM(msgrcv) SYSNUM(msgsnd) SYSNUM(msync) SYSNUM(munlock) SYSNUM(munlockall) SYSNUM(munmap) SYSNUM(name_to_handle_at) SYSNUM(nanosleep) SYSNUM(newfstatat) SYSNUM(nfsservctl) SYSNUM(nice) SYSNUM(oldfstat) SYSNUM(oldlstat) SYSNUM(oldolduname) SYSNUM(oldstat) SYSNUM(olduname) SYSNUM(open) SYSNUM(open_by_handle_at) SYSNUM(openat) SYSNUM(pause) SYSNUM(pciconfig_iobase) SYSNUM(pciconfig_read) SYSNUM(pciconfig_write) SYSNUM(perf_event_open) SYSNUM(personality) SYSNUM(pipe) SYSNUM(pipe2) SYSNUM(pivot_root) SYSNUM(poll) SYSNUM(ppoll) SYSNUM(prctl) SYSNUM(pread64) SYSNUM(preadv) SYSNUM(prlimit64) SYSNUM(process_vm_readv) SYSNUM(process_vm_writev) SYSNUM(prof) SYSNUM(profil) SYSNUM(pselect6) SYSNUM(ptrace) SYSNUM(putpmsg) SYSNUM(pwrite64) SYSNUM(pwritev) SYSNUM(query_module) SYSNUM(quotactl) SYSNUM(read) SYSNUM(readahead) SYSNUM(readdir) SYSNUM(readlink) SYSNUM(readlinkat) SYSNUM(readv) SYSNUM(reboot) SYSNUM(recv) SYSNUM(recvfrom) SYSNUM(recvmmsg) SYSNUM(recvmsg) SYSNUM(remap_file_pages) SYSNUM(removexattr) SYSNUM(rename) SYSNUM(renameat) SYSNUM(renameat2) SYSNUM(request_key) SYSNUM(restart_syscall) SYSNUM(rmdir) SYSNUM(rt_sigaction) SYSNUM(rt_sigpending) SYSNUM(rt_sigprocmask) SYSNUM(rt_sigqueueinfo) SYSNUM(rt_sigreturn) SYSNUM(rt_sigsuspend) SYSNUM(rt_sigtimedwait) SYSNUM(rt_tgsigqueueinfo) SYSNUM(sched_get_priority_max) SYSNUM(sched_get_priority_min) SYSNUM(sched_getaffinity) SYSNUM(sched_getattr) SYSNUM(sched_getparam) SYSNUM(sched_getscheduler) SYSNUM(sched_rr_get_interval) SYSNUM(sched_setaffinity) SYSNUM(sched_setattr) SYSNUM(sched_setparam) SYSNUM(sched_setscheduler) SYSNUM(sched_yield) SYSNUM(security) SYSNUM(select) SYSNUM(semctl) SYSNUM(semget) SYSNUM(semop) SYSNUM(semtimedop) SYSNUM(send) SYSNUM(sendfile) SYSNUM(sendfile64) SYSNUM(sendmmsg) SYSNUM(sendmsg) SYSNUM(sendto) SYSNUM(set_mempolicy) SYSNUM(set_robust_list) SYSNUM(set_thread_area) SYSNUM(set_tid_address) SYSNUM(setdomainname) SYSNUM(setfsgid) SYSNUM(setfsgid32) SYSNUM(setfsuid) SYSNUM(setfsuid32) SYSNUM(setgid) SYSNUM(setgid32) SYSNUM(setgroups) SYSNUM(setgroups32) SYSNUM(sethostname) SYSNUM(setitimer) SYSNUM(setns) SYSNUM(setpgid) SYSNUM(setpriority) SYSNUM(setregid) SYSNUM(setregid32) SYSNUM(setresgid) SYSNUM(setresgid32) SYSNUM(setresuid) SYSNUM(setresuid32) SYSNUM(setreuid) SYSNUM(setreuid32) SYSNUM(setrlimit) SYSNUM(setsid) SYSNUM(setsockopt) SYSNUM(settimeofday) SYSNUM(setuid) SYSNUM(setuid32) SYSNUM(setxattr) SYSNUM(sgetmask) SYSNUM(shmat) SYSNUM(shmctl) SYSNUM(shmdt) SYSNUM(shmget) SYSNUM(shutdown) SYSNUM(sigaction) SYSNUM(sigaltstack) SYSNUM(signal) SYSNUM(signalfd) SYSNUM(signalfd4) SYSNUM(sigpending) SYSNUM(sigprocmask) SYSNUM(sigreturn) SYSNUM(sigsuspend) SYSNUM(socket) SYSNUM(socketcall) SYSNUM(socketpair) SYSNUM(splice) SYSNUM(ssetmask) SYSNUM(stat) SYSNUM(stat64) SYSNUM(statfs) SYSNUM(statfs64) SYSNUM(stime) SYSNUM(stty) SYSNUM(swapoff) SYSNUM(swapon) SYSNUM(symlink) SYSNUM(symlinkat) SYSNUM(sync) SYSNUM(sync_file_range) SYSNUM(sync_file_range2) SYSNUM(syncfs) SYSNUM(sysfs) SYSNUM(sysinfo) SYSNUM(syslog) SYSNUM(tee) SYSNUM(tgkill) SYSNUM(time) SYSNUM(timer_create) SYSNUM(timer_delete) SYSNUM(timer_getoverrun) SYSNUM(timer_gettime) SYSNUM(timer_settime) SYSNUM(timerfd_create) SYSNUM(timerfd_gettime) SYSNUM(timerfd_settime) SYSNUM(times) SYSNUM(tkill) SYSNUM(truncate) SYSNUM(truncate64) SYSNUM(tuxcall) SYSNUM(ugetrlimit) SYSNUM(ulimit) SYSNUM(umask) SYSNUM(umount) SYSNUM(umount2) SYSNUM(uname) SYSNUM(unlink) SYSNUM(unlinkat) SYSNUM(unshare) SYSNUM(uselib) SYSNUM(ustat) SYSNUM(utime) SYSNUM(utimensat) SYSNUM(utimes) SYSNUM(vfork) SYSNUM(vhangup) SYSNUM(vm86) SYSNUM(vm86old) SYSNUM(vmsplice) SYSNUM(vserver) SYSNUM(wait4) SYSNUM(waitid) SYSNUM(waitpid) SYSNUM(write) SYSNUM(writev) SYSNUM(x32_execve) SYSNUM(x32_get_robust_list) SYSNUM(x32_ioctl) SYSNUM(x32_kexec_load) SYSNUM(x32_move_pages) SYSNUM(x32_mq_notify) SYSNUM(x32_preadv) SYSNUM(x32_process_vm_readv) SYSNUM(x32_process_vm_writev) SYSNUM(x32_ptrace) SYSNUM(x32_pwritev) SYSNUM(x32_readv) SYSNUM(x32_recvfrom) SYSNUM(x32_recvmmsg) SYSNUM(x32_recvmsg) SYSNUM(x32_rt_sigaction) SYSNUM(x32_rt_sigpending) SYSNUM(x32_rt_sigqueueinfo) SYSNUM(x32_rt_sigreturn) SYSNUM(x32_rt_sigtimedwait) SYSNUM(x32_rt_tgsigqueueinfo) SYSNUM(x32_sendmmsg) SYSNUM(x32_sendmsg) SYSNUM(x32_set_robust_list) SYSNUM(x32_sigaltstack) SYSNUM(x32_timer_create) SYSNUM(x32_vmsplice) SYSNUM(x32_waitid) SYSNUM(x32_writev) SYSNUM(statx) SYSNUM(utimensat_time64) proot-5.4.0/src/tracee/000077500000000000000000000000001442763353300147275ustar00rootroot00000000000000proot-5.4.0/src/tracee/abi.h000066400000000000000000000061401442763353300156340ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef TRACEE_ABI_H #define TRACEE_ABI_H #include #include /* offsetof(), */ #include "tracee/tracee.h" #include "tracee/reg.h" #include "arch.h" #include "attribute.h" typedef enum { ABI_DEFAULT = 0, ABI_2, /* x86_32 on x86_64. */ ABI_3, /* x32 on x86_64. */ NB_MAX_ABIS, } Abi; /** * Return the ABI currently used by the given @tracee. */ #if defined(ARCH_X86_64) static inline Abi get_abi(const Tracee *tracee) { /* The ABI can be changed by a syscall ("execve" typically), * however the change is only effective once the syscall has * *fully* returned, hence the use of _regs[ORIGINAL]. */ switch (tracee->_regs[ORIGINAL].cs) { case 0x23: return ABI_2; case 0x33: if (tracee->_regs[ORIGINAL].ds == 0x2B) return ABI_3; /* Fall through. */ default: return ABI_DEFAULT; } } /** * Return true if @tracee is a 32-bit process running on a 64-bit * kernel. */ static inline bool is_32on64_mode(const Tracee *tracee) { /* Unlike the ABI, 32-bit/64-bit mode change is effective * immediately, hence _regs[CURRENT].cs. */ switch (tracee->_regs[CURRENT].cs) { case 0x23: return true; case 0x33: if (tracee->_regs[CURRENT].ds == 0x2B) return true; /* Fall through. */ default: return false; } } #else static inline Abi get_abi(const Tracee *tracee UNUSED) { return ABI_DEFAULT; } static inline bool is_32on64_mode(const Tracee *tracee UNUSED) { return false; } #endif /** * Return the size of a word according to the ABI currently used by * the given @tracee. */ static inline size_t sizeof_word(const Tracee *tracee) { return (is_32on64_mode(tracee) ? sizeof(word_t) / 2 : sizeof(word_t)); } #include /** * Return the offset of the 'uid' field in a 'stat' structure * according to the ABI currently used by the given @tracee. */ static inline off_t offsetof_stat_uid(const Tracee *tracee) { return (is_32on64_mode(tracee) ? OFFSETOF_STAT_UID_32 : offsetof(struct stat, st_uid)); } /** * Return the offset of the 'gid' field in a 'stat' structure * according to the ABI currently used by the given @tracee. */ static inline off_t offsetof_stat_gid(const Tracee *tracee) { return (is_32on64_mode(tracee) ? OFFSETOF_STAT_GID_32 : offsetof(struct stat, st_gid)); } #endif /* TRACEE_ABI_H */ proot-5.4.0/src/tracee/event.c000066400000000000000000000553471442763353300162320ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include #include /* CLONE_*, */ #include /* pid_t, */ #include /* ptrace(1), PTRACE_*, */ #include /* waitpid(2), */ #include /* waitpid(2), */ #include /* uname(2), */ #include /* fork(2), chdir(2), getpid(2), */ #include /* strcmp(3), */ #include /* errno(3), */ #include /* bool, true, false, */ #include /* assert(3), */ #include /* atexit(3), getenv(3), */ #include /* talloc_*, */ #include /* PRI*, */ #include /* KERNEL_VERSION, */ #include "tracee/event.h" #include "cli/note.h" #include "path/path.h" #include "path/binding.h" #include "syscall/syscall.h" #include "syscall/seccomp.h" #include "ptrace/wait.h" #include "extension/extension.h" #include "execve/elf.h" #include "attribute.h" #include "compat.h" /** * Start @tracee->exe with the given @argv[]. This function * returns -errno if an error occurred, otherwise 0. */ int launch_process(Tracee *tracee, char *const argv[]) { char *const default_argv[] = { "-sh", NULL }; long status; pid_t pid; /* Warn about open file descriptors. They won't be * translated until they are closed. */ list_open_fd(tracee); pid = fork(); switch(pid) { case -1: note(tracee, ERROR, SYSTEM, "fork()"); return -errno; case 0: /* child */ /* Declare myself as ptraceable before executing the * requested program. */ status = ptrace(PTRACE_TRACEME, 0, NULL, NULL); if (status < 0) { note(tracee, ERROR, SYSTEM, "ptrace(TRACEME)"); return -errno; } /* Synchronize with the tracer's event loop. Without * this trick the tracer only sees the "return" from * the next execve(2) so PRoot wouldn't handle the * interpreter/runner. I also verified that strace * does the same thing. */ kill(getpid(), SIGSTOP); /* Improve performance by using seccomp mode 2, unless * this support is explicitly disabled. */ if (getenv("PROOT_NO_SECCOMP") == NULL) (void) enable_syscall_filtering(tracee); /* Now process is ptraced, so the current rootfs is already the * guest rootfs. Note: Valgrind can't handle execve(2) on * "foreign" binaries (ENOEXEC) but can handle execvp(3) on such * binaries. */ execvp(tracee->exe, argv[0] != NULL ? argv : default_argv); return -errno; default: /* parent */ /* We know the pid of the first tracee now. */ tracee->pid = pid; return 0; } /* Never reached. */ return -ENOSYS; } /* Send the KILL signal to all tracees when PRoot has received a fatal * signal. */ static void kill_all_tracees2(int signum, siginfo_t *siginfo UNUSED, void *ucontext UNUSED) { note(NULL, WARNING, INTERNAL, "signal %d received from process %d", signum, siginfo->si_pid); kill_all_tracees(); /* Exit immediately for system signals (segmentation fault, * illegal instruction, ...), otherwise exit cleanly through * the event loop. */ if (signum != SIGQUIT) _exit(EXIT_FAILURE); } /** * Helper for print_talloc_hierarchy(). */ static void print_talloc_chunk(const void *ptr, int depth, int max_depth UNUSED, int is_ref, void *data UNUSED) { const char *name; size_t count; size_t size; name = talloc_get_name(ptr); size = talloc_get_size(ptr); count = talloc_reference_count(ptr); if (depth == 0) return; while (depth-- > 1) fprintf(stderr, "\t"); fprintf(stderr, "%-16s ", name); if (is_ref) fprintf(stderr, "-> %-8p", ptr); else { fprintf(stderr, "%-8p %zd bytes %zd ref'", ptr, size, count); if (name[0] == '$') { fprintf(stderr, "\t(\"%s\")", (char *)ptr); } if (name[0] == '@') { char **argv; int i; fprintf(stderr, "\t("); for (i = 0, argv = (char **)ptr; argv[i] != NULL; i++) fprintf(stderr, "\"%s\", ", argv[i]); fprintf(stderr, ")"); } else if (strcmp(name, "Tracee") == 0) { fprintf(stderr, "\t(pid = %d, parent = %p)", ((Tracee *)ptr)->pid, ((Tracee *)ptr)->parent); } else if (strcmp(name, "Bindings") == 0) { Tracee *tracee; tracee = TRACEE(ptr); if (ptr == tracee->fs->bindings.pending) fprintf(stderr, "\t(pending)"); else if (ptr == tracee->fs->bindings.guest) fprintf(stderr, "\t(guest)"); else if (ptr == tracee->fs->bindings.host) fprintf(stderr, "\t(host)"); } else if (strcmp(name, "Binding") == 0) { Binding *binding = (Binding *)ptr; fprintf(stderr, "\t(%s:%s)", binding->host.path, binding->guest.path); } } fprintf(stderr, "\n"); } /* Print on stderr the complete talloc hierarchy. */ static void print_talloc_hierarchy(int signum, siginfo_t *siginfo UNUSED, void *ucontext UNUSED) { switch (signum) { case SIGUSR1: talloc_report_depth_cb(NULL, 0, 100, print_talloc_chunk, NULL); break; case SIGUSR2: talloc_report_depth_file(NULL, 0, 100, stderr); break; default: break; } } static int last_exit_status = -1; /** * Check if kernel >= 4.8 */ static bool is_kernel_4_8(void) { static int version_48 = -1; int major = 0; int minor = 0; if (version_48 != -1) return version_48; version_48 = false; struct utsname utsname; if (uname(&utsname) < 0) return false; sscanf(utsname.release, "%d.%d", &major, &minor); if ((major == 4 && minor >= 8) || major > 4) version_48 = true; return version_48; } /** * Check if this instance of PRoot can *technically* handle @tracee. */ static void check_architecture(Tracee *tracee) { struct utsname utsname; ElfHeader elf_header; char path[PATH_MAX]; int status; if (tracee->exe == NULL) return; status = translate_path(tracee, path, AT_FDCWD, tracee->exe, false); if (status < 0) return; status = open_elf(path, &elf_header); if (status < 0) return; close(status); if (!IS_CLASS64(elf_header) || sizeof(word_t) == sizeof(uint64_t)) return; note(tracee, ERROR, USER, "'%s' is a 64-bit program whereas this version of " "%s handles 32-bit programs only", path, tracee->tool_name); status = uname(&utsname); if (status < 0) return; if (strcmp(utsname.machine, "x86_64") != 0) return; note(tracee, INFO, USER, "A 64-bit version that supports 32-bit binaries is required"); } /** * Wait then handle any event from any tracee. This function returns * the exit status of the last terminated program. */ int event_loop() { struct sigaction signal_action; long status; int signum; /* Kill all tracees when exiting. */ status = atexit(kill_all_tracees); if (status != 0) note(NULL, WARNING, INTERNAL, "atexit() failed"); /* All signals are blocked when the signal handler is called. * SIGINFO is used to know which process has signaled us and * RESTART is used to restart waitpid(2) seamlessly. */ bzero(&signal_action, sizeof(signal_action)); signal_action.sa_flags = SA_SIGINFO | SA_RESTART; status = sigfillset(&signal_action.sa_mask); if (status < 0) note(NULL, WARNING, SYSTEM, "sigfillset()"); /* Handle all signals. */ for (signum = 0; signum < SIGRTMAX; signum++) { switch (signum) { case SIGQUIT: case SIGILL: case SIGABRT: case SIGFPE: case SIGSEGV: /* Kill all tracees on abnormal termination * signals. This ensures no process is left * untraced. */ signal_action.sa_sigaction = kill_all_tracees2; break; case SIGUSR1: case SIGUSR2: /* Print on stderr the complete talloc * hierarchy, useful for debug purpose. */ signal_action.sa_sigaction = print_talloc_hierarchy; break; case SIGCHLD: case SIGCONT: case SIGSTOP: case SIGTSTP: case SIGTTIN: case SIGTTOU: /* The default action is OK for these signals, * they are related to tty and job control. */ continue; default: /* Ignore all other signals, including * terminating ones (^C for instance). */ signal_action.sa_sigaction = (void *)SIG_IGN; break; } status = sigaction(signum, &signal_action, NULL); if (status < 0 && errno != EINVAL) note(NULL, WARNING, SYSTEM, "sigaction(%d)", signum); } while (1) { int tracee_status; Tracee *tracee; int signal; pid_t pid; /* This is the only safe place to free tracees. */ free_terminated_tracees(); /* Wait for the next tracee's stop. */ pid = waitpid(-1, &tracee_status, __WALL); if (pid < 0) { if (errno != ECHILD) { note(NULL, ERROR, SYSTEM, "waitpid()"); return EXIT_FAILURE; } break; } /* Get information about this tracee. */ tracee = get_tracee(NULL, pid, true); assert(tracee != NULL); tracee->running = false; VERBOSE(tracee, 6, "vpid %" PRIu64 ": got event %x", tracee->vpid, tracee_status); status = notify_extensions(tracee, NEW_STATUS, tracee_status, 0); if (status != 0) continue; if (tracee->as_ptracee.ptracer != NULL) { bool keep_stopped = handle_ptracee_event(tracee, tracee_status); if (keep_stopped) continue; } signal = handle_tracee_event(tracee, tracee_status); (void) restart_tracee(tracee, signal); } return last_exit_status; } /** * For kernels >= 4.8.0 * Handle the current event (@tracee_status) of the given @tracee. * This function returns the "computed" signal that should be used to * restart the given @tracee. */ static int handle_tracee_event_kernel_4_8(Tracee *tracee, int tracee_status) { static bool seccomp_detected = false; static bool seccomp_enabled = false; /* added for 4.8.0 */ long status; int signal; /* Don't overwrite restart_how if it is explicitly set * elsewhere, i.e in the ptrace emulation when single * stepping. */ if (tracee->restart_how == 0) { /* When seccomp is enabled, all events are restarted in * non-stop mode, but this default choice could be overwritten * later if necessary. The check against "sysexit_pending" * ensures PTRACE_SYSCALL (used to hit the exit stage under * seccomp) is not cleared due to an event that would happen * before the exit stage, eg. PTRACE_EVENT_EXEC for the exit * stage of execve(2). */ if (tracee->seccomp == ENABLED && !tracee->sysexit_pending) tracee->restart_how = PTRACE_CONT; else tracee->restart_how = PTRACE_SYSCALL; } /* Not a signal-stop by default. */ signal = 0; if (WIFEXITED(tracee_status)) { last_exit_status = WEXITSTATUS(tracee_status); VERBOSE(tracee, 1, "vpid %" PRIu64 ": exited with status %d", tracee->vpid, last_exit_status); terminate_tracee(tracee); } else if (WIFSIGNALED(tracee_status)) { check_architecture(tracee); VERBOSE(tracee, 1, "vpid %" PRIu64 ": terminated with signal %d", tracee->vpid, WTERMSIG(tracee_status)); terminate_tracee(tracee); } else if (WIFSTOPPED(tracee_status)) { /* Don't use WSTOPSIG() to extract the signal * since it clears the PTRACE_EVENT_* bits. */ signal = (tracee_status & 0xfff00) >> 8; switch (signal) { static bool deliver_sigtrap = false; case SIGTRAP: { const unsigned long default_ptrace_options = ( PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACEEXEC | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXIT); /* Distinguish some events from others and * automatically trace each new process with * the same options. * * Note that only the first bare SIGTRAP is * related to the tracing loop, others SIGTRAP * carry tracing information because of * TRACE*FORK/CLONE/EXEC. */ if (deliver_sigtrap) break; /* Deliver this signal as-is. */ deliver_sigtrap = true; /* Try to enable seccomp mode 2... */ status = ptrace(PTRACE_SETOPTIONS, tracee->pid, NULL, default_ptrace_options | PTRACE_O_TRACESECCOMP); if (status < 0) { seccomp_enabled = false; /* ... otherwise use default options only. */ status = ptrace(PTRACE_SETOPTIONS, tracee->pid, NULL, default_ptrace_options); if (status < 0) { note(tracee, ERROR, SYSTEM, "ptrace(PTRACE_SETOPTIONS)"); exit(EXIT_FAILURE); } } else { if (getenv("PROOT_NO_SECCOMP") == NULL) seccomp_enabled = true; } } /* Fall through. */ case SIGTRAP | PTRACE_EVENT_SECCOMP2 << 8: case SIGTRAP | PTRACE_EVENT_SECCOMP << 8: if (!seccomp_detected && seccomp_enabled) { VERBOSE(tracee, 1, "ptrace acceleration (seccomp mode 2) enabled"); tracee->seccomp = ENABLED; seccomp_detected = true; } if (signal == (SIGTRAP | PTRACE_EVENT_SECCOMP2 << 8) || signal == (SIGTRAP | PTRACE_EVENT_SECCOMP << 8)) { unsigned long flags = 0; signal = 0; /* Use the common ptrace flow if seccomp was * explicitly disabled for this tracee. */ if (tracee->seccomp != ENABLED) break; status = ptrace(PTRACE_GETEVENTMSG, tracee->pid, NULL, &flags); if (status < 0) break; if ((flags & FILTER_SYSEXIT) == 0) { tracee->restart_how = PTRACE_CONT; translate_syscall(tracee); if (tracee->seccomp == DISABLING) tracee->restart_how = PTRACE_SYSCALL; break; } } /* Fall through. */ case SIGTRAP | 0x80: signal = 0; /* This tracee got signaled then freed during the sysenter stage but the kernel reports the sysexit stage; just discard this spurious tracee/event. */ if (tracee->exe == NULL) { tracee->restart_how = PTRACE_CONT; /* SYSCALL OR CONT */ return 0; } switch (tracee->seccomp) { case ENABLED: if (IS_IN_SYSENTER(tracee)) { /* sysenter: ensure the sysexit * stage will be hit under seccomp. */ tracee->restart_how = PTRACE_SYSCALL; tracee->sysexit_pending = true; } else { /* sysexit: the next sysenter * will be notified by seccomp. */ tracee->restart_how = PTRACE_CONT; tracee->sysexit_pending = false; } /* Fall through. */ case DISABLED: translate_syscall(tracee); /* This syscall has disabled seccomp. */ if (tracee->seccomp == DISABLING) { tracee->restart_how = PTRACE_SYSCALL; tracee->seccomp = DISABLED; } break; case DISABLING: /* Seccomp was disabled by the * previous syscall, but its sysenter * stage was already handled. */ tracee->seccomp = DISABLED; if (IS_IN_SYSENTER(tracee)) tracee->status = 1; break; } break; case SIGTRAP | PTRACE_EVENT_VFORK << 8: signal = 0; (void) new_child(tracee, CLONE_VFORK); break; case SIGTRAP | PTRACE_EVENT_FORK << 8: case SIGTRAP | PTRACE_EVENT_CLONE << 8: signal = 0; (void) new_child(tracee, 0); break; case SIGTRAP | PTRACE_EVENT_VFORK_DONE << 8: case SIGTRAP | PTRACE_EVENT_EXEC << 8: case SIGTRAP | PTRACE_EVENT_EXIT << 8: signal = 0; break; case SIGSTOP: /* Stop this tracee until PRoot has received * the EVENT_*FORK|CLONE notification. */ if (tracee->exe == NULL) { tracee->sigstop = SIGSTOP_PENDING; signal = -1; } /* For each tracee, the first SIGSTOP * is only used to notify the tracer. */ if (tracee->sigstop == SIGSTOP_IGNORED) { tracee->sigstop = SIGSTOP_ALLOWED; signal = 0; } break; default: /* Deliver this signal as-is. */ break; } } /* Clear the pending event, if any. */ tracee->as_ptracee.event4.proot.pending = false; return signal; } /** * For kernels < 4.8.0 * Handle the current event (@tracee_status) of the given @tracee. * This function returns the "computed" signal that should be used to * restart the given @tracee. */ int handle_tracee_event(Tracee *tracee, int tracee_status) { static bool seccomp_detected = false; long status; int signal; if (is_kernel_4_8()) return handle_tracee_event_kernel_4_8(tracee, tracee_status); /* Don't overwrite restart_how if it is explicitly set * elsewhere, i.e in the ptrace emulation when single * stepping. */ if (tracee->restart_how == 0) { /* When seccomp is enabled, all events are restarted in * non-stop mode, but this default choice could be overwritten * later if necessary. The check against "sysexit_pending" * ensures PTRACE_SYSCALL (used to hit the exit stage under * seccomp) is not cleared due to an event that would happen * before the exit stage, eg. PTRACE_EVENT_EXEC for the exit * stage of execve(2). */ if (tracee->seccomp == ENABLED && !tracee->sysexit_pending) tracee->restart_how = PTRACE_CONT; else tracee->restart_how = PTRACE_SYSCALL; } /* Not a signal-stop by default. */ signal = 0; if (WIFEXITED(tracee_status)) { last_exit_status = WEXITSTATUS(tracee_status); VERBOSE(tracee, 1, "vpid %" PRIu64 ": exited with status %d", tracee->vpid, last_exit_status); terminate_tracee(tracee); } else if (WIFSIGNALED(tracee_status)) { check_architecture(tracee); VERBOSE(tracee, 1, "vpid %" PRIu64 ": terminated with signal %d", tracee->vpid, WTERMSIG(tracee_status)); terminate_tracee(tracee); } else if (WIFSTOPPED(tracee_status)) { /* Don't use WSTOPSIG() to extract the signal * since it clears the PTRACE_EVENT_* bits. */ signal = (tracee_status & 0xfff00) >> 8; switch (signal) { static bool deliver_sigtrap = false; case SIGTRAP: { const unsigned long default_ptrace_options = ( PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACEEXEC | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXIT); /* Distinguish some events from others and * automatically trace each new process with * the same options. * * Note that only the first bare SIGTRAP is * related to the tracing loop, others SIGTRAP * carry tracing information because of * TRACE*FORK/CLONE/EXEC. */ if (deliver_sigtrap) break; /* Deliver this signal as-is. */ deliver_sigtrap = true; /* Try to enable seccomp mode 2... */ status = ptrace(PTRACE_SETOPTIONS, tracee->pid, NULL, default_ptrace_options | PTRACE_O_TRACESECCOMP); if (status < 0) { /* ... otherwise use default options only. */ status = ptrace(PTRACE_SETOPTIONS, tracee->pid, NULL, default_ptrace_options); if (status < 0) { note(tracee, ERROR, SYSTEM, "ptrace(PTRACE_SETOPTIONS)"); exit(EXIT_FAILURE); } } } /* Fall through. */ case SIGTRAP | 0x80: signal = 0; /* This tracee got signaled then freed during the sysenter stage but the kernel reports the sysexit stage; just discard this spurious tracee/event. */ if (tracee->exe == NULL) { tracee->restart_how = PTRACE_CONT; /* SYSCALL OR CONT */ return 0; } switch (tracee->seccomp) { case ENABLED: if (IS_IN_SYSENTER(tracee)) { /* sysenter: ensure the sysexit * stage will be hit under seccomp. */ tracee->restart_how = PTRACE_SYSCALL; tracee->sysexit_pending = true; } else { /* sysexit: the next sysenter * will be notified by seccomp. */ tracee->restart_how = PTRACE_CONT; tracee->sysexit_pending = false; } /* Fall through. */ case DISABLED: translate_syscall(tracee); /* This syscall has disabled seccomp. */ if (tracee->seccomp == DISABLING) { tracee->restart_how = PTRACE_SYSCALL; tracee->seccomp = DISABLED; } break; case DISABLING: /* Seccomp was disabled by the * previous syscall, but its sysenter * stage was already handled. */ tracee->seccomp = DISABLED; if (IS_IN_SYSENTER(tracee)) tracee->status = 1; break; } break; case SIGTRAP | PTRACE_EVENT_SECCOMP2 << 8: case SIGTRAP | PTRACE_EVENT_SECCOMP << 8: { unsigned long flags = 0; signal = 0; if (!seccomp_detected) { VERBOSE(tracee, 1, "ptrace acceleration (seccomp mode 2) enabled"); tracee->seccomp = ENABLED; seccomp_detected = true; } /* Use the common ptrace flow if seccomp was * explicitely disabled for this tracee. */ if (tracee->seccomp != ENABLED) break; status = ptrace(PTRACE_GETEVENTMSG, tracee->pid, NULL, &flags); if (status < 0) break; /* Use the common ptrace flow when * sysexit has to be handled. */ if ((flags & FILTER_SYSEXIT) != 0) { tracee->restart_how = PTRACE_SYSCALL; break; } /* Otherwise, handle the sysenter * stage right now. */ tracee->restart_how = PTRACE_CONT; translate_syscall(tracee); /* This syscall has disabled seccomp, so move * the ptrace flow back to the common path to * ensure its sysexit will be handled. */ if (tracee->seccomp == DISABLING) tracee->restart_how = PTRACE_SYSCALL; break; } case SIGTRAP | PTRACE_EVENT_VFORK << 8: signal = 0; (void) new_child(tracee, CLONE_VFORK); break; case SIGTRAP | PTRACE_EVENT_FORK << 8: case SIGTRAP | PTRACE_EVENT_CLONE << 8: signal = 0; (void) new_child(tracee, 0); break; case SIGTRAP | PTRACE_EVENT_VFORK_DONE << 8: case SIGTRAP | PTRACE_EVENT_EXEC << 8: case SIGTRAP | PTRACE_EVENT_EXIT << 8: signal = 0; break; case SIGSTOP: /* Stop this tracee until PRoot has received * the EVENT_*FORK|CLONE notification. */ if (tracee->exe == NULL) { tracee->sigstop = SIGSTOP_PENDING; signal = -1; } /* For each tracee, the first SIGSTOP * is only used to notify the tracer. */ if (tracee->sigstop == SIGSTOP_IGNORED) { tracee->sigstop = SIGSTOP_ALLOWED; signal = 0; } break; default: /* Deliver this signal as-is. */ break; } } /* Clear the pending event, if any. */ tracee->as_ptracee.event4.proot.pending = false; return signal; } /** * Restart the given @tracee with the specified @signal. This * function returns false if the tracee was not restarted (error or * put in the "waiting for ptracee" state), otherwise true. */ bool restart_tracee(Tracee *tracee, int signal) { int status; /* Put in the "stopped"/"waiting for ptracee" state?. */ if (tracee->as_ptracer.wait_pid != 0 || signal == -1) return false; /* Restart the tracee and stop it at the next instruction, or * at the next entry or exit of a system call. */ status = ptrace(tracee->restart_how, tracee->pid, NULL, signal); if (status < 0) return false; /* The process likely died in a syscall. */ VERBOSE(tracee, 6, "vpid %" PRIu64 ": restarted using %d, signal %d", tracee->vpid, tracee->restart_how, signal); tracee->restart_how = 0; tracee->running = true; return true; } proot-5.4.0/src/tracee/event.h000066400000000000000000000022501442763353300162200ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef TRACEE_EVENT_H #define TRACEE_EVENT_H #include #include "tracee/tracee.h" extern int launch_process(Tracee *tracee, char *const argv[]); extern int event_loop(); extern int handle_tracee_event(Tracee *tracee, int tracee_status); extern bool restart_tracee(Tracee *tracee, int signal); #endif /* TRACEE_EVENT_H */ proot-5.4.0/src/tracee/mem.c000066400000000000000000000357771442763353300156740ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* ptrace(2), PTRACE_*, */ #include /* pid_t, size_t, */ #include /* NULL, */ #include /* offsetof(), */ #include /* struct user*, */ #include /* errno, */ #include /* assert(3), */ #include /* waitpid(2), */ #include /* memcpy(3), */ #include /* uint*_t, */ #include /* process_vm_*, struct iovec, */ #include /* sysconf(3), */ #include /* mmap(2), munmap(2), MAP_*, */ #include "tracee/mem.h" #include "tracee/abi.h" #include "syscall/heap.h" #include "arch.h" /* word_t, NO_MISALIGNED_ACCESS */ #include "build.h" /* HAVE_PROCESS_VM, */ #include "cli/note.h" /** * Load the word at the given @address, potentially *not* aligned. */ static inline word_t load_word(const void *address) { #ifdef NO_MISALIGNED_ACCESS if (((word_t)address) % sizeof(word_t) == 0) return *(word_t *)address; else { word_t value; memcpy(&value, address, sizeof(word_t)); return value; } #else return *(word_t *)address; #endif } /** * Store the word with the given @value to the given @address, * potentially *not* aligned. */ static inline void store_word(void *address, word_t value) { #ifdef NO_MISALIGNED_ACCESS if (((word_t)address) % sizeof(word_t) == 0) *((word_t *)address) = value; else memcpy(address, &value, sizeof(word_t)); #else *((word_t *)address) = value; #endif } /** * Copy @size bytes from the buffer @src_tracer to the address * @dest_tracee within the memory space of the @tracee process. It * returns -errno if an error occured, otherwise 0. */ int write_data(const Tracee *tracee, word_t dest_tracee, const void *src_tracer, word_t size) { word_t *src = (word_t *)src_tracer; word_t *dest = (word_t *)dest_tracee; long status; word_t word, i, j; word_t nb_trailing_bytes; word_t nb_full_words; uint8_t *last_dest_word; uint8_t *last_src_word; #if defined(HAVE_PROCESS_VM) struct iovec local; struct iovec remote; local.iov_base = src; local.iov_len = size; remote.iov_base = dest; remote.iov_len = size; status = process_vm_writev(tracee->pid, &local, 1, &remote, 1, 0); if ((size_t) status == size) return 0; /* Fallback to ptrace if something went wrong. */ #endif /* HAVE_PROCESS_VM */ nb_trailing_bytes = size % sizeof(word_t); nb_full_words = (size - nb_trailing_bytes) / sizeof(word_t); /* Copy one word by one word, except for the last one. */ for (i = 0; i < nb_full_words; i++) { status = ptrace(PTRACE_POKEDATA, tracee->pid, dest + i, load_word(&src[i])); if (status < 0) { note(tracee, WARNING, SYSTEM, "ptrace(POKEDATA)"); return -EFAULT; } } if (nb_trailing_bytes == 0) return 0; /* Copy the bytes in the last word carefully since we have to * overwrite only the relevant ones. */ word = ptrace(PTRACE_PEEKDATA, tracee->pid, dest + i, NULL); if (errno != 0) { note(tracee, WARNING, SYSTEM, "ptrace(PEEKDATA)"); return -EFAULT; } last_dest_word = (uint8_t *)&word; last_src_word = (uint8_t *)&src[i]; for (j = 0; j < nb_trailing_bytes; j++) last_dest_word[j] = last_src_word[j]; status = ptrace(PTRACE_POKEDATA, tracee->pid, dest + i, word); if (status < 0) { note(tracee, WARNING, SYSTEM, "ptrace(POKEDATA)"); return -EFAULT; } return 0; } /** * Gather the @src_tracer_count buffers pointed to by @src_tracer to * the address @dest_tracee within the memory space of the @tracee * process. This function returns -errno if an error occured, * otherwise 0. */ int writev_data(const Tracee *tracee, word_t dest_tracee, const struct iovec *src_tracer, int src_tracer_count) { size_t size; int status; int i; #if defined(HAVE_PROCESS_VM) struct iovec remote; for (i = 0, size = 0; i < src_tracer_count; i++) size += src_tracer[i].iov_len; remote.iov_base = (word_t *)dest_tracee; remote.iov_len = size; status = process_vm_writev(tracee->pid, src_tracer, src_tracer_count, &remote, 1, 0); if ((size_t) status == size) return 0; /* Fallback to iterative-write if something went wrong. */ #endif /* HAVE_PROCESS_VM */ for (i = 0, size = 0; i < src_tracer_count; i++) { status = write_data(tracee, dest_tracee + size, src_tracer[i].iov_base, src_tracer[i].iov_len); if (status < 0) return status; size += src_tracer[i].iov_len; } return 0; } /** * Copy @size bytes to the buffer @dest_tracer from the address * @src_tracee within the memory space of the @tracee process. It * returns -errno if an error occured, otherwise 0. */ int read_data(const Tracee *tracee, void *dest_tracer, word_t src_tracee, word_t size) { word_t *src = (word_t *)src_tracee; word_t *dest = (word_t *)dest_tracer; word_t nb_trailing_bytes; word_t nb_full_words; word_t word, i, j; uint8_t *last_src_word; uint8_t *last_dest_word; #if defined(HAVE_PROCESS_VM) long status; struct iovec local; struct iovec remote; local.iov_base = dest; local.iov_len = size; remote.iov_base = src; remote.iov_len = size; status = process_vm_readv(tracee->pid, &local, 1, &remote, 1, 0); if ((size_t) status == size) return 0; /* Fallback to ptrace if something went wrong. */ #endif /* HAVE_PROCESS_VM */ nb_trailing_bytes = size % sizeof(word_t); nb_full_words = (size - nb_trailing_bytes) / sizeof(word_t); /* Copy one word by one word, except for the last one. */ for (i = 0; i < nb_full_words; i++) { word = ptrace(PTRACE_PEEKDATA, tracee->pid, src + i, NULL); if (errno != 0) { note(tracee, WARNING, SYSTEM, "ptrace(PEEKDATA)"); return -EFAULT; } store_word(&dest[i], word); } if (nb_trailing_bytes == 0) return 0; /* Copy the bytes from the last word carefully since we have * to not overwrite the bytes lying beyond @dest_tracer. */ word = ptrace(PTRACE_PEEKDATA, tracee->pid, src + i, NULL); if (errno != 0) { note(tracee, WARNING, SYSTEM, "ptrace(PEEKDATA)"); return -EFAULT; } last_dest_word = (uint8_t *)&dest[i]; last_src_word = (uint8_t *)&word; for (j = 0; j < nb_trailing_bytes; j++) last_dest_word[j] = last_src_word[j]; return 0; } /** * Copy to @dest_tracer at most @max_size bytes from the string * pointed to by @src_tracee within the memory space of the @tracee * process. This function returns -errno on error, otherwise * it returns the number in bytes of the string, including the * end-of-string terminator. */ int read_string(const Tracee *tracee, char *dest_tracer, word_t src_tracee, word_t max_size) { word_t *src = (word_t *)src_tracee; word_t *dest = (word_t *)dest_tracer; word_t nb_trailing_bytes; word_t nb_full_words; word_t word, i, j; uint8_t *src_word; uint8_t *dest_word; #if defined(HAVE_PROCESS_VM) /* [process_vm] system calls do not check the memory regions * in the remote process until just before doing the * read/write. Consequently, a partial read/write [1] may * result if one of the remote_iov elements points to an * invalid memory region in the remote process. No further * reads/writes will be attempted beyond that point. Keep * this in mind when attempting to read data of unknown length * (such as C strings that are null-terminated) from a remote * process, by avoiding spanning memory pages (typically 4KiB) * in a single remote iovec element. (Instead, split the * remote read into two remote_iov elements and have them * merge back into a single write local_iov entry. The first * read entry goes up to the page boundary, while the second * starts on the next page boundary.). * * [1] Partial transfers apply at the granularity of iovec * elements. These system calls won't perform a partial * transfer that splits a single iovec element. * * -- man 2 process_vm_readv */ long status; size_t size; size_t offset; struct iovec local; struct iovec remote; static size_t chunk_size = 0; static uintptr_t chunk_mask; /* A chunk shall not cross a page boundary. */ if (chunk_size == 0) { chunk_size = sysconf(_SC_PAGE_SIZE); chunk_size = (chunk_size > 0 && chunk_size < 1024 ? chunk_size : 1024); chunk_mask = ~(chunk_size - 1); } /* Read the string by chunk. */ offset = 0; do { uintptr_t current_chunk = (src_tracee + offset) & chunk_mask; uintptr_t next_chunk = current_chunk + chunk_size; /* Compute the number of bytes available up to the * next chunk or up to max_size. */ size = next_chunk - (src_tracee + offset); size = (size < max_size - offset ? size : max_size - offset); local.iov_base = (uint8_t *)dest + offset; local.iov_len = size; remote.iov_base = (uint8_t *)src + offset; remote.iov_len = size; status = process_vm_readv(tracee->pid, &local, 1, &remote, 1, 0); if ((size_t) status != size) goto fallback; status = strnlen(local.iov_base, size); if ((size_t) status < size) { size = offset + status + 1; assert(size <= max_size); return size; } offset += size; } while (offset < max_size); assert(offset == max_size); /* Fallback to ptrace if something went wrong. */ fallback: #endif /* HAVE_PROCESS_VM */ nb_trailing_bytes = max_size % sizeof(word_t); nb_full_words = (max_size - nb_trailing_bytes) / sizeof(word_t); /* Copy one word by one word, except for the last one. */ for (i = 0; i < nb_full_words; i++) { word = ptrace(PTRACE_PEEKDATA, tracee->pid, src + i, NULL); if (errno != 0) return -EFAULT; store_word(&dest[i], word); /* Stop once an end-of-string is detected. */ src_word = (uint8_t *)&word; for (j = 0; j < sizeof(word_t); j++) if (src_word[j] == '\0') return i * sizeof(word_t) + j + 1; } /* Copy the bytes from the last word carefully since we have * to not overwrite the bytes lying beyond @dest_tracer. */ word = ptrace(PTRACE_PEEKDATA, tracee->pid, src + i, NULL); if (errno != 0) return -EFAULT; dest_word = (uint8_t *)&dest[i]; src_word = (uint8_t *)&word; for (j = 0; j < nb_trailing_bytes; j++) { dest_word[j] = src_word[j]; if (src_word[j] == '\0') break; } return i * sizeof(word_t) + j + 1; } /** * Return the value of the word at the given @address in the @tracee's * memory space. The caller must test errno to check if an error * occured. */ word_t peek_word(const Tracee *tracee, word_t address) { word_t result = 0; #if defined(HAVE_PROCESS_VM) int status; struct iovec local; struct iovec remote; local.iov_base = &result; local.iov_len = sizeof_word(tracee); remote.iov_base = (void *)address; remote.iov_len = sizeof_word(tracee); errno = 0; status = process_vm_readv(tracee->pid, &local, 1, &remote, 1, 0); if (status > 0) return result; /* Fallback to ptrace if something went wrong. */ #endif errno = 0; result = (word_t) ptrace(PTRACE_PEEKDATA, tracee->pid, address, NULL); /* From ptrace(2) manual: "Unfortunately, under Linux, * different variations of this fault will return EIO or * EFAULT more or less arbitrarily." */ if (errno == EIO) errno = EFAULT; /* Use only the 32 LSB when running a 32-bit process on a * 64-bit kernel. */ if (is_32on64_mode(tracee)) result &= 0xFFFFFFFF; return result; } /** * Set the word at the given @address in the @tracee's memory space to * the given @value. The caller must test errno to check if an error * occured. */ void poke_word(const Tracee *tracee, word_t address, word_t value) { word_t tmp; #if defined(HAVE_PROCESS_VM) int status; struct iovec local; struct iovec remote; /* Note: &value points to the 32 LSB on 64-bit little-endian * architecture. */ local.iov_base = &value; local.iov_len = sizeof_word(tracee); remote.iov_base = (void *)address; remote.iov_len = sizeof_word(tracee); errno = 0; status = process_vm_writev(tracee->pid, &local, 1, &remote, 1, 0); if (status > 0) return; /* Fallback to ptrace if something went wrong. */ #endif /* Don't overwrite the 32 MSB when running a 32-bit process on * a 64-bit kernel. */ if (is_32on64_mode(tracee)) { errno = 0; tmp = (word_t) ptrace(PTRACE_PEEKDATA, tracee->pid, address, NULL); if (errno != 0) return; value |= (tmp & 0xFFFFFFFF00000000ULL); } errno = 0; (void) ptrace(PTRACE_POKEDATA, tracee->pid, address, value); /* From ptrace(2) manual: "Unfortunately, under Linux, * different variations of this fault will return EIO or * EFAULT more or less arbitrarily." */ if (errno == EIO) errno = EFAULT; return; } /** * Allocate @size bytes in the @tracee's memory space. This function * returns the address of the allocated memory in the @tracee's memory * space, otherwise 0 if an error occured. */ word_t alloc_mem(Tracee *tracee, ssize_t size) { word_t stack_pointer; /* This function should be called in sysenter only since the * stack pointer is systematically restored at the end of * sysexit (except for execve, but in this case the stack * pointer should be handled with care since it is used by the * process to retrieve argc, argv, envp, and auxv). */ assert(IS_IN_SYSENTER(tracee)); /* Get the current value of the stack pointer from the tracee's * USER area. */ stack_pointer = peek_reg(tracee, CURRENT, STACK_POINTER); /* Some ABIs specify an amount of bytes after the stack * pointer that shall not be used by anything but the compiler * (for optimization purpose). */ if (stack_pointer == peek_reg(tracee, ORIGINAL, STACK_POINTER)) size += RED_ZONE_SIZE; /* Align the stack */ size = ((size - 1) / STACK_ALIGNMENT + 1) * STACK_ALIGNMENT; /* Sanity check. */ if ( (size > 0 && stack_pointer <= (word_t) size) || (size < 0 && stack_pointer >= ULONG_MAX + size)) { note(tracee, WARNING, INTERNAL, "integer under/overflow detected in %s", __FUNCTION__); return 0; } /* Remember the stack grows downward. */ stack_pointer -= size; /* Set the new value of the stack pointer in the tracee's USER * area. */ poke_reg(tracee, STACK_POINTER, stack_pointer); return stack_pointer; } /** * Clear @size bytes at the given @address in the @tracee's memory * space. This function returns -errno if an error occured, otherwise * 0. */ int clear_mem(const Tracee *tracee, word_t address, size_t size) { int status; void *zeros; zeros = mmap(NULL, size, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (zeros == MAP_FAILED) return -errno; status = write_data(tracee, address, zeros, size); munmap(zeros, size); return status; } proot-5.4.0/src/tracee/mem.h000066400000000000000000000072401442763353300156610ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef TRACEE_MEM_H #define TRACEE_MEM_H #include /* PATH_MAX, */ #include /* pid_t, size_t, */ #include /* pid_t, size_t, */ #include /* struct iovec, */ #include /* ENAMETOOLONG, */ #include "arch.h" /* word_t, */ #include "tracee/tracee.h" extern int write_data(const Tracee *tracee, word_t dest_tracee, const void *src_tracer, word_t size); extern int writev_data(const Tracee *tracee, word_t dest_tracee, const struct iovec *src_tracer, int src_tracer_count); extern int read_data(const Tracee *tracee, void *dest_tracer, word_t src_tracee, word_t size); extern int read_string(const Tracee *tracee, char *dest_tracer, word_t src_tracee, word_t max_size); extern word_t peek_word(const Tracee *tracee, word_t address); extern void poke_word(const Tracee *tracee, word_t address, word_t value); extern word_t alloc_mem(Tracee *tracee, ssize_t size); extern int clear_mem(const Tracee *tracee, word_t address, size_t size); /** * Copy to @dest_tracer at most PATH_MAX bytes -- including the * end-of-string terminator -- from the string pointed to by * @src_tracee within the memory space of the @tracee process. This * function returns -errno on error, otherwise it returns the number * in bytes of the string, including the end-of-string terminator. */ static inline int read_path(const Tracee *tracee, char dest_tracer[PATH_MAX], word_t src_tracee) { int status; status = read_string(tracee, dest_tracer, src_tracee, PATH_MAX); if (status < 0) return status; if (status >= PATH_MAX) return -ENAMETOOLONG; return status; } /** * Generate a function that returns the value of the @type at the * given @address in the @tracee's memory space. The caller must test * errno to check if an error occured. */ #define GENERATE_peek(type) \ static inline type ## _t peek_ ## type(const Tracee *tracee, word_t address) \ { \ type ## _t result; \ errno = -read_data(tracee, &result, address, sizeof(type ## _t)); \ return result; \ } GENERATE_peek(uint8); GENERATE_peek(uint16); GENERATE_peek(uint32); GENERATE_peek(uint64); GENERATE_peek(int8); GENERATE_peek(int16); GENERATE_peek(int32); GENERATE_peek(int64); #undef GENERATE_peek /** * Generate a function that set the @type at the given @address in the * @tracee's memory space to the given @value. The caller must test * errno to check if an error occured. */ #define GENERATE_poke(type) \ static inline void poke_ ## type(const Tracee *tracee, word_t address, type ## _t value) \ { \ errno = -write_data(tracee, address, &value, sizeof(type ## _t)); \ } GENERATE_poke(uint8); GENERATE_poke(uint16); GENERATE_poke(uint32); GENERATE_poke(uint64); GENERATE_poke(int8); GENERATE_poke(int16); GENERATE_poke(int32); GENERATE_poke(int64); #undef GENERATE_poke #endif /* TRACEE_MEM_H */ proot-5.4.0/src/tracee/reg.c000066400000000000000000000253011442763353300156510ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* off_t */ #include /* struct user*, */ #include /* ptrace(2), PTRACE*, */ #include /* assert(3), */ #include /* errno(3), */ #include /* offsetof(), */ #include /* *int*_t, */ #include /* PRI*, */ #include /* ULONG_MAX, */ #include /* memcpy(3), */ #include /* struct iovec, */ #include "arch.h" #if defined(ARCH_ARM64) #include /* NT_PRSTATUS */ #endif #include "syscall/sysnum.h" #include "tracee/reg.h" #include "tracee/abi.h" #include "cli/note.h" #include "compat.h" /** * Compute the offset of the register @reg_name in the USER area. */ #define USER_REGS_OFFSET(reg_name) \ (offsetof(struct user, regs) \ + offsetof(struct user_regs_struct, reg_name)) #define REG(tracee, version, index) \ (*(word_t*) (((uint8_t *) &tracee->_regs[version]) + reg_offset[index])) /* Specify the ABI registers (syscall argument passing, stack pointer). * See sysdeps/unix/sysv/linux/${ARCH}/syscall.S from the GNU C Library. */ #if defined(ARCH_X86_64) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(orig_rax), [SYSARG_1] = USER_REGS_OFFSET(rdi), [SYSARG_2] = USER_REGS_OFFSET(rsi), [SYSARG_3] = USER_REGS_OFFSET(rdx), [SYSARG_4] = USER_REGS_OFFSET(r10), [SYSARG_5] = USER_REGS_OFFSET(r8), [SYSARG_6] = USER_REGS_OFFSET(r9), [SYSARG_RESULT] = USER_REGS_OFFSET(rax), [STACK_POINTER] = USER_REGS_OFFSET(rsp), [INSTR_POINTER] = USER_REGS_OFFSET(rip), [RTLD_FINI] = USER_REGS_OFFSET(rdx), [STATE_FLAGS] = USER_REGS_OFFSET(eflags), [USERARG_1] = USER_REGS_OFFSET(rdi), }; static off_t reg_offset_x86[] = { [SYSARG_NUM] = USER_REGS_OFFSET(orig_rax), [SYSARG_1] = USER_REGS_OFFSET(rbx), [SYSARG_2] = USER_REGS_OFFSET(rcx), [SYSARG_3] = USER_REGS_OFFSET(rdx), [SYSARG_4] = USER_REGS_OFFSET(rsi), [SYSARG_5] = USER_REGS_OFFSET(rdi), [SYSARG_6] = USER_REGS_OFFSET(rbp), [SYSARG_RESULT] = USER_REGS_OFFSET(rax), [STACK_POINTER] = USER_REGS_OFFSET(rsp), [INSTR_POINTER] = USER_REGS_OFFSET(rip), [RTLD_FINI] = USER_REGS_OFFSET(rdx), [STATE_FLAGS] = USER_REGS_OFFSET(eflags), [USERARG_1] = USER_REGS_OFFSET(rax), }; #undef REG #define REG(tracee, version, index) \ (*(word_t*) (tracee->_regs[version].cs == 0x23 \ ? (((uint8_t *) &tracee->_regs[version]) + reg_offset_x86[index]) \ : (((uint8_t *) &tracee->_regs[version]) + reg_offset[index]))) #elif defined(ARCH_ARM_EABI) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(uregs[7]), [SYSARG_1] = USER_REGS_OFFSET(uregs[0]), [SYSARG_2] = USER_REGS_OFFSET(uregs[1]), [SYSARG_3] = USER_REGS_OFFSET(uregs[2]), [SYSARG_4] = USER_REGS_OFFSET(uregs[3]), [SYSARG_5] = USER_REGS_OFFSET(uregs[4]), [SYSARG_6] = USER_REGS_OFFSET(uregs[5]), [SYSARG_RESULT] = USER_REGS_OFFSET(uregs[0]), [STACK_POINTER] = USER_REGS_OFFSET(uregs[13]), [INSTR_POINTER] = USER_REGS_OFFSET(uregs[15]), [USERARG_1] = USER_REGS_OFFSET(uregs[0]), }; #elif defined(ARCH_ARM64) #undef USER_REGS_OFFSET #define USER_REGS_OFFSET(reg_name) offsetof(struct user_regs_struct, reg_name) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(regs[8]), [SYSARG_1] = USER_REGS_OFFSET(regs[0]), [SYSARG_2] = USER_REGS_OFFSET(regs[1]), [SYSARG_3] = USER_REGS_OFFSET(regs[2]), [SYSARG_4] = USER_REGS_OFFSET(regs[3]), [SYSARG_5] = USER_REGS_OFFSET(regs[4]), [SYSARG_6] = USER_REGS_OFFSET(regs[5]), [SYSARG_RESULT] = USER_REGS_OFFSET(regs[0]), [STACK_POINTER] = USER_REGS_OFFSET(sp), [INSTR_POINTER] = USER_REGS_OFFSET(pc), [USERARG_1] = USER_REGS_OFFSET(regs[0]), }; #elif defined(ARCH_X86) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(orig_eax), [SYSARG_1] = USER_REGS_OFFSET(ebx), [SYSARG_2] = USER_REGS_OFFSET(ecx), [SYSARG_3] = USER_REGS_OFFSET(edx), [SYSARG_4] = USER_REGS_OFFSET(esi), [SYSARG_5] = USER_REGS_OFFSET(edi), [SYSARG_6] = USER_REGS_OFFSET(ebp), [SYSARG_RESULT] = USER_REGS_OFFSET(eax), [STACK_POINTER] = USER_REGS_OFFSET(esp), [INSTR_POINTER] = USER_REGS_OFFSET(eip), [RTLD_FINI] = USER_REGS_OFFSET(edx), [STATE_FLAGS] = USER_REGS_OFFSET(eflags), [USERARG_1] = USER_REGS_OFFSET(eax), }; #elif defined(ARCH_SH4) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(regs[3]), [SYSARG_1] = USER_REGS_OFFSET(regs[4]), [SYSARG_2] = USER_REGS_OFFSET(regs[5]), [SYSARG_3] = USER_REGS_OFFSET(regs[6]), [SYSARG_4] = USER_REGS_OFFSET(regs[7]), [SYSARG_5] = USER_REGS_OFFSET(regs[0]), [SYSARG_6] = USER_REGS_OFFSET(regs[1]), [SYSARG_RESULT] = USER_REGS_OFFSET(regs[0]), [STACK_POINTER] = USER_REGS_OFFSET(regs[15]), [INSTR_POINTER] = USER_REGS_OFFSET(pc), [RTLD_FINI] = USER_REGS_OFFSET(r4), }; #else #error "Unsupported architecture" #endif /** * Return the *cached* value of the given @tracees' @reg. */ word_t peek_reg(const Tracee *tracee, RegVersion version, Reg reg) { word_t result; assert(version < NB_REG_VERSION); result = REG(tracee, version, reg); /* Use only the 32 least significant bits (LSB) when running * 32-bit processes on a 64-bit kernel. */ if (is_32on64_mode(tracee)) result &= 0xFFFFFFFF; return result; } /** * Set the *cached* value of the given @tracees' @reg. */ void poke_reg(Tracee *tracee, Reg reg, word_t value) { if (peek_reg(tracee, CURRENT, reg) == value) return; REG(tracee, CURRENT, reg) = value; tracee->_regs_were_changed = true; } /** * Print the value of the current @tracee's registers according * to the @verbose_level. Note: @message is mixed to the output. */ void print_current_regs(Tracee *tracee, int verbose_level, const char *message) { if (tracee->verbose < verbose_level) return; note(tracee, INFO, INTERNAL, "vpid %" PRIu64 ": %s: %s(0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx) = 0x%lx [0x%lx, %d]", tracee->vpid, message, stringify_sysnum(get_sysnum(tracee, CURRENT)), peek_reg(tracee, CURRENT, SYSARG_1), peek_reg(tracee, CURRENT, SYSARG_2), peek_reg(tracee, CURRENT, SYSARG_3), peek_reg(tracee, CURRENT, SYSARG_4), peek_reg(tracee, CURRENT, SYSARG_5), peek_reg(tracee, CURRENT, SYSARG_6), peek_reg(tracee, CURRENT, SYSARG_RESULT), peek_reg(tracee, CURRENT, STACK_POINTER), get_abi(tracee)); } /** * Save the @tracee's current register bank into the @version register * bank. */ void save_current_regs(Tracee *tracee, RegVersion version) { /* Optimization: don't restore original register values if * they were never changed. */ if (version == ORIGINAL) tracee->_regs_were_changed = false; memcpy(&tracee->_regs[version], &tracee->_regs[CURRENT], sizeof(tracee->_regs[CURRENT])); } /** * Copy all @tracee's general purpose registers into a dedicated * cache. This function returns -errno if an error occured, 0 * otherwise. */ int fetch_regs(Tracee *tracee) { int status; #if defined(ARCH_ARM64) struct iovec regs; regs.iov_base = &tracee->_regs[CURRENT]; regs.iov_len = sizeof(tracee->_regs[CURRENT]); status = ptrace(PTRACE_GETREGSET, tracee->pid, NT_PRSTATUS, ®s); #else status = ptrace(PTRACE_GETREGS, tracee->pid, NULL, &tracee->_regs[CURRENT]); #endif if (status < 0) return status; return 0; } /** * Copy the cached values of all @tracee's general purpose registers * back to the process, if necessary. This function returns -errno if * an error occured, 0 otherwise. */ int push_regs(Tracee *tracee) { int status; if (tracee->_regs_were_changed) { /* At the very end of a syscall, with regard to the * entry, only the result register can be modified by * PRoot. */ if (tracee->restore_original_regs) { /* Restore the sysarg register only if it is * not the same as the result register. Note: * it's never the case on x86 architectures, * so don't make this check, otherwise it * would introduce useless complexity because * of the multiple ABI support. */ #if defined(ARCH_X86) || defined(ARCH_X86_64) # define RESTORE(sysarg) (REG(tracee, CURRENT, sysarg) = REG(tracee, ORIGINAL, sysarg)) #else # define RESTORE(sysarg) (void) (reg_offset[SYSARG_RESULT] != reg_offset[sysarg] && \ (REG(tracee, CURRENT, sysarg) = REG(tracee, ORIGINAL, sysarg))) #endif RESTORE(SYSARG_NUM); RESTORE(SYSARG_1); RESTORE(SYSARG_2); RESTORE(SYSARG_3); RESTORE(SYSARG_4); RESTORE(SYSARG_5); RESTORE(SYSARG_6); RESTORE(STACK_POINTER); } #if defined(ARCH_ARM64) struct iovec regs; word_t current_sysnum = REG(tracee, CURRENT, SYSARG_NUM); /* Update syscall number if needed. On arm64, a new * subcommand has been added to PTRACE_{S,G}ETREGSET * to allow write/read of current sycall number. */ if (current_sysnum != REG(tracee, ORIGINAL, SYSARG_NUM)) { regs.iov_base = ¤t_sysnum; regs.iov_len = sizeof(current_sysnum); status = ptrace(PTRACE_SETREGSET, tracee->pid, NT_ARM_SYSTEM_CALL, ®s); if (status < 0) note(tracee, WARNING, SYSTEM, "can't set the syscall number"); } /* Update other registers. */ regs.iov_base = &tracee->_regs[CURRENT]; regs.iov_len = sizeof(tracee->_regs[CURRENT]); status = ptrace(PTRACE_SETREGSET, tracee->pid, NT_PRSTATUS, ®s); #else # if defined(ARCH_ARM_EABI) /* On ARM, a special ptrace request is required to * change effectively the syscall number during a * ptrace-stop. */ word_t current_sysnum = REG(tracee, CURRENT, SYSARG_NUM); if (current_sysnum != REG(tracee, ORIGINAL, SYSARG_NUM)) { status = ptrace(PTRACE_SET_SYSCALL, tracee->pid, 0, current_sysnum); if (status < 0) note(tracee, WARNING, SYSTEM, "can't set the syscall number"); } # endif status = ptrace(PTRACE_SETREGS, tracee->pid, NULL, &tracee->_regs[CURRENT]); #endif if (status < 0) return status; } return 0; } proot-5.4.0/src/tracee/reg.h000066400000000000000000000027751442763353300156700ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef TRACEE_REG_H #define TRACEE_REG_H #include "tracee/tracee.h" #include "arch.h" typedef enum { SYSARG_NUM = 0, SYSARG_1, SYSARG_2, SYSARG_3, SYSARG_4, SYSARG_5, SYSARG_6, SYSARG_RESULT, STACK_POINTER, INSTR_POINTER, RTLD_FINI, STATE_FLAGS, USERARG_1, } Reg; extern int fetch_regs(Tracee *tracee); extern int push_regs(Tracee *tracee); extern word_t peek_reg(const Tracee *tracee, RegVersion version, Reg reg); extern void poke_reg(Tracee *tracee, Reg reg, word_t value); extern void print_current_regs(Tracee *tracee, int verbose_level, const char *message); extern void save_current_regs(Tracee *tracee, RegVersion version); #endif /* TRACEE_REG_H */ proot-5.4.0/src/tracee/tracee.c000066400000000000000000000433541442763353300163470ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* CLONE_*, */ #include /* pid_t, size_t, */ #include /* NULL, */ #include /* assert(3), */ #include /* bzero(3), */ #include /* bool, true, false, */ #include /* LIST_*, */ #include /* talloc_*, */ #include /* kill(2), SIGKILL, */ #include /* ptrace(2), PTRACE_*, */ #include /* E*, */ #include /* PRI*, */ #include "tracee/tracee.h" #include "tracee/reg.h" #include "path/binding.h" #include "syscall/sysnum.h" #include "tracee/event.h" #include "ptrace/ptrace.h" #include "ptrace/wait.h" #include "extension/extension.h" #include "cli/note.h" #include "compat.h" #ifndef __W_STOPCODE #define __W_STOPCODE(sig) ((sig) <<8 | 0x7f) #endif typedef LIST_HEAD(tracees, tracee) Tracees; static Tracees tracees; /** * Remove @zombie from its parent's list of zombies. Note: this is a * talloc destructor. */ static int remove_zombie(Tracee *zombie) { LIST_REMOVE(zombie, link); return 0; } /** * Perform some specific treatments against @pointer according to its * type, before it gets unlinked from @tracee_->life_context. */ static void clean_life_span_object(const void *pointer, int depth UNUSED, int max_depth UNUSED, int is_ref UNUSED, void *tracee_) { Binding *binding; Tracee *tracee; tracee = talloc_get_type_abort(tracee_, Tracee); /* So far, only bindings need a special treatment. */ binding = talloc_get_type(pointer, Binding); if (binding != NULL) remove_binding_from_all_lists(tracee, binding); } /** * Remove @tracee from the list of tracees and update all of its * children & ptracees, and its ptracer. Note: this is a talloc * destructor. */ static int remove_tracee(Tracee *tracee) { Tracee *relative; Tracee *ptracer; int event; LIST_REMOVE(tracee, link); /* Clean objects that are linked to this tracee's life * span. */ talloc_report_depth_cb(tracee->life_context, 0, 100, clean_life_span_object, tracee); /* This could be optimize by using a dedicated list of * children and ptracees. */ LIST_FOREACH(relative, &tracees, link) { /* Its children are now orphan. */ if (relative->parent == tracee) relative->parent = NULL; /* Its tracees are now free. */ if (relative->as_ptracee.ptracer == tracee) { /* Release the pending event, if any. */ relative->as_ptracee.ptracer = NULL; if (relative->as_ptracee.event4.proot.pending) { event = handle_tracee_event(relative, relative->as_ptracee.event4.proot.value); (void) restart_tracee(relative, event); } else if (relative->as_ptracee.event4.ptracer.pending) { event = relative->as_ptracee.event4.proot.value; (void) restart_tracee(relative, event); } bzero(&relative->as_ptracee, sizeof(relative->as_ptracee)); } } /* Nothing else to do if it's not a ptracee. */ ptracer = tracee->as_ptracee.ptracer; if (ptracer == NULL) return 0; /* Zombify this ptracee until its ptracer is notified about * its death. */ event = tracee->as_ptracee.event4.ptracer.value; if (tracee->as_ptracee.event4.ptracer.pending && (WIFEXITED(event) || WIFSIGNALED(event))) { Tracee *zombie; zombie = new_dummy_tracee(ptracer); if (zombie != NULL) { LIST_INSERT_HEAD(&PTRACER.zombies, zombie, link); talloc_set_destructor(zombie, remove_zombie); zombie->parent = tracee->parent; zombie->clone = tracee->clone; zombie->pid = tracee->pid; detach_from_ptracer(tracee); attach_to_ptracer(zombie, ptracer); zombie->as_ptracee.event4.ptracer.pending = true; zombie->as_ptracee.event4.ptracer.value = event; zombie->as_ptracee.is_zombie = true; return 0; } /* Fallback to the common path. */ } detach_from_ptracer(tracee); /* Wake its ptracer if there's nothing else to wait for. */ if (PTRACER.nb_ptracees == 0 && PTRACER.wait_pid != 0) { /* Update the return value of ptracer's wait(2). */ poke_reg(ptracer, SYSARG_RESULT, -ECHILD); /* Don't forget to write its register cache back. */ (void) push_regs(ptracer); PTRACER.wait_pid = 0; (void) restart_tracee(ptracer, 0); } return 0; } /** * Allocate a new entry for a dummy tracee (no pid, no destructor, not * in the list of tracees, ...). The new allocated memory is attached * to the given @context. This function returns NULL if an error * occurred (ENOMEM), otherwise it returns the newly allocated * structure. */ Tracee *new_dummy_tracee(TALLOC_CTX *context) { Tracee *tracee; tracee = talloc_zero(context, Tracee); if (tracee == NULL) return NULL; /* Allocate a memory collector. */ tracee->ctx = talloc_new(tracee); if (tracee->ctx == NULL) goto no_mem; /* By default new tracees have an empty file-system * name-space and heap. */ tracee->fs = talloc_zero(tracee, FileSystemNameSpace); tracee->heap = talloc_zero(tracee, Heap); if (tracee->fs == NULL || tracee->heap == NULL) goto no_mem; return tracee; no_mem: TALLOC_FREE(tracee); return NULL; } static uint64_t next_vpid = 1; /** * Allocate a new entry for the tracee @pid, then set its destructor * and add it to the list of tracees. This function returns NULL if * an error occurred (ENOMEM), otherwise it returns the newly * allocated structure. */ static Tracee *new_tracee(pid_t pid) { Tracee *tracee; tracee = new_dummy_tracee(NULL); if (tracee == NULL) return NULL; talloc_set_destructor(tracee, remove_tracee); tracee->pid = pid; tracee->vpid = next_vpid++; LIST_INSERT_HEAD(&tracees, tracee, link); tracee->life_context = talloc_new(tracee); return tracee; } /** * Return the first [stopped?] tracee with the given * @pid (-1 for any) which has the given @ptracer, and which has a * pending event for its ptracer if @only_with_pevent is true. See * wait(2) manual for the meaning of @wait_options. This function * returns NULL if there's no such ptracee. */ Tracee *get_ptracee(const Tracee *ptracer, pid_t pid, bool only_stopped, bool only_with_pevent, word_t wait_options) { Tracee *ptracee; /* Return zombies first. */ LIST_FOREACH(ptracee, &PTRACER.zombies, link) { /* Not the ptracee you're looking for? */ if (pid != ptracee->pid && pid != -1) continue; /* Not the expected kind of cloned process? */ if (!EXPECTED_WAIT_CLONE(wait_options, ptracee)) continue; return ptracee; } LIST_FOREACH(ptracee, &tracees, link) { /* Discard tracees that don't have this ptracer. */ if (PTRACEE.ptracer != ptracer) continue; /* Not the ptracee you're looking for? */ if (pid != ptracee->pid && pid != -1) continue; /* Not the expected kind of cloned process? */ if (!EXPECTED_WAIT_CLONE(wait_options, ptracee)) continue; /* No need to do more checks if its stopped state * doesn't matter. Be careful when using such * maybe-running tracee. */ if (!only_stopped) return ptracee; /* Is this tracee in the stopped state? */ if (ptracee->running) continue; /* Has a pending event for its ptracer? */ if (PTRACEE.event4.ptracer.pending || !only_with_pevent) return ptracee; /* No need to go further if the specific tracee isn't * in the expected state? */ if (pid == ptracee->pid) return NULL; } return NULL; } /** * Wrapper for get_ptracee(), this ensures only a stopped tracee is * returned (or NULL). */ Tracee *get_stopped_ptracee(const Tracee *ptracer, pid_t pid, bool only_with_pevent, word_t wait_options) { return get_ptracee(ptracer, pid, true, only_with_pevent, wait_options); } /** * Wrapper for get_ptracee(), this ensures no running tracee is * returned. */ bool has_ptracees(const Tracee *ptracer, pid_t pid, word_t wait_options) { return (get_ptracee(ptracer, pid, false, false, wait_options) != NULL); } /** * Return the entry related to the tracee @pid. If no entry were * found, a new one is created if @create is true, otherwise NULL is * returned. */ Tracee *get_tracee(const Tracee *current_tracee, pid_t pid, bool create) { Tracee *tracee; /* Don't reset the memory collector if the searched tracee is * the current one: there's likely pointers to the * sub-allocated data in the caller. */ if (current_tracee != NULL && current_tracee->pid == pid) return (Tracee *)current_tracee; LIST_FOREACH(tracee, &tracees, link) { if (tracee->pid == pid) { /* Flush then allocate a new memory collector. */ TALLOC_FREE(tracee->ctx); tracee->ctx = talloc_new(tracee); return tracee; } } return (create ? new_tracee(pid) : NULL); } /** * Mark tracee as terminated and optionally take action. */ void terminate_tracee(Tracee *tracee) { tracee->terminated = true; /* Case where the terminated tracee is marked to kill all tracees on exit. */ if (tracee->killall_on_exit) { VERBOSE(tracee, 1, "terminating all tracees on exit"); kill_all_tracees(); } } /** * Free all tracees marked as terminated. */ void free_terminated_tracees() { Tracee *next; /* Items can't be deleted when using LIST_FOREACH. */ next = tracees.lh_first; while (next != NULL) { Tracee *tracee = next; next = tracee->link.le_next; if (tracee->terminated) TALLOC_FREE(tracee); } } /** * Make new @parent's child inherit from it. Depending on * @clone_flags, some information are copied or shared. This function * returns -errno if an error occured, otherwise 0. */ int new_child(Tracee *parent, word_t clone_flags) { int ptrace_options; unsigned long pid; Tracee *child; int status; /* If the tracee calls clone(2) with the CLONE_VFORK flag, * PTRACE_EVENT_VFORK will be delivered instead [...]; * otherwise if the tracee calls clone(2) with the exit signal * set to SIGCHLD, PTRACE_EVENT_FORK will be delivered [...] * * -- ptrace(2) man-page * * That means we have to check if it's actually a clone(2) in * order to get the right flags. */ status = fetch_regs(parent); if (status >= 0 && get_sysnum(parent, CURRENT) == PR_clone) clone_flags = peek_reg(parent, CURRENT, SYSARG_1); /* Get the pid of the parent's new child. */ status = ptrace(PTRACE_GETEVENTMSG, parent->pid, NULL, &pid); if (status < 0 || pid == 0) { note(parent, WARNING, SYSTEM, "ptrace(GETEVENTMSG)"); return status; } child = get_tracee(parent, (pid_t) pid, true); if (child == NULL) { note(parent, WARNING, SYSTEM, "running out of memory"); return -ENOMEM; } /* Sanity checks. */ assert(child != NULL && child->exe == NULL && child->fs->cwd == NULL && child->fs->bindings.pending == NULL && child->fs->bindings.guest == NULL && child->fs->bindings.host == NULL && child->qemu == NULL && child->glue == NULL && child->parent == NULL && child->as_ptracee.ptracer == NULL); child->verbose = parent->verbose; child->seccomp = parent->seccomp; child->sysexit_pending = parent->sysexit_pending; child->restart_how = parent->restart_how; /* If CLONE_VM is set, the calling process and the child * process run in the same memory space [...] any memory * mapping or unmapping performed with mmap(2) or munmap(2) by * the child or calling process also affects the other * process. * * If CLONE_VM is not set, the child process runs in a * separate copy of the memory space of the calling process at * the time of clone(). Memory writes or file * mappings/unmappings performed by one of the processes do * not affect the other, as with fork(2). * * -- clone(2) man-page */ TALLOC_FREE(child->heap); child->heap = ((clone_flags & CLONE_VM) != 0) ? talloc_reference(child, parent->heap) : talloc_memdup(child, parent->heap, sizeof(Heap)); if (child->heap == NULL) return -ENOMEM; /* If CLONE_PARENT is set, then the parent of the new child * (as returned by getppid(2)) will be the same as that of the * calling process. * * If CLONE_PARENT is not set, then (as with fork(2)) the * child's parent is the calling process. * * -- clone(2) man-page */ if ((clone_flags & CLONE_PARENT) != 0) child->parent = parent->parent; else child->parent = parent; /* Remember if this child belongs to the same thread group as * its parent. This is currently useful for ptrace emulation * only but it deserves to be extended to support execve(2) * specificity (ie. when a thread calls execve(2), its pid * gets replaced by the pid of its thread group leader). */ child->clone = ((clone_flags & CLONE_THREAD) != 0); /* Depending on how the new process is created, it may be * automatically traced by the parent's tracer. */ ptrace_options = ( clone_flags == 0 ? PTRACE_O_TRACEFORK : (clone_flags & 0xFF) == SIGCHLD ? PTRACE_O_TRACEFORK : (clone_flags & CLONE_VFORK) != 0 ? PTRACE_O_TRACEVFORK : PTRACE_O_TRACECLONE); if (parent->as_ptracee.ptracer != NULL && ( (ptrace_options & parent->as_ptracee.options) != 0 || (clone_flags & CLONE_PTRACE) != 0)) { attach_to_ptracer(child, parent->as_ptracee.ptracer); /* All these flags are inheritable, no matter why this * child is being traced. */ child->as_ptracee.options |= (parent->as_ptracee.options & ( PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC | PTRACE_O_TRACEEXIT | PTRACE_O_TRACEFORK | PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE)); } /* If CLONE_FS is set, the parent and the child process share * the same file system information. This includes the root * of the file system, the current working directory, and the * umask. Any call to chroot(2), chdir(2), or umask(2) * performed by the parent process or the child process also * affects the other process. * * If CLONE_FS is not set, the child process works on a copy * of the file system information of the parent process at the * time of the clone() call. Calls to chroot(2), chdir(2), * umask(2) performed later by one of the processes do not * affect the other process. * * -- clone(2) man-page */ TALLOC_FREE(child->fs); if ((clone_flags & CLONE_FS) != 0) { /* File-system name-space is shared. */ child->fs = talloc_reference(child, parent->fs); } else { /* File-system name-space is copied. */ child->fs = talloc_zero(child, FileSystemNameSpace); if (child->fs == NULL) return -ENOMEM; child->fs->cwd = talloc_strdup(child->fs, parent->fs->cwd); if (child->fs->cwd == NULL) return -ENOMEM; talloc_set_name_const(child->fs->cwd, "$cwd"); /* Bindings are shared across file-system name-spaces since a * "mount --bind" made by a process affects all other processes * under Linux. Actually they are copied when a sub * reconfiguration occured (nested proot or chroot(2)). */ child->fs->bindings.guest = talloc_reference(child->fs, parent->fs->bindings.guest); child->fs->bindings.host = talloc_reference(child->fs, parent->fs->bindings.host); } /* The path to the executable is unshared only once the child * process does a call to execve(2). */ child->exe = talloc_reference(child, parent->exe); child->qemu = talloc_reference(child, parent->qemu); child->glue = talloc_reference(child, parent->glue); child->host_ldso_paths = talloc_reference(child, parent->host_ldso_paths); child->guest_ldso_paths = talloc_reference(child, parent->guest_ldso_paths); child->tool_name = parent->tool_name; inherit_extensions(child, parent, clone_flags); /* Restart the child tracee if it was already alive but * stopped until that moment. */ if (child->sigstop == SIGSTOP_PENDING) { bool keep_stopped = false; child->sigstop = SIGSTOP_ALLOWED; /* Notify its ptracer if it is ready to be traced. */ if (child->as_ptracee.ptracer != NULL) { /* Sanity check. */ assert(!child->as_ptracee.tracing_started); keep_stopped = handle_ptracee_event(child, __W_STOPCODE(SIGSTOP)); /* Note that this event was already handled by * PRoot since child->as_ptracee.ptracer was * NULL up to now. */ child->as_ptracee.event4.proot.pending = false; child->as_ptracee.event4.proot.value = 0; } if (!keep_stopped) (void) restart_tracee(child, 0); } VERBOSE(child, 1, "vpid %" PRIu64 ": pid %d", child->vpid, child->pid); return 0; } /** * Helper for swap_config(). */ static void reparent_config(Tracee *new_parent, Tracee *old_parent) { new_parent->verbose = old_parent->verbose; #define REPARENT(field) do { \ talloc_reparent(old_parent, new_parent, old_parent->field); \ new_parent->field = old_parent->field; \ } while(0); REPARENT(fs); REPARENT(exe); REPARENT(qemu); REPARENT(glue); REPARENT(extensions); #undef REPARENT } /** * Swap configuration (pointers and parentality) between @tracee1 and @tracee2. */ int swap_config(Tracee *tracee1, Tracee *tracee2) { Tracee *tmp; tmp = talloc_zero(tracee1->ctx, Tracee); if (tmp == NULL) return -ENOMEM; reparent_config(tmp, tracee1); reparent_config(tracee1, tracee2); reparent_config(tracee2, tmp); return 0; } /* Send the KILL signal to all tracees. */ void kill_all_tracees() { Tracee *tracee; LIST_FOREACH(tracee, &tracees, link) kill(tracee->pid, SIGKILL); } proot-5.4.0/src/tracee/tracee.h000066400000000000000000000211401442763353300163410ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #ifndef TRACEE_H #define TRACEE_H #include /* pid_t, size_t, */ #include /* struct user*, */ #include /* bool, */ #include /* LIST_*, */ #include /* enum __ptrace_request */ #include /* talloc_*, */ #include /* *int*_t, */ #include /* __WAIT_* */ #include "arch.h" /* word_t, user_regs_struct, */ #include "compat.h" #if defined(__GLIBC__) #define PTRACE_REQUEST_TYPE enum __ptrace_request #else #define PTRACE_REQUEST_TYPE int #endif typedef enum { CURRENT = 0, ORIGINAL = 1, MODIFIED = 2, NB_REG_VERSION } RegVersion; struct bindings; struct load_info; struct extensions; struct chained_syscalls; /* Information related to a file-system name-space. */ typedef struct { struct { /* List of bindings as specified by the user but not canonicalized yet. */ struct bindings *pending; /* List of bindings canonicalized and sorted in the "guest" order. */ struct bindings *guest; /* List of bindings canonicalized and sorted in the "host" order. */ struct bindings *host; } bindings; /* Current working directory, à la /proc/self/pwd. */ char *cwd; } FileSystemNameSpace; /* Virtual heap, emulated with a regular memory mapping. */ typedef struct { word_t base; size_t size; bool disabled; } Heap; /* Information related to a tracee process. */ typedef struct tracee { /********************************************************************** * Private resources * **********************************************************************/ /* Link for the list of all tracees. */ LIST_ENTRY(tracee) link; /* Process identifier. */ pid_t pid; /* Unique tracee identifier. */ uint64_t vpid; /* Is it currently running or not? */ bool running; /* Is this tracee ready to be freed? TODO: move to a list * dedicated to terminated tracees instead. */ bool terminated; /* Whether termination of this tracee implies an immediate kill * of all tracees. */ bool killall_on_exit; /* Parent of this tracee, NULL if none. */ struct tracee *parent; /* Is it a "clone", i.e has the same parent as its creator. */ bool clone; /* Support for ptrace emulation (tracer side). */ struct { size_t nb_ptracees; LIST_HEAD(zombies, tracee) zombies; pid_t wait_pid; word_t wait_options; enum { DOESNT_WAIT = 0, WAITS_IN_KERNEL, WAITS_IN_PROOT } waits_in; } as_ptracer; /* Support for ptrace emulation (tracee side). */ struct { struct tracee *ptracer; struct { #define STRUCT_EVENT struct { int value; bool pending; } STRUCT_EVENT proot; STRUCT_EVENT ptracer; } event4; bool tracing_started; bool ignore_loader_syscalls; bool ignore_syscalls; word_t options; bool is_zombie; } as_ptracee; /* Current status: * 0: enter syscall * 1: exit syscall no error * -errno: exit syscall with error. */ int status; #define IS_IN_SYSENTER(tracee) ((tracee)->status == 0) #define IS_IN_SYSEXIT(tracee) (!IS_IN_SYSENTER(tracee)) #define IS_IN_SYSEXIT2(tracee, sysnum) (IS_IN_SYSEXIT(tracee) \ && get_sysnum((tracee), ORIGINAL) == sysnum) /* How this tracee is restarted. */ PTRACE_REQUEST_TYPE restart_how; /* Value of the tracee's general purpose registers. */ struct user_regs_struct _regs[NB_REG_VERSION]; bool _regs_were_changed; bool restore_original_regs; /* State for the special handling of SIGSTOP. */ enum { SIGSTOP_IGNORED = 0, /* Ignore SIGSTOP (once the parent is known). */ SIGSTOP_ALLOWED, /* Allow SIGSTOP (once the parent is known). */ SIGSTOP_PENDING, /* Block SIGSTOP until the parent is unknown. */ } sigstop; /* Context used to collect all the temporary dynamic memory * allocations. */ TALLOC_CTX *ctx; /* Context used to collect all dynamic memory allocations that * should be released once this tracee is freed. */ TALLOC_CTX *life_context; /* Note: I could rename "ctx" in "event_span" and * "life_context" in "life_span". */ /* Specify the type of the final component during the * initialization of a binding. This variable is first * defined in bind_path() then used in build_glue(). */ mode_t glue_type; /* During a sub-reconfiguration, the new setup is relatively * to @tracee's file-system name-space. Also, @paths holds * its $PATH environment variable in order to emulate the * execvp(3) behavior. */ struct { struct tracee *tracee; const char *paths; } reconf; /* Unrequested syscalls inserted by PRoot after an actual * syscall. */ struct { struct chained_syscalls *syscalls; bool force_final_result; word_t final_result; } chain; /* Load info generated during execve sysenter and used during * execve sysexit. */ struct load_info *load_info; /* Disable mixed-execution (native host) check */ bool mixed_mode; /********************************************************************** * Private but inherited resources * **********************************************************************/ /* Verbose level. */ int verbose; /* State of the seccomp acceleration for this tracee. */ enum { DISABLED = 0, DISABLING, ENABLED } seccomp; /* Ensure the sysexit stage is always hit under seccomp. */ bool sysexit_pending; /********************************************************************** * Shared or private resources, depending on the CLONE_FS/VM flags. * **********************************************************************/ /* Information related to a file-system name-space. */ FileSystemNameSpace *fs; /* Virtual heap, emulated with a regular memory mapping. */ Heap *heap; /********************************************************************** * Shared resources until the tracee makes a call to execve(). * **********************************************************************/ /* Path to the executable, à la /proc/self/exe. */ char *exe; char *new_exe; /********************************************************************** * Shared or private resources, depending on the (re-)configuration * **********************************************************************/ /* Runner command-line. */ char **qemu; /* Path to glue between the guest rootfs and the host rootfs. */ const char *glue; /* List of extensions enabled for this tracee. */ struct extensions *extensions; /********************************************************************** * Shared but read-only resources * **********************************************************************/ /* For the mixed-mode, the guest LD_LIBRARY_PATH is saved * during the "guest -> host" transition, in order to be * restored during the "host -> guest" transition (only if the * host LD_LIBRARY_PATH hasn't changed). */ const char *host_ldso_paths; const char *guest_ldso_paths; /* For diagnostic purpose. */ const char *tool_name; } Tracee; #define HOST_ROOTFS "/host-rootfs" #define TRACEE(a) talloc_get_type_abort(talloc_parent(talloc_parent(a)), Tracee) extern Tracee *get_tracee(const Tracee *tracee, pid_t pid, bool create); extern Tracee *get_ptracee(const Tracee *ptracer, pid_t pid, bool only_stopped, bool only_with_pevent, word_t wait_options); extern Tracee *get_stopped_ptracee(const Tracee *ptracer, pid_t pid, bool only_with_pevent, word_t wait_options); extern bool has_ptracees(const Tracee *ptracer, pid_t pid, word_t wait_options); extern int new_child(Tracee *parent, word_t clone_flags); extern Tracee *new_dummy_tracee(TALLOC_CTX *context); extern void terminate_tracee(Tracee *tracee); extern void free_terminated_tracees(); extern int swap_config(Tracee *tracee1, Tracee *tracee2); extern void kill_all_tracees(); #endif /* TRACEE_H */ proot-5.4.0/test/000077500000000000000000000000001442763353300136545ustar00rootroot00000000000000proot-5.4.0/test/GNUmakefile000066400000000000000000000142711442763353300157330ustar00rootroot00000000000000DIR = $(dir $(abspath $(firstword $(MAKEFILE_LIST)))) ROOTFS = $(DIR)/rootfs SRC_DIR = $(DIR)/../src PROOT = $(SRC_DIR)/proot CARE = $(SRC_DIR)/care CC = gcc PROOT_RAW = $(PROOT) CHECK_TESTS = $(patsubst %,check-%, $(wildcard test-*.sh) $(wildcard test-*.c)) .PHONY: check clean_failure check_failure setup check-% check: | clean_failure check_failure memcheck: PROOT_RAW := $(PROOT) memcheck: PROOT := $(shell which valgrind) -q --error-exitcode=1 $(PROOT) memcheck: check clean_failure: @rm -f failure check_failure: $(CHECK_TESTS) @bash -c '! test -e failure' check-%.sh: %.sh setup $(Q)env CARE="$(CARE)" PROOT_RAW="$(PROOT_RAW)" PROOT="$(PROOT)" ROOTFS=$(ROOTFS) PATH="$${PATH}:/bin" sh -ex $< $(silently); $(call check,$*) check-%.c: $(ROOTFS)/bin/% setup $(call check_c,$*,$(PROOT) -b /proc -r $(ROOTFS) /bin/$*) # Special cases. check-test-sysexit.c: test-sysexit $(call check_c,$<,env PROOT_FORCE_KOMPAT=1 $(PROOT) -k 3.4242XX ./$<) check-test-bdc90417.c: test-bdc90417 $(call check_c,$<,$(PROOT) -w . ./$<) check-test-5bed7141.c: test-5bed7141 $(call check_c,$<,$(PROOT) ./$<) check-test-5bed7143.c: test-5bed7143 $(call check_c,$<,$(PROOT) -r $(ROOTFS) -b . ./$<) $(call check_c,$<,$(PROOT) ./$<) check-test-16573e73.c: test-16573e73 $(call check_c,$<,$(PROOT) ./$<) $(call check_c,$<,$(PROOT) ./$< 1) check-test-82ba4ba1.c: test-82ba4ba1 $(call check_c,$<,$(PROOT) -0 ./$<) $(call check_c,$<,! $(PROOT) -i 123:456 ./$<) check-test-kkkkkkkk.c: test-kkkkkkkk $(call check_c,$<,$(PROOT) ./$<) check-test-25069c12.c: test-25069c12 $(call check_c,$<,$(PROOT) ./$<) check-test-25069c13.c: test-25069c13 $(call check_c,$<,$(PROOT) ./$<) check-test-1ffc8309.c: test-1ffc8309 $(call check_c,$<,env PROOT_FORCE_KOMPAT=1 $(PROOT) -k $(shell uname -r) ./$<) check-test-c5a7a0f0.c: test-c5a7a0f0 $(call check_c,$<,$(PROOT) -0 ./$<) $(call check_c,$<,! $(PROOT) -i 123:456 ./$<) #check-test-a3e68988.c: test-a3e68988 # @which gdb >/dev/null 2>&1 || rm -f $< # $(call check_c,$<,$(PROOT) gdb -return-child-result -ex run -ex quit ./$<) #check-test-c47aeb7d.c: test-c47aeb7d # @which gdb >/dev/null 2>&1 || rm -f $< # $(call check_c,$<,$(PROOT) gdb -return-child-result -ex run -ex quit ./$<) check-test-fdf487a0.c: test-fdf487a0 $(call check_c,$<,echo test | $(PROOT) ./$<) check-test-iiiiiiii.c: test-iiiiiiii $(call check_c,$<,echo test | env PROOT_DONT_POLLUTE_ROOTFS=1 $(PROOT) -b /bin:/this_shall_not_exist_outside_proot ./$<) check-test-9c07fad8.c: test-9c07fad8 $(call check_c,$<,$(PROOT) ./$<) check-test-fa205b56.c: test-fa205b56 $(call check_c,$<,$(PROOT) ./$<) check_c = $(Q)if [ -e $< ]; then \ $(2) $(silently); $(call check,$(1)) \ else \ echo " CHECK $(1) skipped"; \ fi check = case "$$?" in \ 0) echo " CHECK $(1) ok";; \ 125) echo " CHECK $(1) skipped";; \ *) echo " CHECK $(1) FAILED"; \ touch failure ;; \ esac ###################################################################### # Build a clean rootfs ROOTFS_BIN = $(ROOTFS)/bin/true $(ROOTFS)/bin/false \ $(ROOTFS)/bin/pwd $(ROOTFS)/bin/readlink $(ROOTFS)/bin/symlink \ $(ROOTFS)/bin/abs-true $(ROOTFS)/bin/rel-true $(ROOTFS)/bin/echo \ $(ROOTFS)/bin/argv0 $(ROOTFS)/bin/readdir $(ROOTFS)/bin/cat \ $(ROOTFS)/bin/chdir_getcwd $(ROOTFS)/bin/fchdir_getcwd $(ROOTFS)/bin/argv \ $(ROOTFS)/bin/fork-wait $(ROOTFS)/bin/ptrace $(ROOTFS)/bin/ptrace-2 \ $(ROOTFS)/bin/ptrace-3 $(ROOTFS)/bin/gdb-ptrace-test $(ROOTFS)/bin/gdb-ptrace-test-signal \ $(ROOTFS)/bin/puts_proc_self_exe $(ROOTFS)/bin/exec $(ROOTFS)/bin/exec-m32 \ $(ROOTFS)/bin/exec-suid $(ROOTFS)/bin/exec-sgid $(ROOTFS)/bin/exec-m32-suid \ $(ROOTFS)/bin/exec-m32-sgid $(ROOTFS)/bin/getresuid $(ROOTFS)/bin/getresgid \ $(ROOTFS)/bin/chroot ROOTFS_DIR = $(ROOTFS)/bin $(ROOTFS)/tmp $(ROOTFS_BIN): | $(ROOTFS_DIR) $(ROOTFS_DIR): @mkdir -p $@ setup: $(ROOTFS_BIN) $(ROOTFS)/bin/abs-true: $(Q)ln -fs $$(which true) $@ $(ROOTFS)/bin/rel-true: $(Q)ln -fs ./true $@ $(ROOTFS)/bin/exec-m32: exec.c $(Q)$(CC) -m32 -static $^ -o $@ $(silently) || true $(ROOTFS)/bin/exec-suid: $(ROOTFS)/bin/exec $(Q)cp $^ $@ $(Q)chmod u+s $@ $(ROOTFS)/bin/exec-sgid: $(ROOTFS)/bin/exec $(Q)cp $^ $@ $(Q)chmod g+s $@ $(ROOTFS)/bin/exec-m32-suid: $(ROOTFS)/bin/exec-m32 $(Q)cp $^ $@ $(silently) || true $(Q)chmod u+s $@ $(silently) || true $(ROOTFS)/bin/exec-m32-sgid: $(ROOTFS)/bin/exec-m32 $(Q)cp $^ $@ $(silently) || true $(Q)chmod g+s $@ $(silently) || true .SECONDARY: $(patsubst %.c,$(ROOTFS)/bin/%, $(wildcard test-*.c)) $(ROOTFS)/bin/%: %.c $(Q)$(CC) -static $*.c -o $@ $(silently) || true # Special cases. test-bdc90417: test-bdc90417.c $(Q)$(CC) $< -o $@ $(silently) || true # Not supported anymore. # test-af062114: test-af062114.c # $(Q)$(CC) $< -Wl,-rpath=foo -o $@ $(silently) || true test-5bed7141: test-5bed7141.c $(Q)$(CC) $< -pthread -static -o $@ $(silently) || true test-16573e73: test-16573e73.c $(Q)$(CC) $< -static -o $@ $(silently) || true test-82ba4ba1: test-82ba4ba1.c $(Q)$(CC) $< -o $@ $(silently) || true test-kkkkkkkk: test-kkkkkkkk.c $(Q)$(CC) $< -o $@ $(silently) || true test-25069c12: test-25069c12.c $(Q)$(CC) $< -o $@ $(silently) || true test-25069c13: test-25069c13.c $(Q)$(CC) $< -o $@ $(silently) || true test-5bed7143: test-5bed7143.c $(Q)$(CC) $< -static -o $@ $(silently) || true test-1ffc8309: test-1ffc8309.c $(Q)$(CC) $< -o $@ $(silently) || true test-c5a7a0f0: test-c5a7a0f0.c $(Q)$(CC) $< -pthread -static -o $@ $(silently) || true test-a3e68988: test-a3e68988.c $(Q)$(CC) $< -o $@ $(silently) || true test-fdf487a0: test-fdf487a0.c $(Q)$(CC) $< -o $@ $(silently) || true test-iiiiiiii: test-iiiiiiii.c $(Q)$(CC) $< -o $@ $(silently) || true test-9c07fad8: test-9c07fad8.c $(Q)$(CC) -fPIE -pie $< -o $@ $(silently) || true test-fa205b56: test-fa205b56.c $(Q)$(CC) $< -pthread -o $@ $(silently) || true test-c47aeb7d: test-c47aeb7d.c $(Q)$(CC) $< -pthread -o $@ $(silently) || true ###################################################################### # Beautified output V = 0 QUIET_LOG = /dev/null ifeq ($(V), 0) quiet = quiet_ Q = @ silently = >> $(QUIET_LOG) 2>&1 else quiet = Q = silently = endif proot-5.4.0/test/argv.c000066400000000000000000000002041442763353300147530ustar00rootroot00000000000000#include int main(int argc, char *argv[]) { int i; for (i = 0; i < argc; i++) printf("%s ", argv[i]); return 0; } proot-5.4.0/test/argv0.c000066400000000000000000000001231442763353300150330ustar00rootroot00000000000000#include int main(int argc, char **argv) { puts(argv[0]); return 0; } proot-5.4.0/test/cat.c000066400000000000000000000011241442763353300145650ustar00rootroot00000000000000#include #include #include #include #include #include #include int main(int argc, char *argv[]) { int status; int fd; int i; for (i = 1; i < argc; i++) { char buffer[1024]; fd = open(argv[i], O_RDONLY); if (fd < 0) { perror("open(2)"); exit(EXIT_FAILURE); } while ((status = read(fd, buffer, sizeof(buffer))) > 0 && write(1, buffer, status) == status) errno = 0; if (errno != 0) { perror("read(2)/write(2)"); exit(EXIT_FAILURE); } (void) close(fd); } exit(EXIT_SUCCESS); } proot-5.4.0/test/chdir_getcwd.c000066400000000000000000000010731442763353300164470ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include #include #include #include #include int main(int argc, char *argv[]) { char path[PATH_MAX]; int status; int fd; if (argc < 2) { fprintf(stderr, "missing argument\n"); exit(EXIT_FAILURE); } status = chdir(argv[1]); if (status < 0) { perror("chdir()"); exit(EXIT_FAILURE); } if (getcwd(path, PATH_MAX) == NULL) { perror("getcwd()"); exit(EXIT_FAILURE); } printf("%s\n", get_current_dir_name()); exit(EXIT_SUCCESS); } proot-5.4.0/test/chroot.c000066400000000000000000000003701442763353300153160ustar00rootroot00000000000000#include #include int main(int argc, char **argv) { if (argc < 2) exit(1); if (chroot(argv[1])) exit(1); if (argc >= 3) execvp(argv[2], &argv[2]); else execlp("sh", "sh", NULL); /* unreachable */ return 1; } proot-5.4.0/test/docker/000077500000000000000000000000001442763353300151235ustar00rootroot00000000000000proot-5.4.0/test/docker/alpine/000077500000000000000000000000001442763353300163735ustar00rootroot00000000000000proot-5.4.0/test/docker/alpine/Dockerfile000066400000000000000000000001021442763353300203560ustar00rootroot00000000000000FROM alpine:latest COPY . /usr/src/proot WORKDIR /usr/src/proot proot-5.4.0/test/docker/alpine/x86_64/000077500000000000000000000000001442763353300173315ustar00rootroot00000000000000proot-5.4.0/test/docker/alpine/x86_64/Dockerfile000066400000000000000000000006071442763353300213260ustar00rootroot00000000000000FROM proot-me/proot:alpine RUN apk update && \ apk upgrade && \ apk add \ bash \ bsd-compat-headers \ coreutils \ git \ grep \ libarchive-dev \ libxslt \ linux-headers \ lzo \ make \ mcookie \ musl-dev \ python3-dev \ py3-docutils \ strace \ swig \ talloc-dev \ uthash-dev proot-5.4.0/test/docker/alpine/x86_64/clang/000077500000000000000000000000001442763353300204155ustar00rootroot00000000000000proot-5.4.0/test/docker/alpine/x86_64/clang/Dockerfile000066400000000000000000000001051442763353300224030ustar00rootroot00000000000000FROM proot-me/proot:alpine-x86_64 RUN apk add clang clang-analyzer proot-5.4.0/test/docker/alpine/x86_64/gcc/000077500000000000000000000000001442763353300200655ustar00rootroot00000000000000proot-5.4.0/test/docker/alpine/x86_64/gcc/Dockerfile000066400000000000000000000002341442763353300220560ustar00rootroot00000000000000FROM proot-me/proot:alpine-x86_64 RUN apk add \ --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing \ gcc \ gdb \ lcov proot-5.4.0/test/docker/centos/000077500000000000000000000000001442763353300164165ustar00rootroot00000000000000proot-5.4.0/test/docker/centos/Dockerfile000066400000000000000000000001021442763353300204010ustar00rootroot00000000000000FROM centos:latest COPY . /usr/src/proot WORKDIR /usr/src/proot proot-5.4.0/test/docker/centos/x86_64/000077500000000000000000000000001442763353300173545ustar00rootroot00000000000000proot-5.4.0/test/docker/centos/x86_64/Dockerfile000066400000000000000000000005631442763353300213520ustar00rootroot00000000000000FROM proot-me/proot:centos RUN yum update -y && \ yum install -y yum-utils && \ yum-config-manager --enable extras && \ yum makecache && \ yum group install -y 'Development Tools' && \ yum install -y \ git \ libarchive-devel \ libtalloc-devel \ python-devel \ sloccount \ strace \ swig \ uthash-devel proot-5.4.0/test/docker/centos/x86_64/clang/000077500000000000000000000000001442763353300204405ustar00rootroot00000000000000proot-5.4.0/test/docker/centos/x86_64/clang/Dockerfile000066400000000000000000000001341442763353300224300ustar00rootroot00000000000000FROM proot-me/proot:centos-x86_64 RUN yum install -y \ clang \ clang-analyzer proot-5.4.0/test/docker/centos/x86_64/gcc/000077500000000000000000000000001442763353300201105ustar00rootroot00000000000000proot-5.4.0/test/docker/centos/x86_64/gcc/Dockerfile000066400000000000000000000000431442763353300220770ustar00rootroot00000000000000FROM proot-me/proot:centos-x86_64 proot-5.4.0/test/docker/debian/000077500000000000000000000000001442763353300163455ustar00rootroot00000000000000proot-5.4.0/test/docker/debian/Dockerfile000066400000000000000000000000751442763353300203410ustar00rootroot00000000000000FROM debian:8 COPY . /usr/src/proot WORKDIR /usr/src/proot proot-5.4.0/test/docker/debian/x86_64/000077500000000000000000000000001442763353300173035ustar00rootroot00000000000000proot-5.4.0/test/docker/debian/x86_64/Dockerfile000066400000000000000000000004011442763353300212700ustar00rootroot00000000000000FROM proot-me/proot:debian RUN apt update && \ apt install -y \ curl \ docutils-common \ git \ libarchive-dev \ libtalloc-dev \ make \ sloccount \ strace \ swig \ uthash-dev \ xsltproc proot-5.4.0/test/docker/debian/x86_64/clang/000077500000000000000000000000001442763353300203675ustar00rootroot00000000000000proot-5.4.0/test/docker/debian/x86_64/clang/Dockerfile000066400000000000000000000001351442763353300223600ustar00rootroot00000000000000FROM proot-me/proot:alpine-x86_64 RUN apt install -y \ clang \ clang-tools-4.0 proot-5.4.0/test/docker/debian/x86_64/gcc/000077500000000000000000000000001442763353300200375ustar00rootroot00000000000000proot-5.4.0/test/docker/debian/x86_64/gcc/Dockerfile000066400000000000000000000001341442763353300220270ustar00rootroot00000000000000FROM proot-me/proot:debian-x86_64 RUN apt install -y \ gcc \ gdb \ lcov proot-5.4.0/test/echo.c000066400000000000000000000001721442763353300147360ustar00rootroot00000000000000#include int main(int argc, char **argv) { int i; for (i = 1; i < argc; i++) puts(argv[i]); return 0; } proot-5.4.0/test/exec.c000066400000000000000000000004531442763353300147460ustar00rootroot00000000000000#include #include #include int main(int argc, char *argv[], char *envp[]) { if (argc < 2) { puts("not enough parameter: filename argv[0] argv[1] ... argv[n]"); exit(EXIT_FAILURE); } execve(argv[1], &argv[2], envp); perror("execve"); exit(EXIT_FAILURE); } proot-5.4.0/test/false.c000066400000000000000000000000351442763353300151100ustar00rootroot00000000000000int main(void) { return 1; } proot-5.4.0/test/fchdir_getcwd.c000066400000000000000000000012271442763353300166160ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include #include #include #include #include int main(int argc, char *argv[]) { char path[PATH_MAX]; int status; int fd; if (argc < 2) { fprintf(stderr, "missing argument\n"); exit(EXIT_FAILURE); } fd = open(argv[1], O_DIRECTORY); if (fd < 0) { perror("open()"); exit(EXIT_FAILURE); } status = fchdir(fd); if (status < 0) { perror("fchdir()"); exit(EXIT_FAILURE); } if (getcwd(path, PATH_MAX) == NULL) { perror("getcwd()"); exit(EXIT_FAILURE); } printf("%s\n", get_current_dir_name()); exit(EXIT_SUCCESS); } proot-5.4.0/test/fix_memory_corruption_execve_proc_self_exe.c000066400000000000000000000004741442763353300247230ustar00rootroot00000000000000#include /* execlp(2), */ #include /* exit(3), getenv(3), setenv(3)*/ #include /* strcmp(3), */ int main(int argc, char *argv[]) { if (getenv("PROC_SELF_EXE") != NULL) exit(EXIT_SUCCESS); setenv("PROC_SELF_EXE", "1", 1); execlp("/proc/self/exe", NULL); exit(EXIT_FAILURE); } proot-5.4.0/test/fork-wait.c000066400000000000000000000010261442763353300157220ustar00rootroot00000000000000#include /* exit(3), */ #include /* fork(2), sleep(3), */ #include /* wait(2), */ #include /* wait(2), */ int main(int argc, char *argv[]) { int child_status; int status; switch (fork()) { case -1: exit(EXIT_FAILURE); case 0: /* Child */ argc > 1 && sleep(2); exit(EXIT_SUCCESS); default: /* Parent */ argc <= 1 && sleep(2); status = wait(&child_status); if (status < 0 || !WIFEXITED(child_status)) exit(EXIT_FAILURE); exit(WEXITSTATUS(child_status)); } } proot-5.4.0/test/gdb-ptrace-test-signal.c000066400000000000000000000070001442763353300202550ustar00rootroot00000000000000// #include #include #include #include #include #include #include #include "../src/compat.h" static int fork_to_function(int (*function)(void)) { int child_pid = fork(); if (child_pid == -1) { perror("fork()"); exit(EXIT_FAILURE); } if (child_pid == 0) return function(); return child_pid; } static int grandchild_function(void) { exit(0); } static int child_function(void) { ptrace(PTRACE_TRACEME, 0, 0, 0); kill(getpid(), SIGSTOP); fork_to_function(grandchild_function); exit(0); } static void kill_child(int child_pid) { pid_t got_pid; int kill_status; if (kill(child_pid, SIGKILL) != 0) { perror("kill()"); exit(EXIT_FAILURE); } got_pid = waitpid (child_pid, &kill_status, 0); if (got_pid != child_pid) { fprintf(stderr, "waitpid: unexpected result %d.", (int)got_pid); exit(EXIT_FAILURE); } if (!WIFSIGNALED(kill_status)) { fprintf(stderr, "waitpid: unexpected status %d.", kill_status); exit(EXIT_FAILURE); } } static void test_tracefork(int child_pid) { int ret, status; long second_pid; ret = ptrace(PTRACE_SETOPTIONS, child_pid, 0, PTRACE_O_TRACEFORK); if (ret != 0) { // Skipped perror("ptrace(PTRACE_SETOPTIONS, PTRACE_O_TRACEFORK)"); kill_child(child_pid); exit(125); } ret = ptrace (PTRACE_CONT, child_pid, 0, 0); if (ret != 0) { perror("ptrace(PTRACE_CONT)"); kill_child(child_pid); exit(EXIT_FAILURE); } ret = waitpid(child_pid, &status, 0); /* Check if we received a fork event notification. */ if (ret == child_pid && WIFSTOPPED(status) && (status >> 16) == PTRACE_EVENT_FORK) { /* We did receive a fork event notification. Make sure its PID is reported. */ second_pid = 0; ret = ptrace(PTRACE_GETEVENTMSG, child_pid, 0, &second_pid); if (ret == 0 && second_pid != 0) { int second_status; /* Do some cleanup and kill the grandchild. */ waitpid(second_pid, &second_status, 0); kill_child(second_pid); } else { perror("ptrace(PTRACE_GETEVENTMSG)"); exit(EXIT_FAILURE); } } else { fprintf(stderr, "Unexpected result from waitpid: pid=%d, status=%d\n", ret, status); exit(EXIT_FAILURE); } } static void check_ptrace_features(int do_fork) { int child_pid, ret, status; child_pid = fork_to_function(child_function); ret = waitpid(child_pid, &status, 0); if (ret == -1) { perror("waitpid()"); exit(EXIT_FAILURE); } else if (ret != child_pid) { fprintf(stderr, "waitpid: unexpected result %d.", ret); exit(EXIT_FAILURE); } else if (!WIFSTOPPED(status)) { fprintf(stderr, "waitpid: unexpected status %d.", status); exit(EXIT_FAILURE); } if (do_fork) test_tracefork(child_pid); kill_child(child_pid); } static int devnull = -1; // SIGCHLD handler which has a high chance of triggering // right before the chained wait4 call. // We need to make sure things are handled correctly even if we static void sigchld_handler(int signo) { if (devnull != -1) close(devnull); devnull = open("/dev/null", O_RDONLY); if (devnull == -1) { perror("open(/dev/null)"); exit(EXIT_FAILURE); } } int main(int argc, char **argv) { struct sigaction sigchld_action = {}; sigchld_action.sa_handler = sigchld_handler; sigemptyset (&sigchld_action.sa_mask); sigchld_action.sa_flags = SA_RESTART; /* Make it the default. */ sigaction(SIGCHLD, &sigchld_action, NULL); (void)argv; check_ptrace_features(argc > 1); if (devnull == -1) { fprintf(stderr, "SIGCHLD handler not called.\n"); exit(EXIT_FAILURE); } close(devnull); return 0; } proot-5.4.0/test/gdb-ptrace-test.c000066400000000000000000000056271442763353300170170ustar00rootroot00000000000000// #include #include #include #include #include #include #include "../src/compat.h" static int fork_to_function(int (*function)(void)) { int child_pid = fork(); if (child_pid == -1) { perror("fork()"); exit(EXIT_FAILURE); } if (child_pid == 0) return function(); return child_pid; } static int grandchild_function(void) { exit(0); } static int child_function(void) { ptrace(PTRACE_TRACEME, 0, 0, 0); kill(getpid(), SIGSTOP); fork_to_function(grandchild_function); exit(0); } static void kill_child(int child_pid) { pid_t got_pid; int kill_status; if (kill(child_pid, SIGKILL) != 0) { perror("kill()"); exit(EXIT_FAILURE); } // Sleep some time to let proot handle the kill event // in order to make sure a delayed wait on a dead ptracee works correctly. sleep(2); got_pid = waitpid(child_pid, &kill_status, 0); if (got_pid != child_pid) { fprintf(stderr, "waitpid: unexpected result %d.", (int)got_pid); exit(EXIT_FAILURE); } if (!WIFSIGNALED(kill_status)) { fprintf(stderr, "waitpid: unexpected status %d.", kill_status); exit(EXIT_FAILURE); } } static void test_tracefork(int child_pid) { int ret, status; long second_pid; ret = ptrace(PTRACE_SETOPTIONS, child_pid, 0, PTRACE_O_TRACEFORK); if (ret != 0) { // Skipped perror("ptrace(PTRACE_SETOPTIONS, PTRACE_O_TRACEFORK)"); kill_child(child_pid); exit(125); } ret = ptrace(PTRACE_CONT, child_pid, 0, 0); if (ret != 0) { perror("ptrace(PTRACE_CONT)"); kill_child(child_pid); exit(EXIT_FAILURE); } ret = waitpid(child_pid, &status, 0); /* Check if we received a fork event notification. */ if (ret == child_pid && WIFSTOPPED(status) && (status >> 16) == PTRACE_EVENT_FORK) { /* We did receive a fork event notification. Make sure its PID is reported. */ second_pid = 0; ret = ptrace(PTRACE_GETEVENTMSG, child_pid, 0, &second_pid); if (ret == 0 && second_pid != 0) { int second_status; /* Do some cleanup and kill the grandchild. */ waitpid(second_pid, &second_status, 0); kill_child(second_pid); } else { perror("ptrace(PTRACE_GETEVENTMSG)"); exit(EXIT_FAILURE); } } else { fprintf(stderr, "Unexpected result from waitpid: pid=%d, status=%d\n", ret, status); exit(EXIT_FAILURE); } } static void check_ptrace_features(int do_fork) { int child_pid, ret, status; child_pid = fork_to_function(child_function); ret = waitpid(child_pid, &status, 0); if (ret == -1) { perror("waitpid()"); exit(EXIT_FAILURE); } else if (ret != child_pid) { fprintf(stderr, "waitpid: unexpected result %d.", ret); exit(EXIT_FAILURE); } else if (!WIFSTOPPED(status)) { fprintf(stderr, "waitpid: unexpected status %d.", status); exit(EXIT_FAILURE); } if (do_fork) test_tracefork(child_pid); kill_child(child_pid); } int main(int argc, char **argv) { (void)argv; check_ptrace_features(argc > 1); return 0; } proot-5.4.0/test/getresgid.c000066400000000000000000000003511442763353300157740ustar00rootroot00000000000000#define _GNU_SOURCE /* See feature_test_macros(7) */ #include #include int main() { uid_t rid; uid_t eid; uid_t sid; getresgid(&rid, &eid, &sid); printf("%d %d %d\n", rid, eid, sid); return 0; } proot-5.4.0/test/getresuid.c000066400000000000000000000003511442763353300160120ustar00rootroot00000000000000#define _GNU_SOURCE /* See feature_test_macros(7) */ #include #include int main() { uid_t rid; uid_t eid; uid_t sid; getresuid(&rid, &eid, &sid); printf("%d %d %d\n", rid, eid, sid); return 0; } proot-5.4.0/test/ptrace-2.c000066400000000000000000000270121442763353300154370ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- * * This file is part of PRoot. * * Copyright (C) 2015 STMicroelectronics * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include /* pid_t, waitpid(2), */ #include /* ptrace(3), PTRACE_*, */ #include /* struct user*, */ #include /* waitpid(2), */ #include /* offsetof(), */ #include /* fork(2), getpid(2), */ #include /* errno(3), */ #include /* bool, true, false, */ #include /* exit, EXIT_*, */ #include /* fprintf(3), stderr, */ #include /* *int*_t, */ #include /* strerror */ #include /* struct iovec */ #include /* NT_PRSTATUS */ #include "../src/arch.h" #if !defined(ARCH_X86_64) && !defined(ARCH_ARM_EABI) && !defined(ARCH_X86) && !defined(ARCH_SH4) # if defined(__x86_64__) # define ARCH_X86_64 1 # elif defined(__ARM_EABI__) # define ARCH_ARM_EABI 1 # elif defined(__aarch64__) # define ARCH_ARM64 1 # elif defined(__arm__) # error "Only EABI is currently supported for ARM" # elif defined(__i386__) # define ARCH_X86 1 # elif defined(__SH4__) # define ARCH_SH4 1 # else # error "Unsupported architecture" # endif #endif /** * Compute the offset of the register @reg_name in the USER area. */ #define USER_REGS_OFFSET(reg_name) \ (offsetof(struct user, regs) \ + offsetof(struct user_regs_struct, reg_name)) #define REG(regs, index) \ (*(unsigned long*) (((uint8_t *) ®s) + reg_offset[index])) typedef enum { SYSARG_NUM = 0, SYSARG_1, SYSARG_2, SYSARG_3, SYSARG_4, SYSARG_5, SYSARG_6, SYSARG_RESULT, STACK_POINTER, INSTR_POINTER, } Reg; /* Specify the ABI registers (syscall argument passing, stack pointer). * See sysdeps/unix/sysv/linux/${ARCH}/syscall.S from the GNU C Library. */ #if defined(ARCH_X86_64) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(orig_rax), [SYSARG_1] = USER_REGS_OFFSET(rdi), [SYSARG_2] = USER_REGS_OFFSET(rsi), [SYSARG_3] = USER_REGS_OFFSET(rdx), [SYSARG_4] = USER_REGS_OFFSET(r10), [SYSARG_5] = USER_REGS_OFFSET(r8), [SYSARG_6] = USER_REGS_OFFSET(r9), [SYSARG_RESULT] = USER_REGS_OFFSET(rax), [STACK_POINTER] = USER_REGS_OFFSET(rsp), [INSTR_POINTER] = USER_REGS_OFFSET(rip), }; static off_t reg_offset_x86[] = { [SYSARG_NUM] = USER_REGS_OFFSET(orig_rax), [SYSARG_1] = USER_REGS_OFFSET(rbx), [SYSARG_2] = USER_REGS_OFFSET(rcx), [SYSARG_3] = USER_REGS_OFFSET(rdx), [SYSARG_4] = USER_REGS_OFFSET(rsi), [SYSARG_5] = USER_REGS_OFFSET(rdi), [SYSARG_6] = USER_REGS_OFFSET(rbp), [SYSARG_RESULT] = USER_REGS_OFFSET(rax), [STACK_POINTER] = USER_REGS_OFFSET(rsp), [INSTR_POINTER] = USER_REGS_OFFSET(rip), }; #undef REG #define REG(regs, index) \ (*(unsigned long*) (regs.cs == 0x23 \ ? (((uint8_t *) ®s) + reg_offset_x86[index]) \ : (((uint8_t *) ®s) + reg_offset[index]))) #elif defined(ARCH_ARM_EABI) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(uregs[7]), [SYSARG_1] = USER_REGS_OFFSET(uregs[0]), [SYSARG_2] = USER_REGS_OFFSET(uregs[1]), [SYSARG_3] = USER_REGS_OFFSET(uregs[2]), [SYSARG_4] = USER_REGS_OFFSET(uregs[3]), [SYSARG_5] = USER_REGS_OFFSET(uregs[4]), [SYSARG_6] = USER_REGS_OFFSET(uregs[5]), [SYSARG_RESULT] = USER_REGS_OFFSET(uregs[0]), [STACK_POINTER] = USER_REGS_OFFSET(uregs[13]), [INSTR_POINTER] = USER_REGS_OFFSET(uregs[15]), }; #elif defined(ARCH_ARM64) #undef USER_REGS_OFFSET #define USER_REGS_OFFSET(reg_name) offsetof(struct user_regs_struct, reg_name) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(regs[8]), [SYSARG_1] = USER_REGS_OFFSET(regs[0]), [SYSARG_2] = USER_REGS_OFFSET(regs[1]), [SYSARG_3] = USER_REGS_OFFSET(regs[2]), [SYSARG_4] = USER_REGS_OFFSET(regs[3]), [SYSARG_5] = USER_REGS_OFFSET(regs[4]), [SYSARG_6] = USER_REGS_OFFSET(regs[5]), [SYSARG_RESULT] = USER_REGS_OFFSET(regs[0]), [STACK_POINTER] = USER_REGS_OFFSET(sp), [INSTR_POINTER] = USER_REGS_OFFSET(pc), }; #elif defined(ARCH_X86) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(orig_eax), [SYSARG_1] = USER_REGS_OFFSET(ebx), [SYSARG_2] = USER_REGS_OFFSET(ecx), [SYSARG_3] = USER_REGS_OFFSET(edx), [SYSARG_4] = USER_REGS_OFFSET(esi), [SYSARG_5] = USER_REGS_OFFSET(edi), [SYSARG_6] = USER_REGS_OFFSET(ebp), [SYSARG_RESULT] = USER_REGS_OFFSET(eax), [STACK_POINTER] = USER_REGS_OFFSET(esp), [INSTR_POINTER] = USER_REGS_OFFSET(eip), }; #elif defined(ARCH_SH4) static off_t reg_offset[] = { [SYSARG_NUM] = USER_REGS_OFFSET(regs[3]), [SYSARG_1] = USER_REGS_OFFSET(regs[4]), [SYSARG_2] = USER_REGS_OFFSET(regs[5]), [SYSARG_3] = USER_REGS_OFFSET(regs[6]), [SYSARG_4] = USER_REGS_OFFSET(regs[7]), [SYSARG_5] = USER_REGS_OFFSET(regs[0]), [SYSARG_6] = USER_REGS_OFFSET(regs[1]), [SYSARG_RESULT] = USER_REGS_OFFSET(regs[0]), [STACK_POINTER] = USER_REGS_OFFSET(regs[15]), [INSTR_POINTER] = USER_REGS_OFFSET(pc), }; #else #error "Unsupported architecture" #endif #if defined(PTRACE_GETREGS) static long read_regs(pid_t pid, struct user_regs_struct *regs) { return ptrace(PTRACE_GETREGS, pid, NULL, regs); } #elif defined(PTRACE_GETREGSET) static long read_regs(pid_t pid, struct user_regs_struct *regs) { struct iovec data; data.iov_base = regs; data.iov_len = sizeof(struct user_regs_struct); return ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &data); } #else #error "PTRACE_GETREGS and PTRACE_GETREGSET not defined" #endif int main(int argc, char *argv[]) { enum __ptrace_request restart_how; int last_exit_status = -1; pid_t *pids = NULL; long status; int signal; pid_t pid; if (argc <= 1) { fprintf(stderr, "Usage: %s /path/to/exe [args]\n", argv[0]); exit(EXIT_FAILURE); } pid = fork(); switch(pid) { case -1: perror("fork()"); exit(EXIT_FAILURE); case 0: /* child */ status = ptrace(PTRACE_TRACEME, 0, NULL, NULL); if (status < 0) { perror("ptrace(TRACEME)"); exit(EXIT_FAILURE); } /* Synchronize with the tracer's event loop. */ kill(getpid(), SIGSTOP); execvp(argv[1], &argv[1]); exit(EXIT_FAILURE); default: /* parent */ break; } restart_how = (getenv("PTRACER_BEHAVIOR_1") == NULL ? PTRACE_SYSCALL : PTRACE_CONT); pids = calloc(1, sizeof(pid_t)); if (pids == NULL) { perror("calloc()"); exit(EXIT_FAILURE); } signal = 0; while (1) { int tracee_status; pid_t pid; pid_t sid; int i; /* Wait for the next tracee's stop. */ pid = waitpid(-1, &tracee_status, __WALL); if (pid < 0) { perror("waitpid()"); if (errno != ECHILD) exit(EXIT_FAILURE); break; } sid = 0; for (i = 0; pids[i] != 0; i++) { if (pid == pids[i]) { sid = i + 1; break; } } if (sid == 0) { pids = realloc(pids, (i + 1 + 1) * sizeof(pid_t)); if (pids == NULL) { perror("realloc()"); exit(EXIT_FAILURE); } pids[i + 1] = 0; pids[i] = pid; sid = i + 1; fprintf(stderr, "sid %d -> pid %d\n", sid, pid); } if (WIFEXITED(tracee_status)) { fprintf(stderr, "sid %d: exited with status %d\n", sid, WEXITSTATUS(tracee_status)); last_exit_status = WEXITSTATUS(tracee_status); continue; /* Skip the call to ptrace(SYSCALL). */ } else if (WIFSIGNALED(tracee_status)) { fprintf(stderr, "sid %d: terminated with signal %d\n", sid, WTERMSIG(tracee_status)); continue; /* Skip the call to ptrace(SYSCALL). */ } else if (WIFCONTINUED(tracee_status)) { fprintf(stderr, "sid %d: continued\n", sid); signal = SIGCONT; } else if (WIFSTOPPED(tracee_status)) { struct user_regs_struct regs; /* Don't use WSTOPSIG() to extract the signal * since it clears the PTRACE_EVENT_* bits. */ signal = (tracee_status & 0xfff00) >> 8; switch (signal) { static bool skip_bare_sigtrap = false; long ptrace_options; case SIGTRAP: fprintf(stderr, "sid %d: SIGTRAP\n", sid); status = read_regs(pid, ®s); if (status < 0) { fprintf(stderr, "sigtrap: ?, ?\n"); } else { fprintf(stderr, "sigtrap: %ld == 0 ? %d\n", REG(regs, SYSARG_NUM), REG(regs, SYSARG_RESULT) == 0); } /* PTRACER_BEHAVIOR_1 */ if (restart_how != PTRACE_SYSCALL) { restart_how = PTRACE_SYSCALL; signal = 0; break; } /* Distinguish some events from others and * automatically trace each new process with * the same options. * * Note that only the first bare SIGTRAP is * related to the tracing loop, others SIGTRAP * carry tracing information because of * TRACE*FORK/CLONE/EXEC. */ if (skip_bare_sigtrap) { signal = 0; break; } skip_bare_sigtrap = true; ptrace_options = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXIT; if (getenv("PTRACER_BEHAVIOR_2") == NULL) ptrace_options |= PTRACE_O_TRACEEXEC; status = ptrace(PTRACE_SETOPTIONS, pid, NULL, ptrace_options); if (status < 0) { perror("ptrace(PTRACE_SETOPTIONS)"); exit(EXIT_FAILURE); } /* Fall through. */ case SIGTRAP | 0x80: fprintf(stderr, "sid %d: PTRACE_EVENT_SYSGOOD\n", sid); signal = 0; status = read_regs(pid, ®s); if (status < 0) { fprintf(stderr, "syscall(?) = ?\n"); } else { fprintf(stderr, "syscall(%ld) == 0 ? %d\n", REG(regs, SYSARG_NUM), REG(regs, SYSARG_RESULT) == 0); } break; case SIGTRAP | PTRACE_EVENT_VFORK << 8: fprintf(stderr, "sid %d: PTRACE_EVENT_VFORK\n", sid); signal = 0; break; case SIGTRAP | PTRACE_EVENT_VFORK_DONE << 8: fprintf(stderr, "sid %d: PTRACE_EVENT_VFORK\n", sid); signal = 0; break; case SIGTRAP | PTRACE_EVENT_FORK << 8: fprintf(stderr, "sid %d: PTRACE_EVENT_FORK\n", sid); signal = 0; break; case SIGTRAP | PTRACE_EVENT_CLONE << 8: fprintf(stderr, "sid %d: PTRACE_EVENT_CLONE\n", sid); signal = 0; break; case SIGTRAP | PTRACE_EVENT_EXEC << 8: fprintf(stderr, "sid %d: PTRACE_EVENT_EXEC\n", sid); signal = 0; break; case SIGTRAP | PTRACE_EVENT_EXIT << 8: fprintf(stderr, "sid %d: PTRACE_EVENT_EXIT\n", sid); signal = 0; break; case SIGSTOP: fprintf(stderr, "sid %d: SIGSTOP\n", sid); signal = 0; break; default: break; } } else { fprintf(stderr, "sid %d: unknown trace event\n", sid); signal = 0; } /* Restart the tracee and stop it at the next entry or * exit of a system call. */ status = ptrace(restart_how, pid, NULL, signal); if (status < 0) fprintf(stderr, "ptrace(, %d, %d): %s\n", sid, signal, strerror(errno)); } return last_exit_status; } proot-5.4.0/test/ptrace-3.c000066400000000000000000000033741442763353300154450ustar00rootroot00000000000000#include #include #include #include #include #include #include #include /* errno(3), */ #include /* struct user*, */ int main(int argc, char **argv) { enum __ptrace_request restart_how; int last_exit_status = -1; int child_status; pid_t *pids = NULL; long status; int signal; pid_t pid; if (argc <= 1) { fprintf(stderr, "Usage: %s /path/to/exe [args]\n", argv[0]); exit(EXIT_FAILURE); } pid = fork(); switch(pid) { case -1: perror("fork()"); exit(EXIT_FAILURE); case 0: /* child */ status = ptrace(PTRACE_TRACEME, 0, NULL, NULL); if (status < 0) { perror("ptrace(TRACEME)"); exit(EXIT_FAILURE); } /* Synchronize with the tracer's event loop. */ kill(getpid(), SIGSTOP); execvp(argv[1], &argv[1]); exit(EXIT_FAILURE); default: /* parent */ break; } while (1) { int tracee_status; pid = waitpid(-1, &tracee_status, __WALL); if (pid < 0) { perror("waitpid()"); if (errno != ECHILD) exit(EXIT_FAILURE); break; } if (WIFEXITED(tracee_status)) { fprintf(stderr, "pid %d: exited with status %d\n", pid, WEXITSTATUS(tracee_status)); last_exit_status = WEXITSTATUS(tracee_status); continue; /* Skip the call to ptrace(SYSCALL). */ } else if (WIFSTOPPED(tracee_status)) { int signal = tracee_status >> 8; if (signal != SIGTRAP && signal != SIGSTOP) { // We expect a SIGSTOP since the child sends it to itself // and a SIGTRAP from the exec + ptrace. // Anything else is an error. fprintf(stderr, "Unexpected signal recieved from pid: %d.\n", pid); exit(EXIT_FAILURE); } status = ptrace(PTRACE_CONT, pid, NULL, 0); } } return last_exit_status; } proot-5.4.0/test/ptrace.c000066400000000000000000000016011442763353300152740ustar00rootroot00000000000000#include #include #include #include #include #include #include int main(int argc, char **argv) { int child_status; long status; pid_t pid; pid = (argc <= 1 ? fork() : vfork()); switch(pid) { case -1: perror("fork()"); exit(EXIT_FAILURE); case 0: /* child */ sleep(2); status = ptrace(PTRACE_TRACEME, 0, NULL, NULL); if (status < 0) { perror("ptrace(TRACEME)"); exit(EXIT_FAILURE); } if (argc <= 1) { kill(getpid(), SIGSTOP); exit(EXIT_SUCCESS); } else { execl("true", "true", NULL); exit(EXIT_FAILURE); } default: /* parent */ pid = waitpid(-1, &child_status, __WALL); if (pid < 0) { perror("waitpid()"); exit(EXIT_FAILURE); } if (argc <= 1) { if (!WIFSTOPPED(child_status)) exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } return 0; } proot-5.4.0/test/puts_proc_self_exe.c000066400000000000000000000004731442763353300177140ustar00rootroot00000000000000#include #include #include #include int main(void) { char path[PATH_MAX]; ssize_t status; status = readlink("/proc/self/exe", path, PATH_MAX - 1); if (status < 0 || status >= PATH_MAX) exit(EXIT_FAILURE); path[status] = '\0'; puts(path); exit(EXIT_SUCCESS); } proot-5.4.0/test/pwd.c000066400000000000000000000006131442763353300146120ustar00rootroot00000000000000#include /* syscall(2), */ #include /* perror(3), */ #include /* PATH_MAX, */ #include /* exit(3), */ #include /* SYS_getcwd, */ int main(void) { char path[PATH_MAX]; int status; status = syscall(SYS_getcwd, path, PATH_MAX); if (status < 0) { perror("getcwd()"); exit(EXIT_FAILURE); } puts(path); exit(EXIT_SUCCESS); } proot-5.4.0/test/readdir.c000066400000000000000000000017161442763353300154370ustar00rootroot00000000000000#include #include #include #include #include int main(int argc, char *argv[]) { struct dirent *dirents; DIR *dir; int status; int i; for (i = 1; i < argc; i++) { dir = opendir(argv[i]); if (dir == NULL) { perror("opendir(3)"); exit(EXIT_FAILURE); } errno = 0; while ((dirents = readdir(dir)) != NULL) { printf("%s %s\n", dirents->d_type == DT_BLK ? "DT_BLK " : dirents->d_type == DT_CHR ? "DT_CHR " : dirents->d_type == DT_DIR ? "DT_DIR " : dirents->d_type == DT_FIFO ? "DT_FIFO" : dirents->d_type == DT_LNK ? "DT_LNK " : dirents->d_type == DT_REG ? "DT_REG " : dirents->d_type == DT_SOCK ? "DT_SOCK" : "DT_UNKNOWN", dirents->d_name); errno = 0; } if (errno != 0) { perror("readdir(3)"); exit(EXIT_FAILURE); } status = closedir(dir); if (status < 0) { perror("closedir(3)"); exit(EXIT_FAILURE); } } exit(EXIT_SUCCESS); } proot-5.4.0/test/readlink.c000066400000000000000000000014131442763353300156100ustar00rootroot00000000000000#include /* syscall(2), */ #include /* perror(3), fprintf(3), */ #include /* PATH_MAX, */ #include /* exit(3), */ #include /* SYS_readlink, */ #include /* AT_FDCWD */ int main(int argc, char *argv[]) { char path[PATH_MAX]; int status; if (argc != 2) { fprintf(stderr, "usage: readlink FILE\n"); exit(EXIT_FAILURE); } #if defined(SYS_readlink) status = syscall(SYS_readlink, argv[1], path, PATH_MAX); #elif defined(SYS_readlinkat) status = syscall(SYS_readlinkat, AT_FDCWD, argv[1], path, PATH_MAX); #else #error "SYS_readlink and SYS_readlinkat doesn't exists" #endif if (status < 0) { perror("readlink()"); exit(EXIT_FAILURE); } path[status] = '\0'; puts(path); exit(EXIT_SUCCESS); } proot-5.4.0/test/sockets/000077500000000000000000000000001442763353300153275ustar00rootroot00000000000000proot-5.4.0/test/sockets/tcpsockets.py000066400000000000000000000031771442763353300200730ustar00rootroot00000000000000from __future__ import print_function import socket import sys import os import time HOST = 'localhost' PORT = 5432 pid = os.fork() # Server if pid: # Create syscall sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if len(sys.argv) > 1: time.sleep(int(sys.argv[1])) # Bind syscall print("Server bind") sock.bind((HOST, PORT)) # Listen syscall print("Server listen") sock.listen(1) try: # Accept syscall print("Server accept") conn, addr = sock.accept() while True: data = conn.recv(1024) if not data: break else: #if len(sys.argv) > 4: # with open("fakeoutput" + sys.argv[4] + ".txt", "a") as testfile: # testfile.write(data) # os.remove("fakeoutput" + sys.argv[4] + ".txt") print("Server data received : " + data.decode()) break finally: # Close syscall sock.close() # Client else: # Socket syscall sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if len(sys.argv) > 2: time.sleep(int(sys.argv[2])) try: # Connect syscall print("Client connect") sock.connect((HOST, PORT)) except socket.error as msg: print(msg, file=sys.stderr) sys.exit(1) if len(sys.argv) > 3: time.sleep(int(sys.argv[3])) try: # send Syscall if len(sys.argv) > 4: sock.send(("test " + sys.argv[4]).encode()) else: sock.send(b"test") finally: sock.close() proot-5.4.0/test/sockets/tcpsocketsipv6.py000066400000000000000000000032221442763353300206670ustar00rootroot00000000000000from __future__ import print_function import socket import sys import os import time HOST = '::1' PORT = 6432 pid = os.fork() addrs = socket.getaddrinfo(HOST, PORT, socket.AF_INET6, 0, socket.SOL_TCP)[0][-1] #addrs = (HOST, PORT) print(addrs) # Server if pid: # Create syscall sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) if len(sys.argv) > 1: time.sleep(int(sys.argv[1])) # Bind syscall print("Server bind") sock.bind(addrs) # Listen syscall print("Server listen") sock.listen(1) try: # Accept syscall print("Server accept") conn, addr = sock.accept() while True: data = conn.recv(1024) if not data: break else: #if len(sys.argv) > 4: # with open("fakeoutput" + sys.argv[4] + ".txt", "a") as testfile: # testfile.write(data) # os.remove("fakeoutput" + sys.argv[4] + ".txt") print("Server data received : " + data.decode()) break finally: # Close syscall sock.close() # Client else: # Socket syscall sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) if len(sys.argv) > 2: time.sleep(int(sys.argv[2])) try: # Connect syscall print("Client connect") sock.connect(addrs) except socket.error as msg: print(msg, file=sys.stderr) sys.exit(1) if len(sys.argv) > 3: time.sleep(int(sys.argv[3])) try: # send Syscall sock.send(("test " + sys.argv[4]).encode()) finally: sock.close() proot-5.4.0/test/sockets/testtcpsocket.sh000066400000000000000000000011771442763353300205700ustar00rootroot00000000000000#!/bin/sh # $1: instance id, # $2: waiting time before server binding # $3: waiting time before client connecting # $4: client waiting time before sending message start_ips_program() { ../../src/proot -v 1 -p $1 python tcpsockets.py $2 $3 $4 $1 } # Instance 1: bind connect send&close # Instance 2: bind connect send&close start_ips_program 5432:54320 1 3 1 & start_ips_program 5432:54321 2 3 1 #start_ips_program 10 0 1 0 # If PRoot allows these two processes to proceed without errors, the test passes. # Without the -p option, they cannot be run at the same time because they use the same port. proot-5.4.0/test/sockets/testtcpsocketauto.sh000066400000000000000000000011431442763353300214520ustar00rootroot00000000000000#!/bin/sh # $1: instance id, # $2: waiting time before server binding # $3: waiting time before client connecting # $4: client waiting time before sending message start_ips_program() { ../../src/proot -v 1 -n python tcpsockets.py $1 $2 $3 } # Instance 1: bind connect send&close # Instance 2: bind connect send&close start_ips_program 1 3 1 & start_ips_program 2 3 1 #start_ips_program 10 0 1 0 # If PRoot allows these two processes to proceed without errors, the test passes. # Without the -n option, they cannot be run at the same time because they use the same port. proot-5.4.0/test/sockets/testtcpsocketipv6.sh000066400000000000000000000012211442763353300213630ustar00rootroot00000000000000#!/bin/sh # $1: instance id, # $2: waiting time before server binding # $3: waiting time before client connecting # $4: client waiting time before sending message start_ips_program() { ../../src/proot -p $1 python tcpsocketsipv6.py $2 $3 $4 $1 } # Instance 1: bind connect send&close # Instance 2: bind connect send&close start_ips_program 6432:56320 1 3 1 & start_ips_program 6432:56321 2 3 1 #start_ips_program 10 0 1 0 # If PRoot allows these two processes to proceed without errors, the test passes. # Without the -p option, they cannot be run at the same time because their share the same unix socket filename. proot-5.4.0/test/symlink.c000066400000000000000000000012411442763353300155040ustar00rootroot00000000000000#include /* syscall(2), */ #include /* perror(3), fprintf(3), */ #include /* exit(3), */ #include /* SYS_symlink, */ #include /* AT_FDCWD */ int main(int argc, char *argv[]) { int status; if (argc != 3) { fprintf(stderr, "usage: symlink REFEREE REFERER\n"); exit(EXIT_FAILURE); } #if defined(SYS_symlink) status = syscall(SYS_symlink, argv[1], argv[2]); #elif defined(SYS_symlinkat) status = syscall(SYS_symlinkat, argv[1], AT_FDCWD, argv[2]); #else #error "SYS_symlink and SYS_symlinkat doesn't exists" #endif if (status < 0) { perror("symlink()"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-00000000.sh000066400000000000000000000001271442763353300160440ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/true ]; then exit 125; fi ${PROOT} -r ${ROOTFS} /bin/true proot-5.4.0/test/test-0228fbe7.sh000066400000000000000000000014431442763353300163260ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which env` ] || [ -z `which head` ] || [ -z `which cmp` ] || [ -z `which rm` ] || [ ! -x ${ROOTFS}/bin/ptrace-2 ] || [ ! -x ${ROOTFS}/bin/true ]; then exit 125; fi TMP1=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) ${ROOTFS}/bin/ptrace-2 ${ROOTFS}/bin/true 2>&1 >${TMP1} ${PROOT} ${ROOTFS}/bin/ptrace-2 ${ROOTFS}/bin/true 2>&1 >${TMP2} cmp ${TMP1} ${TMP2} env PTRACER_BEHAVIOR_1=1 ${ROOTFS}/bin/ptrace-2 ${ROOTFS}/bin/true 2>&1 >${TMP1} env PTRACER_BEHAVIOR_1=1 ${PROOT} ${ROOTFS}/bin/ptrace-2 ${ROOTFS}/bin/true 2>&1 >${TMP2} cmp ${TMP1} ${TMP2} env PTRACER_BEHAVIOR_2=1 ${ROOTFS}/bin/ptrace-2 ${ROOTFS}/bin/true 2>&1 >${TMP1} env PTRACER_BEHAVIOR_2=1 ${PROOT} ${ROOTFS}/bin/ptrace-2 ${ROOTFS}/bin/true 2>&1 >${TMP2} cmp ${TMP1} ${TMP2} rm -f ${TMP1} ${TMP2} proot-5.4.0/test/test-0238c7f1.sh000066400000000000000000000004311442763353300162400ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/pwd ] || [ -z `which grep` ]; then exit 125; fi ${PROOT} -m /:/hostfs -w /hostfs/etc -r ${ROOTFS} pwd | grep '^/hostfs/etc$' ${PROOT} -m /:/hostfs -w /hostfs -r ${ROOTFS} pwd | grep '^/hostfs$' ${PROOT} -m /:/hostfs -w / -r ${ROOTFS} pwd | grep '^/$' proot-5.4.0/test/test-03969e70.sh000066400000000000000000000017301442763353300161740ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/true ] || [ -z `which env` ]; then exit 125; fi ! ${PROOT} -r ${ROOTFS} /true [ $? -eq 0 ] ! ${PROOT} -r ${ROOTFS} ./true [ $? -eq 0 ] ! env PATH='' ${PROOT} -r ${ROOTFS} true [ $? -eq 0 ] ! env PATH='' ${PROOT} -r ${ROOTFS} -w /bin true [ $? -eq 0 ] env PATH='' ${PROOT} -r ${ROOTFS} -w /bin ./true env PATH='' ${PROOT} -r ${ROOTFS} -w / bin/true env PATH='' ${PROOT} -r ${ROOTFS} -w / bin/./true env PATH='' ${PROOT} -r ${ROOTFS} -w / ../bin/true ! env PATH='' ${PROOT} -r ${ROOTFS} -w /bin/true ../true [ $? -eq 0 ] ! env --unset PATH ${PROOT} -r ${ROOTFS} true [ $? -eq 0 ] ! env --unset PATH ${PROOT} -r ${ROOTFS} -w /bin true [ $? -eq 0 ] env --unset PATH ${PROOT} -r ${ROOTFS} -w /bin ./true env --unset PATH ${PROOT} -r ${ROOTFS} -w / /bin/true env --unset PATH ${PROOT} -r ${ROOTFS} -w / /bin/./true env --unset PATH ${PROOT} -r ${ROOTFS} -w / ../bin/true ! env --unset PATH ${PROOT} -r ${ROOTFS} -w /bin/true ../true [ $? -eq 0 ] proot-5.4.0/test/test-071599da.sh000066400000000000000000000011431442763353300162470ustar00rootroot00000000000000if [ -z `which uname` ] || [ -z `which true` ] || [ -z `which env` ] || [ -z `which grep` ] || [ -z `which tail` ] || [ ! -x ${ROOTFS}/bin/true ]; then exit 125; fi ${PROOT} -k $(uname -r) true env PROOT_FORCE_KOMPAT=1 ${PROOT} -k $(uname -r) true ${PROOT} -k $(uname -r) ${ROOTFS}/bin/true env PROOT_FORCE_KOMPAT=1 ${PROOT} -k $(uname -r) ${ROOTFS}/bin/true if env LD_SHOW_AUXV=1 true | grep -q ^AT_RANDOM; then env PROOT_FORCE_KOMPAT=1 ${PROOT} -k $(uname -r) env LD_SHOW_AUXV=1 true | tail -1 | grep ^AT_RANDOM fi ! ${PROOT} -k $(uname -r) env LD_SHOW_AUXV=1 true | grep AT_SYSINFO [ $? -eq 0 ] proot-5.4.0/test/test-0830d8a8.sh000066400000000000000000000002171442763353300162440ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/exec-m32 ] || [ ! -x ${ROOTFS}/bin/true ]; then exit 125; fi ${PROOT} ${ROOTFS}/bin/exec-m32 ${ROOTFS}/bin/true proot-5.4.0/test/test-092c5e26.sh000066400000000000000000000033211442763353300162430ustar00rootroot00000000000000#!/bin/sh set -eu if [ ! -x "${ROOTFS}/bin/echo" ] || \ [ ! -x "${ROOTFS}/bin/argv0" ] || \ [ -z "$(command -v mcookie)" ] || \ [ -z "$(command -v grep)" ] || \ [ -z "$(command -v cat)" ] || \ [ -z "$(command -v rm)" ]; then exit 125; fi TMP="$(mcookie)" TMP_ABS="/tmp/${TMP}" "${PROOT}" -r "${ROOTFS}" argv0 | grep '^argv0$' "${PROOT}" -r "${ROOTFS}" /bin/argv0 | grep '^/bin/argv0$' cat > "${ROOTFS}/${TMP_ABS}" < "${ROOTFS}/${TMP_ABS}" </dev/null); cd_result=$? cat ${x} 2>/dev/null; cat_result=$? readlink ${x} 2>/dev/null 1>&2; readlink_result=$? echo "${x}, $cd_result, $cat_result, $readlink_result" >> $output done } echo "path, chdir, cat, readlink" > $output for a in ${x1}; do for b in ${x2}; do make_tests "${a}/${b}" done for b in ${x3}; do make_tests "${a}/${b}" done done } if [ -z ${PROOT_STAGE2} ]; then create_components () { touch r${1} 2>/dev/null mkdir -p d${1} 2>/dev/null ln -fs r${1} rl${1} 2>/dev/null ln -fs d${1} dl${1} 2>/dev/null } create_components 1 $(cd d1; create_components 2) REF=/tmp/`mcookie` mkdir -p /tmp generate $REF env PROOT_STAGE2=$REF ${PROOT} -w ${PWD} sh ./$0 exit $? fi TMP=/tmp/`mcookie` mkdir -p /tmp generate $TMP set -e cmp $TMP $PROOT_STAGE2 rm $TMP $PROOT_STAGE2 proot-5.4.0/test/test-16573e73.c000066400000000000000000000010111442763353300157720ustar00rootroot00000000000000#include #include #include #include int main(int argc, char **ignored) { char *const argv[] = { "true", NULL}; char *const envp[] = { NULL }; pid_t pid; int status; pid = (argc <= 1 ? vfork() : fork()); switch (pid) { case -1: exit(EXIT_FAILURE); case 0: /* child */ exit(execve("/bin/true", argv, envp)); default: /* parent */ if (wait(&status) < 0 || !WIFEXITED(status)) exit(EXIT_FAILURE); exit(WEXITSTATUS(status)); } exit(EXIT_FAILURE); } proot-5.4.0/test/test-1743dd3d.sh000066400000000000000000000006121442763353300163210ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/true ] || [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which echo` ] || [ -z `which chmod` ]; then exit 125; fi TMP=/tmp/`mcookie` rm -f ${ROOTFS}/${TMP} mkdir -p ${ROOTFS}/tmp echo '#!/bin/true' > ${ROOTFS}/${TMP} chmod -x ${ROOTFS}/${TMP} ! ${PROOT} -r ${ROOTFS} ${TMP} chmod +x ${ROOTFS}/${TMP} ${PROOT} -r ${ROOTFS} ${TMP} rm -f ${ROOTFS}/${TMP} proot-5.4.0/test/test-1c68c218.c000066400000000000000000000007041442763353300160550ustar00rootroot00000000000000#include #include #include #include int main() { int status; char *path; path = tmpnam(NULL); status = symlink(path, path); if (status < 0) exit(EXIT_FAILURE); status = fchownat(AT_FDCWD, path, getuid(), getgid(), 0); if (status >= 0) exit(EXIT_FAILURE); status = fchownat(AT_FDCWD, path, getuid(), getgid(), AT_SYMLINK_NOFOLLOW); if (status < 0) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } proot-5.4.0/test/test-1cd9d8f9.sh000066400000000000000000000001541442763353300164200ustar00rootroot00000000000000if ! `which pwd` -P || [ -z `which grep` ] ; then exit 125; fi ${PROOT} -w /tmp pwd -P | grep '^/tmp$' proot-5.4.0/test/test-1fedd9a3.sh000066400000000000000000000021721442763353300164670ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which id` ] || [ -z `which mkdir` ] || [ -z `which touch` ] || [ -z `which chmod` ] || [ -z `which stat` ] || [ -z `which grep` ]; then exit 125; fi if [ `id -u` -eq 0 ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir -p ${TMP}/foo chmod a-rwx ${TMP}/foo chmod a-rwx ${TMP} ! ${PROOT} touch ${TMP}/foo/bar [ $? -eq 0 ] ! ${PROOT} -i 123:456 touch ${TMP}/foo/bar [ $? -eq 0 ] ${PROOT} -0 touch ${TMP}/foo/bar stat -c %a ${TMP} | grep '^0$' ! stat -c %a ${TMP}/foo [ $? -eq 0 ] ! ${PROOT} -i 123:456 stat -c %a ${TMP}/foo | grep '^0$' [ $? -eq 0 ] ${PROOT} -0 stat -c %a ${TMP}/foo | grep '^0$' chmod -R a+rwx ${TMP} chmod a-rwx ${TMP}/foo/bar chmod a-rwx ${TMP}/foo chmod a-rwx ${TMP} ! ${PROOT} chmod g+w ${TMP}/foo/bar [ $? -eq 0 ] ! ${PROOT} -i 123:456 chmod g+w ${TMP}/foo/bar [ $? -eq 0 ] ${PROOT} -0 chmod g+w ${TMP}/foo/bar chmod u+wx ${TMP} chmod u+x ${TMP}/foo stat -c %a ${TMP}/foo/bar | grep '^20$' chmod -R +rwx ${TMP} rm -fr ${TMP} mkdir -p ${TMP}/foo chmod -rwx ${TMP} ! rm -fr ${TMP} [ $? -eq 0 ] ! ${PROOT} -i 123:456 rm -fr ${TMP} [ $? -eq 0 ] ${PROOT} -0 rm -fr ${TMP} proot-5.4.0/test/test-1ffc8309.c000066400000000000000000000007151442763353300161430ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include #include #include #include int main() { int fds[2]; int status; uint8_t buffer; status = pipe2(fds, O_NONBLOCK); if (status < 0) { perror("pipe2"); exit(EXIT_FAILURE); } (void) alarm(5); (void) read(fds[0], &buffer, 1); if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("read"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-1ffc8309.sh000066400000000000000000000003561442763353300163340ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which env` ] || [ -z `which uname` ] || [ -z `which rm` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir ${TMP} env PROOT_FORCE_KOMPAT=1 ${PROOT} -k $(uname -r) rm -r ${TMP} proot-5.4.0/test/test-22222222.sh000066400000000000000000000012221442763353300160610ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/readlink ] || [ -z `which mcookie` ] || [ -z `which touch` ] || [ -z `which mkdir` ] || [ -z `which ln` ] || [ -z `which grep` ] || [ -z `which rm` ]; then exit 125; fi REGULAR=`mcookie` SYMLINK=`mcookie` touch /tmp/${REGULAR} ln -fs /tmp/${REGULAR} /tmp/${SYMLINK} mkdir -p ${ROOTFS}/tmp touch ${ROOTFS}/tmp/${REGULAR} ln -fs /tmp/${REGULAR} ${ROOTFS}/tmp/${SYMLINK} ${PROOT} -b /tmp:/ced -r ${ROOTFS} /bin/readlink /tmp/${SYMLINK} | grep ^/tmp/${REGULAR}$ ${PROOT} -b /tmp:/ced -r ${ROOTFS} /bin/readlink /ced/${SYMLINK} | grep ^/ced/${REGULAR}$ rm -f /tmp/${REGULAR} rm -f /tmp/${SYMLINK} rm -f ${ROOTFS}/tmp/${REGULAR} proot-5.4.0/test/test-230f47ch.sh000066400000000000000000000017521442763353300163320ustar00rootroot00000000000000if [ -z `which id` ] || [ -z `which uname` ] || [ -z `which grep` ]; then exit 125; fi ! ${PROOT} ${PROOT_RAW} /bin/true if [ $? -eq 0 ]; then exit 125; fi kver=$(uname -r) ${PROOT} ${PROOT_RAW} -0 id -u | grep ^0$ ${PROOT} ${PROOT_RAW} -i 123:456 id -u | grep ^123$ ${PROOT} ${PROOT_RAW} -k $kver-3.33.333 uname -r | grep ^.*-3\.33\.333$ ${PROOT} -0 ${PROOT_RAW} id -u | grep ^0$ ${PROOT} -i 123:456 ${PROOT_RAW} id -u | grep ^123$ ${PROOT} -k $kver-3.33.333 ${PROOT_RAW} uname -r | grep ^.*-3\.33\.333$ ${PROOT} -0 ${PROOT_RAW} -k $kver-3.33.333 id -u | grep ^0$ ${PROOT} -0 ${PROOT_RAW} -k $kver-3.33.333 uname -r | grep ^.*-3\.33\.333$ ${PROOT} -k $kver-3.33.333 ${PROOT_RAW} -0 id -u | grep ^0$ ${PROOT} -k $kver-3.33.333 ${PROOT_RAW} -0 uname -r | grep ^.*-3\.33\.333$ ${PROOT} -i 123:456 ${PROOT_RAW} -k $kver-3.33.333 id -u | grep ^123$ ${PROOT} -k $kver-3.33.333 ${PROOT_RAW} -i 123:456 id -u | grep ^123$ proot-5.4.0/test/test-2401b850.sh000066400000000000000000000053361442763353300161610ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which echo` ] || [ -z `which rm` ] || [ -z `which touch` ] || [ -z `which chmod` ] || [ -z `which grep` ]; then exit 125; fi TMP=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) rm -f ${TMP} touch ${TMP} chmod +x ${TMP} # Valgrind prepends "/bin/sh" in front of foreign binaries and uses # LD_PRELOAD. if $(echo ${PROOT} | grep -q valgrind); then ENV=$(which env) PROOT="env PROOT_FORCE_FOREIGN_BINARY=1 ${PROOT}" COMMAND1="-E LD_PRELOAD=.* -0 /bin/sh /bin/sh ${TMP}" TEST1="-- -U LD_LIBRARY_PATH -E LD_PRELOAD=.* -0 env ${ENV} LD_LIBRARY_PATH=test1 ${TMP}" TEST2="-- -E LD_PRELOAD=.* -E LD_LIBRARY_PATH=test2 -0 /bin/sh /bin/sh ${TMP}" TEST3="-- -E LD_PRELOAD=.* -E LD_LIBRARY_PATH=test2 -0 env ${ENV} LD_LIBRARY_PATH=test1 ${TMP}" TEST4="-- -U LD_LIBRARY_PATH -E LD_PRELOAD=.* -0 env ${ENV} LD_TRACE_LOADED_OBJECTS=1 ${TMP}" TEST5="-- -E LD_PRELOAD= -E LD_LIBRARY_PATH=test5 -0 sh /bin/sh -c ${TMP}" TEST52="-- -E LD_PRELOAD= -E LD_LIBRARY_PATH=test5 -0 sh /bin/sh -c sh -c ${TMP}" TEST6="-- -E LD_PRELOAD= -E LD_LIBRARY_PATH=test5 -0 env ${ENV} LD_LIBRARY_PATH=test6 ${TMP}" COMMAND2="-E LD_PRELOAD=.* -0 ${TMP} ${TMP} ${TMP2}" else COMMAND1="-0 ${TMP} ${TMP}" TEST1="-- -E LD_LIBRARY_PATH=test1 ${COMMAND1}" TEST2="-- -E LD_LIBRARY_PATH=test2 ${COMMAND1}" TEST3="${TEST1}" TEST4="-- -E LD_TRACE_LOADED_OBJECTS=1 -E LD_LIBRARY_PATH=.+ ${COMMAND1}" TEST5="-- -E LD_LIBRARY_PATH=test5 ${COMMAND1}" TEST52=${TEST5} TEST6="-- -E LD_LIBRARY_PATH=test6 ${COMMAND1}" COMMAND2="-0 ${TMP} ${TMP} ${TMP2}" fi ${PROOT} -q true ${TMP} ! ${PROOT} -q false ${TMP} [ $? -eq 0 ] (cd /; ${PROOT} -q ./$(which true) ${TMP}) ! (cd /; ${PROOT} -q ./$(which false) ${TMP}) [ $? -eq 0 ] HOST_LD_LIBRARY_PATH=$(${PROOT} -q 'echo --' env | grep LD_LIBRARY_PATH) test ! -z "${HOST_LD_LIBRARY_PATH}" unset LD_LIBRARY_PATH ${PROOT} -q 'echo --' ${TMP} | grep -- "^-- -U LD_LIBRARY_PATH ${COMMAND1}$" ${PROOT} -q 'echo --' env LD_LIBRARY_PATH=test1 ${TMP} | grep -- "^${TEST1}$" env LD_LIBRARY_PATH=test2 ${PROOT} -q 'echo --' ${TMP} | grep -- "^${TEST2}$" env LD_LIBRARY_PATH=test2 ${PROOT} -q 'echo --' env LD_LIBRARY_PATH=test1 ${TMP} | grep -- "^${TEST3}$" ${PROOT} -q 'echo --' env LD_TRACE_LOADED_OBJECTS=1 ${TMP} | grep -E -- "^${TEST4}$" env LD_LIBRARY_PATH=test5 ${PROOT} -q 'echo --' sh -c ${TMP} | grep -- "^${TEST5}$" env LD_LIBRARY_PATH=test5 ${PROOT} -q 'echo --' sh -c "sh -c ${TMP}" | grep -- "^${TEST52}$" env LD_LIBRARY_PATH=test5 ${PROOT} -q 'echo --' env LD_LIBRARY_PATH=test6 ${TMP} | grep -- "^${TEST6}$" rm -f ${TMP2} echo "#!${TMP}" > ${TMP2} chmod +x ${TMP2} ${PROOT} -q 'echo --' ${TMP2} | grep -- "^-- -U LD_LIBRARY_PATH ${COMMAND2}$" rm -fr ${TMP} ${TMP2} proot-5.4.0/test/test-25069c12.c000066400000000000000000000005431442763353300157720ustar00rootroot00000000000000#include /* execv(2), */ #include /* exit(3), getenv(3), setenv(3), */ #include /* strcmp(3), */ int main(int argc, char *argv[]) { char *void_array[] = { NULL }; if (getenv("PROC_SELF_EXE") != NULL) exit(EXIT_SUCCESS); setenv("PROC_SELF_EXE", "1", 1); execv("/proc/self/exe", void_array); exit(EXIT_FAILURE); } proot-5.4.0/test/test-25069c13.c000066400000000000000000000004741442763353300157760ustar00rootroot00000000000000#include /* execv(2), */ #include /* exit(3), getenv(3), setenv(3), */ #include /* strcmp(3), */ int main(int argc, char *argv[]) { if (getenv("PROC_SELF_EXE") != NULL) exit(EXIT_SUCCESS); setenv("PROC_SELF_EXE", "1", 1); execv("/proc/self/exe", NULL); exit(EXIT_FAILURE); } proot-5.4.0/test/test-2db65cd2.sh000066400000000000000000000010171442763353300163770ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/true ] || [ -z `which gdb` ]; then exit 125; fi TMP1=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) TMP3=/tmp/$(mcookie) TMP4=/tmp/$(mcookie) TMP5=/tmp/$(mcookie) cat > ${TMP5} < ${TMP1} ! grep -v 'process' ${TMP1} > ${TMP2} ${PROOT} ${COMMAND} > ${TMP4} ! grep -v 'process' ${TMP4} > ${TMP3} ! grep -v '^proot warning: ' ${TMP3} > ${TMP4} cmp ${TMP2} ${TMP4} rm -f ${TMP1} ${TMP2} ${TMP3} ${TMP4} ${TMP5} proot-5.4.0/test/test-305ae31d.c000066400000000000000000000025111442763353300161210ustar00rootroot00000000000000#include #include #include #include #include #include #include int main() { int fd; int fd_dir; int fd_file; char path[64]; /* 64 > sizeof("/proc//fd/") + 2 * sizeof(#ULONG_MAX) */ int status; fd_dir = open("/bin", O_RDONLY); if (fd_dir < 0) exit(EXIT_FAILURE); fd_file = open("/bin/true", O_RDONLY); if (fd_file < 0) exit(EXIT_FAILURE); status = snprintf(path, sizeof(path), "/proc/%d/fd/%d/", getpid(), fd_dir); if (status < 0 || status >= sizeof(path)) exit(EXIT_FAILURE); fd = open(path, O_RDONLY); if (fd < 0) exit(EXIT_FAILURE); close(fd); status = snprintf(path, sizeof(path), "/proc/%d/fd/%d/..", getpid(), fd_dir); if (status < 0 || status >= sizeof(path)) exit(EXIT_FAILURE); fd = open(path, O_RDONLY); if (fd < 0) exit(EXIT_FAILURE); close(fd); status = snprintf(path, sizeof(path), "/proc/%d/fd/%d/..", getpid(), fd_file); if (status < 0 || status >= sizeof(path)) exit(EXIT_FAILURE); fd = open(path, O_RDONLY); if (fd >= 0 || errno != ENOTDIR) exit(EXIT_FAILURE); status = snprintf(path, sizeof(path), "/proc/%d/fd/999999/..", getpid()); if (status < 0 || status >= sizeof(path)) exit(EXIT_FAILURE); fd = open(path, O_RDONLY); if (fd >= 0 || errno != ENOENT) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } proot-5.4.0/test/test-305ae31d.sh000066400000000000000000000003071442763353300163120ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which ln` ] || [ -z `which true` ] || [ -z `which rm` ]; then exit 125; fi TMP=$(mcookie) ln -s /proc/self/mounts ${TMP} ${PROOT} -b ${TMP} true rm ${TMP} proot-5.4.0/test/test-311b7a95.sh000066400000000000000000000005561442763353300162470ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which rm` ] || [ -z `which chmod` ] || [ -z `which echo` ]; then exit 125; fi TMP1=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) echo "#! ${TMP2} -a" > ${TMP1} echo "#! $(which echo) -b" > ${TMP2} chmod +x ${TMP1} ${TMP2} RESULT=$(${PROOT} ${TMP1}) EXPECTED=$(${TMP1}) test "${RESULT}" = "${EXPECTED}" rm -f ${TMP1} ${TMP2} proot-5.4.0/test/test-33333333.c000066400000000000000000000006541442763353300157110ustar00rootroot00000000000000/* Check a child is traced even if its parent doesn't call wait(2). * * Reported-by: Clément BAZIN * on Ubuntu 11.10 x86_64 */ #include /* exit(3), */ #include /* fork(2), */ int main(void) { switch (fork()) { case -1: exit(EXIT_FAILURE); case 0: /* Child: XXX */ sleep(2); return 0; default: /* Parent: "look child, no wait(2)!" */ return 1; } } proot-5.4.0/test/test-33333334.c000066400000000000000000000010451442763353300157050ustar00rootroot00000000000000#include /* perror(3) */ #include /* exit(3), */ #include /* wait(3) */ #include /* fork(2), */ int main(void) { int child_status; int status; switch (fork()) { case -1: exit(EXIT_FAILURE); case 0: /* child */ return 13; default: /* parent */ status = wait(&child_status); if (status < 0) { perror("wait()"); exit(EXIT_FAILURE); } if (!WIFEXITED(child_status)) exit(EXIT_FAILURE); if (WEXITSTATUS(child_status) != 13) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } } proot-5.4.0/test/test-3624be91.sh000066400000000000000000000002771442763353300162520ustar00rootroot00000000000000if [ -z `which sh` ] || [ -z `which kill` ] || [ -z `which grep` ] || [ -z `which cut` ]; then exit 125; fi ${PROOT} sh -c 'kill -15 $(grep TracerPid /proc/self/status | cut -f 2 -d :)' proot-5.4.0/test/test-3dec4597.sh000066400000000000000000000001711442763353300163330ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/pwd ]; then exit 125; fi ${PROOT} -m /tmp:/longer-tmp -w /longer-tmp -r ${ROOTFS} /bin/pwd proot-5.4.0/test/test-44444444.c000066400000000000000000000005311442763353300157130ustar00rootroot00000000000000#include #include #include #include int main(void) { char buffer[2 * PATH_MAX]; if (!getcwd(buffer, sizeof(buffer))) { perror("getcwd"); exit(EXIT_FAILURE); } if (readlink("/bin/abs-true", buffer, sizeof(buffer)) < 0) { perror("readlink"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-517e1d6a.sh000066400000000000000000000024111442763353300163200ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/argv ] || [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which chmod` ] || [ -z `which env` ] || [ -z `which rm` ] || [ -z `which grep` ] || [ -z `which env` ] || [ -z `which ln` ]; then exit 125; fi BIN_DIR=/tmp/$(mcookie) TMP=$(mcookie) TMP2=$(mcookie) mkdir ${BIN_DIR} echo "#! ${ROOTFS}/bin/argv -x" > ${BIN_DIR}/${TMP} chmod +x ${BIN_DIR}/${TMP} ln -s ${BIN_DIR}/${TMP} ${BIN_DIR}/${TMP2} ${PROOT} env ${BIN_DIR}/${TMP} | grep "${ROOTFS}/bin/argv -x ${BIN_DIR}/${TMP}" ${PROOT} env PATH=${BIN_DIR} ${TMP} | grep "${ROOTFS}/bin/argv -x ${BIN_DIR}/${TMP}" (cd ${BIN_DIR}; ${PROOT} env ./${TMP}) | grep "${ROOTFS}/bin/argv -x ./${TMP}" ${PROOT} env ${BIN_DIR}/${TMP2} | grep "${ROOTFS}/bin/argv -x ${BIN_DIR}/${TMP2}" ${PROOT} env PATH=${BIN_DIR} ${TMP2} | grep "${ROOTFS}/bin/argv -x ${BIN_DIR}/${TMP2}" (cd ${BIN_DIR}; ${PROOT} env ./${TMP2}) | grep "${ROOTFS}/bin/argv -x ./${TMP2}" ${PROOT} ${BIN_DIR}/${TMP} | grep "${ROOTFS}/bin/argv -x ${BIN_DIR}/${TMP}" env PATH=${BIN_DIR} ${PROOT} ${TMP} | grep "${ROOTFS}/bin/argv -x ${BIN_DIR}/${TMP}" # TODO: (cd ${BIN_DIR}; ${PROOT} ./${TMP}) | grep "${ROOTFS}/bin/argv -x ./${TMP}" (cd ${BIN_DIR}; ${PROOT} sh -c "true; ./${TMP}") | grep "${ROOTFS}/bin/argv -x ./${TMP}" rm -fr ${BIN_DIR} proot-5.4.0/test/test-517e1d6b.sh000066400000000000000000000004541442763353300163260ustar00rootroot00000000000000if [ -z `which true` ] || [ -z `which realpath` ] || [ -z `which grep` ] || [ -z `which env` ] || [ ! -x ${ROOTFS}/bin/puts_proc_self_exe ]; then exit 125; fi TRUE=$(realpath $(which true)) env PROOT_FORCE_FOREIGN_BINARY=1 ${PROOT} -q ${ROOTFS}/bin/puts_proc_self_exe ${TRUE} | grep ^${TRUE}$ proot-5.4.0/test/test-51943658.c000066400000000000000000000024461442763353300157330ustar00rootroot00000000000000#include /* syscall(2), */ #include /* perror(3), fprintf(3), */ #include /* PATH_MAX, */ #include /* exit(3), */ #include /* strcmp */ #include /* openat(2), */ int main(void) { int dir_fd; int dir_fd1; int dir_fd2; ssize_t status; char path1[PATH_MAX]; char path2[PATH_MAX]; char fd_link[64]; /* Format the path to the "virtual" link. */ dir_fd = open("/", O_RDONLY); if (dir_fd < 0) { perror("open(2)"); exit(EXIT_FAILURE); } dir_fd1 = openat(dir_fd, ".", O_RDONLY); if (dir_fd1 < 0) { perror("openat(2)"); exit(EXIT_FAILURE); } dir_fd2 = openat(dir_fd, "..", O_RDONLY); if (dir_fd2 < 0) { perror("openat(2)"); exit(EXIT_FAILURE); } sprintf(fd_link, "/proc/self/fd/%d", dir_fd1); status = readlink(fd_link, path1, PATH_MAX - 1); if (status < 0) { perror("readlink(2)"); exit(EXIT_FAILURE); } path1[status] = '\0'; sprintf(fd_link, "/proc/self/fd/%d", dir_fd2); status = readlink(fd_link, path2, PATH_MAX - 1); if (status < 0) { perror("readlink(2)"); exit(EXIT_FAILURE); } path2[status] = '\0'; if (strcmp(path1, "/") != 0) { fprintf(stderr, "/. != /"); exit(EXIT_FAILURE); } if (strcmp(path2, "/") != 0) { fprintf(stderr, "/.. != /"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-53355a5b.sh000066400000000000000000000003621442763353300162420ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which chmod` ] || [ -z `which rm` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir ${TMP} chmod a-x ${TMP} ! ${PROOT} sh -c "cd $TMP" [ $? -eq 0 ] chmod a+x ${TMP} rm -fr ${TMP} proot-5.4.0/test/test-5467b986.sh000066400000000000000000000035301442763353300162040ustar00rootroot00000000000000if [ -z $(which mcookie) ] || [ -z $(which grep) ] || [ ! -x ${ROOTFS}/bin/readlink ] || [ ! -x ${ROOTFS}/bin/chdir_getcwd ] || [ ! -x ${ROOTFS}/bin/fchdir_getcwd ]; then exit 125; fi DOES_NOT_EXIST=/$(mcookie) ${PROOT} -v -1 -b /proc -w ${DOES_NOT_EXIST} -r ${ROOTFS} readlink /proc/self/cwd | grep '^/$' ${PROOT} -v -1 -w /a -b /tmp:/a -b /tmp:/b -r ${ROOTFS} pwd | grep '^/a$' ${PROOT} -v -1 -w /a -b /tmp:/b -b /tmp:/a -r ${ROOTFS} pwd | grep '^/a$' ${PROOT} -v -1 -w /b -b /tmp:/a -b /tmp:/b -r ${ROOTFS} pwd | grep '^/b$' ${PROOT} -v -1 -w /b -b /tmp:/b -b /tmp:/a -r ${ROOTFS} pwd | grep '^/b$' ${PROOT} -v -1 -b /tmp:/a -b /tmp:/b -r ${ROOTFS} chdir_getcwd /a | grep '^/[ab]$' ${PROOT} -v -1 -b /tmp:/b -b /tmp:/a -r ${ROOTFS} chdir_getcwd /a | grep '^/[ab]$' ${PROOT} -v -1 -b /tmp:/a -b /tmp:/b -r ${ROOTFS} chdir_getcwd /b | grep '^/[ab]$' ${PROOT} -v -1 -b /tmp:/b -b /tmp:/a -r ${ROOTFS} chdir_getcwd /b | grep '^/[ab]$' ${PROOT} -v -1 -b /tmp:/a -b /tmp:/b -r ${ROOTFS} fchdir_getcwd /a | grep '^/[ab]$' ${PROOT} -v -1 -b /tmp:/b -b /tmp:/a -r ${ROOTFS} fchdir_getcwd /a | grep '^/[ab]$' ${PROOT} -v -1 -b /tmp:/a -b /tmp:/b -r ${ROOTFS} fchdir_getcwd /b | grep '^/[ab]$' ${PROOT} -v -1 -b /tmp:/b -b /tmp:/a -r ${ROOTFS} fchdir_getcwd /b | grep '^/[ab]$' ! ${PROOT} -r ${ROOTFS} chdir_getcwd /bin/true [ $? -eq 0 ] ! ${PROOT} -r ${ROOTFS} fchdir_getcwd /bin/true [ $? -eq 0 ] ! ${PROOT} -w /bin -r ${ROOTFS} chdir_getcwd true [ $? -eq 0 ] ! ${PROOT} -w /bin -r ${ROOTFS} fchdir_getcwd true [ $? -eq 0 ] ${PROOT} -v -1 -w /usr -r / ${ROOTFS}/bin/chdir_getcwd share | grep '^/usr/share$' ${PROOT} -v -1 -w /usr -r / ${ROOTFS}/bin/fchdir_getcwd share | grep '^/usr/share$' (cd /; ${PROOT} -v -1 -w usr -r / ${ROOTFS}/bin/chdir_getcwd share | grep '^/usr/share$') (cd /; ${PROOT} -v -1 -w usr -r / ${ROOTFS}/bin/fchdir_getcwd share | grep '^/usr/share$') proot-5.4.0/test/test-55b731d3.sh000066400000000000000000000000741442763353300162430ustar00rootroot00000000000000if ! `which pwd` -P; then exit 125; fi ${PROOT} pwd -P proot-5.4.0/test/test-55fd1da5.sh000066400000000000000000000001131442763353300163760ustar00rootroot00000000000000if [ -z `which ls` ]; then exit 125; fi ${PROOT} -b /etc:/x ls -la /x proot-5.4.0/test/test-5996858d.sh000066400000000000000000000015101442763353300162070ustar00rootroot00000000000000if [ -z `which uname` ] || [ -z `which grep` ] || [ -z `which domainname` ] || [ -z `which hostname` ]|| [ -z `which env` ] || [ -z `which true`]; then exit 125; fi UTSNAME="\\sysname\\nodename\\$(uname -r)\\version\\machine\\domainname\\0\\" ${PROOT} -k ${UTSNAME} uname -s | grep ^sysname$ ${PROOT} -k ${UTSNAME} uname -n | grep ^nodename$ ${PROOT} -k ${UTSNAME} uname -v | grep ^version$ ${PROOT} -k ${UTSNAME} uname -m | grep ^machine$ ${PROOT} -k ${UTSNAME} domainname | grep ^domainname$ ${PROOT} -k ${UTSNAME} env LD_SHOW_AUXV=1 true | grep -E '^AT_HWCAP:[[:space:]]*0?$' ${PROOT} -0 -k ${UTSNAME} sh -c 'domainname domainname2; domainname' | grep ^domainname2$ ${PROOT} -0 -k ${UTSNAME} sh -c 'hostname hostname2; hostname' | grep ^hostname2$ ${PROOT} -0 -k ${UTSNAME} sh -c 'hostname hostname2; uname -n' | grep ^hostname2$ proot-5.4.0/test/test-5bed7141.c000066400000000000000000000034321442763353300161330ustar00rootroot00000000000000#include #include #include #include #include #include #include static void *routine(void *path) { int status; status = chdir(path); if (status < 0) { perror("chdir"); pthread_exit((void *)-1); } pthread_exit(NULL); } static void pterror(const char *message, int error) { fprintf(stderr, "%s: %s\n", message, strerror(error)); } void check_cwd(const char *expected) { char path[PATH_MAX]; int status; if (getcwd(path, PATH_MAX) == NULL) { perror("getcwd"); exit(EXIT_FAILURE); } if (strcmp(path, expected) != 0) { fprintf(stderr, "getcwd: %s != %s\n", path, expected); exit(EXIT_FAILURE); } status = readlink("/proc/self/cwd", path, PATH_MAX - 1); if (status < 0) { perror("readlink"); exit(EXIT_FAILURE); } path[status] = '\0'; if (strcmp(path, expected) != 0) { fprintf(stderr, "readlink /proc/self/cwd: %s != %s\n", path, expected); exit(EXIT_FAILURE); } } int main(int argc, char *argv[]) { pthread_t thread; int child_status; void *result; int status; status = pthread_create(&thread, NULL, routine, "/etc"); if (status != 0) { pterror("pthread_create", status); exit(EXIT_FAILURE); } status = pthread_join(thread, &result); if (status != 0) { pterror("pthread_create", status); exit(EXIT_FAILURE); } if (result != NULL) exit(EXIT_FAILURE); check_cwd("/etc"); switch (fork()) { case -1: perror("readlink"); exit(EXIT_FAILURE); case 0: /* child */ status = chdir("/etc"); if (status < 0) { perror("chdir"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); default: status = wait(&child_status); if (status < 0 || child_status != 0) { perror("wait()"); exit(EXIT_FAILURE); } check_cwd("/etc"); break; } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-5bed7142.sh000066400000000000000000000005171442763353300163250ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/pwd ] || [ -z `which mkdir` ] || [ -z `which grep` ] || [ -z `which mcookie` ] || [ -z `which pwd` ]; then exit 125; fi mkdir -p ${ROOTFS}/${PWD} ${PROOT} -v 1 -w . -r ${ROOTFS} pwd | grep ^${PWD}$ TMP=/tmp/$(mcookie) mkdir ${TMP} ! ${PROOT} sh -c "cd ${TMP}; rmdir ${TMP}; $(which pwd) -P" [ $? -eq 0 ] proot-5.4.0/test/test-5bed7143.c000066400000000000000000000025501442763353300161350ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #define TEMPLATE "/tmp/proot-test-5bed7143-XXXXXX" #define COOKIE1 "2fde3df3558fa30bec1b8ebad42df20f" #define COOKIE2 "2ba90289e48d1896e0601239ac25f764" int main() { char *path1; char *path2; char *cwd; int status; path1 = mkdtemp(strdup(TEMPLATE)); if (path1 == NULL) exit(EXIT_FAILURE); status = chdir(path1); if (status < 0) exit(EXIT_FAILURE); status = mkdir(COOKIE1, 0777); if (status < 0) exit(EXIT_FAILURE); status = chdir(COOKIE1); if (status < 0) exit(EXIT_FAILURE); status = creat(COOKIE2, O_RDWR); if (status < 0) exit(EXIT_FAILURE); close(status); path2 = mktemp(strdup(TEMPLATE)); status = rename(path1, path2); if (status < 0) exit(EXIT_FAILURE); status = access(COOKIE2, F_OK); if (status < 0) exit(EXIT_FAILURE); cwd = get_current_dir_name(); if (cwd == NULL || memcmp(cwd, path2, strlen(path2)) != 0) exit(EXIT_FAILURE); status = unlink(COOKIE2); if (status < 0) exit(EXIT_FAILURE); status = rmdir(cwd); if (status < 0) exit(EXIT_FAILURE); if (get_current_dir_name() != NULL || errno != ENOENT) exit(EXIT_FAILURE); status = rmdir(path2); if (status < 0) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } proot-5.4.0/test/test-654decce.sh000066400000000000000000000071711442763353300164750ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/readdir ] || [ ! -x ${ROOTFS}/bin/cat ] || [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which chmod` ] || [ -z `which grep` ] || [ -z `which rm` ] || [ -z `which id` ]; then exit 125; fi if [ `id -u` -eq 0 ]; then exit 125; fi TMP1=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) TMP3=$(mcookie) TMP4=$(mcookie) echo "content of ${TMP1}" > ${TMP1} mkdir -p ${ROOTFS}/${TMP2} chmod -rw ${ROOTFS}/${TMP2} export LANG=C ! ${PROOT} -v -1 -r ${ROOTFS} -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} readdir ${TMP2} | grep '^opendir(3): Permission denied$' ${PROOT} -v -1 -r ${ROOTFS} -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} readdir ${TMP2}/${TMP3} | grep "DT_REG ${TMP4}" ${PROOT} -v -1 -r ${ROOTFS} -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} cat ${TMP2}/${TMP3}/${TMP4} | grep "^content of ${TMP1}$" ${PROOT} -v -1 -r ${ROOTFS} -b /tmp:${TMP2}/${TMP3}/${TMP4} readdir ${TMP2}/${TMP3} | grep "DT_DIR ${TMP4}" # TODO ${PROOT} -v -1 -r ${ROOTFS} -b /tmp:${TMP2}/${TMP3}/${TMP4} readdir /tmp | grep "DT_DIR ${TMP2}" # TODO ${PROOT} -v -1 -r ${ROOTFS} -b /tmp:/${TMP4} readdir / | grep "DT_REG ${TMP4}" ${PROOT} -v -1 -r ${ROOTFS} -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} -b /etc/fstab:${TMP2}/${TMP3}/motd readdir ${TMP2}/${TMP3} | grep "DT_REG ${TMP4}" ${PROOT} -v -1 -r ${ROOTFS} -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} -b /etc/fstab:${TMP2}/${TMP3}/motd readdir ${TMP2}/${TMP3} | grep "DT_REG motd" ${PROOT} -v -1 -r ${ROOTFS} -b ${TMP1}:${TMP2}/${TMP3}/${TMP4}/motd -b /etc/fstab:${TMP2}/${TMP3}/motd cat ${TMP2}/${TMP3}/${TMP4}/motd | grep "^content of ${TMP1}$" ${PROOT} -v -1 -r ${ROOTFS} -b /etc/fstab:${TMP2}/${TMP3}/motd -b ${TMP1}:${TMP2}/${TMP3}/${TMP4}/motd cat ${TMP2}/${TMP3}/${TMP4}/motd | grep "^content of ${TMP1}$" ! chmod +rw ${ROOTFS}/${TMP2} rm -fr ${ROOTFS}/${TMP2} mkdir -p ${TMP2} chmod -rw ${TMP2} export LANG=C ! ${PROOT} -v -1 -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} ${ROOTFS}/bin/readdir ${TMP2} | grep '^opendir(3): Permission denied$' ${PROOT} -v -1 -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} ${ROOTFS}/bin/readdir ${TMP2}/${TMP3} | grep "DT_REG ${TMP4}" ${PROOT} -v -1 -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} ${ROOTFS}/bin/cat ${TMP2}/${TMP3}/${TMP4} | grep "^content of ${TMP1}$" ${PROOT} -v -1 -b /tmp:${TMP2}/${TMP3}/${TMP4} ${ROOTFS}/bin/readdir ${TMP2}/${TMP3} | grep "DT_DIR ${TMP4}" # TODO ${PROOT} -v -1 -b /tmp:${TMP2}/${TMP3}/${TMP4} readdir /tmp | grep "DT_DIR ${TMP2}" # TODO ${PROOT} -v -1 -b /tmp:/${TMP4} readdir / | grep "DT_REG ${TMP4}" ${PROOT} -v -1 -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} -b /etc/fstab:${TMP2}/${TMP3}/motd ${ROOTFS}/bin/readdir ${TMP2}/${TMP3} | grep "DT_REG ${TMP4}" ${PROOT} -v -1 -b ${TMP1}:${TMP2}/${TMP3}/${TMP4} -b /etc/fstab:${TMP2}/${TMP3}/motd ${ROOTFS}/bin/readdir ${TMP2}/${TMP3} | grep "DT_REG motd" ${PROOT} -v -1 -b ${TMP1}:${TMP2}/${TMP3}/${TMP4}/motd -b /etc/fstab:${TMP2}/${TMP3}/motd ${ROOTFS}/bin/cat ${TMP2}/${TMP3}/${TMP4}/motd | grep "^content of ${TMP1}$" ${PROOT} -v -1 -b /etc/fstab:${TMP2}/${TMP3}/motd -b ${TMP1}:${TMP2}/${TMP3}/${TMP4}/motd ${ROOTFS}/bin/cat ${TMP2}/${TMP3}/${TMP4}/motd | grep "^content of ${TMP1}$" ${PROOT} -b /bin:/this1/does/not/exist -b /tmp:/this2/does/not/exist ${ROOTFS}/bin/readdir /this1/ ${PROOT} -b /bin:/this1/does/not/exist -b /tmp:/this2/does/not/exist ${ROOTFS}/bin/readdir /this2/ ${PROOT} -b /tmp:/this1/does/not/exist -b /bin:/this2/does/not/exist ${ROOTFS}/bin/readdir /this1/ ${PROOT} -b /tmp:/this1/does/not/exist -b /bin:/this2/does/not/exist ${ROOTFS}/bin/readdir /this2/ ! chmod +rw ${TMP1} ${TMP2} rm -fr ${TMP1} ${TMP2} proot-5.4.0/test/test-66666666.c000066400000000000000000000013461442763353300157400ustar00rootroot00000000000000#include #include #include #include #include #include #include bool sigtrap_received = false; void handler(int signo) { if (signo == SIGTRAP) sigtrap_received = true; } int main() { struct sigaction sa; int status; sa.sa_flags = 0; sa.sa_handler = handler; status = sigemptyset(&sa.sa_mask); if (status < 0) { perror("sigemptyset()"); exit(EXIT_FAILURE); } status = sigaction(SIGTRAP, &sa, 0); if (status < 0) { perror("sigaction(SIGTRAP)"); exit(EXIT_FAILURE); } status = raise(SIGTRAP); if (status != 0) { perror("raise(SIGTRAP)"); exit(EXIT_FAILURE); } if (sigtrap_received) exit(EXIT_SUCCESS); else exit(EXIT_FAILURE); } proot-5.4.0/test/test-67972fbe.sh000066400000000000000000000006531442763353300163440ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/readdir ] || [ ! -e /bin/true ] || [ -z `which mkdir` ] || [ -z `which ln` ] || [ -z `which rm` ] || [ -z `which grep` ] || [ -z `which mcookie` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir -p ${ROOTFS}/${TMP}/run/dbus mkdir -p ${ROOTFS}/${TMP}/var ln -s ../run ${ROOTFS}/${TMP}/var/run ${PROOT} -b /bin:${TMP}/var/run/dbus -r ${ROOTFS} readdir ${TMP}/var/run/dbus/ | grep true rm -fr ${TMP} proot-5.4.0/test/test-691786c8.sh000066400000000000000000000030741442763353300162100ustar00rootroot00000000000000#!/bin/sh set -eu if [ ! -x "/usr/bin/echo" ] || \ [ -z "$(command -v mcookie)" ] || \ [ -z "$(command -v chmod)" ] || \ [ -z "$(command -v env)" ] || \ [ -z "$(command -v rm)" ]; then exit 125; fi TMP="/tmp/$(mcookie)" echo '#!/usr/bin/echo XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' > "${TMP}" chmod +x "${TMP}" RESULT="$(${PROOT} ${TMP})" EXPECTED="$(${TMP})" [ "${RESULT}" = "${EXPECTED}" ] echo '#!//../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../usr/bin/echo XXXXXXXXX' > "${TMP}" RESULT="$(${PROOT} ${TMP})" EXPECTED="$(${TMP})" [ "${RESULT}" = "${EXPECTED}" ] echo '#!/../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../usr/bin/echo XXXXXXXXX' > "${TMP}" if ${TMP}; then # Linux kernel 5.1-rc1 increases the shebang limit to 256 [ "${RESULT}" = "XXXXXXXXX ${TMP}" ] ${PROOT} ${TMP} else [ "${RESULT}" = "${TMP}" ] ! ${PROOT} ${TMP} fi echo '#! ' > ${TMP} ${PROOT} ${TMP} echo '#!' > ${TMP} ${PROOT} ${TMP} /usr/bin/echo "#!${TMP}" > ${TMP} env LANG=C ${PROOT} ${TMP} 2>&1 | grep 'Too many levels of symbolic links' [ $? -eq 0 ] rm -f "${TMP}" proot-5.4.0/test/test-6b5a254a.sh000066400000000000000000000015011442763353300163130ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which echo` ] || [ -z `which touch` ] || [ -z `which rm` ]; then exit 125; fi FOO1=/tmp/$(mcookie) FOO2=/tmp/$(mcookie) FOO3=/tmp/$(mcookie) FOO4=/tmp/$(mcookie) echo "content of FOO1" > ${FOO1} echo "content of FOO2" > ${FOO2} ln -s ${FOO1} ${FOO3} # FOO3 -> FOO1 ln -s ${FOO2} ${FOO4} # FOO4 -> FOO2 ${PROOT} -b ${FOO3}:${FOO4} cat ${FOO2} | grep '^content of FOO1$' ${PROOT} -b ${FOO4}:${FOO3} cat ${FOO1} | grep '^content of FOO2$' ${PROOT} -b ${FOO3}:${FOO4}! cat ${FOO2} | grep '^content of FOO2$' ${PROOT} -b ${FOO4}:${FOO3}! cat ${FOO1} | grep '^content of FOO1$' ${PROOT} -v -1 -b ${FOO1} -b ${FOO3} cat ${FOO1} | grep '^content of FOO1$' ${PROOT} -v -1 -b ${FOO1} -b ${FOO2}:/tmp/../${FOO1} cat ${FOO1} | grep '^content of FOO2$' rm -f ${FOO1} ${FOO2} ${FOO3} proot-5.4.0/test/test-6d1e2650.sh000066400000000000000000000002601442763353300162370ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/true ] || [ -z `which env` ]; then exit 125; fi ! env PATH=/nib ${PROOT} -r ${ROOTFS} true [ $? -eq 0 ] env PATH=/bin ${PROOT} -r ${ROOTFS} true proot-5.4.0/test/test-6fb08ce1.sh000066400000000000000000000003371442763353300164060ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which grep` ] || [ -z `which rm` ]; then exit 125; fi TMP=/tmp/$(mcookie) echo "OK" > ${TMP} ${PROOT} -b ${TMP}:/etc/fstab -b /dev/null -b /etc cat /etc/fstab | grep ^OK$ rm ${TMP} proot-5.4.0/test/test-713b6910.sh000066400000000000000000000022631442763353300161640ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which rm` ] || [ -z `which cat` ] || [ -z `which chmod` ] || [ -z `which ln` ] || [ -z `which grep` ] || [ -z `which mkdir` ] || [ ! -x ${ROOTFS}/bin/readlink ]; then exit 125; fi ###################################################################### TMP1=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) TMP3=/tmp/$(mcookie) TMP4=/tmp/$(mcookie) rm -fr ${TMP1} ${TMP2} ${TMP3} ${TMP4} ###################################################################### cat > ${TMP1} <<'EOF' #!/bin/sh echo $0 EOF chmod +x ${TMP1} ln -s ${TMP1} ${TMP2} ${PROOT} ${TMP2} | grep -v ${TMP1} ${PROOT} ${TMP2} | grep ${TMP2} ###################################################################### mkdir -p ${TMP3} cd ${TMP3} ln -s $(which true) false ! ${PROOT} false echo "#!$(which false)" > true chmod a-x true ${PROOT} true ###################################################################### ln -s ${ROOTFS}/bin/readlink ${TMP4} TEST1=$(${PROOT} ${ROOTFS}/bin/readlink /proc/self/exe) TEST2=$(${PROOT} ${TMP4} /proc/self/exe) test "${TEST1}" = "${TEST2}" ###################################################################### cd / rm -fr ${TMP1} ${TMP2} ${TMP3} ${TMP4} proot-5.4.0/test/test-7601199b.sh000066400000000000000000000001731442763353300161700ustar00rootroot00000000000000if [ ! -x /bin/sh ] || [ -z `which grep` ]; then exit 125; fi ${PROOT} -w /tmp /bin/sh -c 'echo $PWD' | grep '^/tmp$' proot-5.4.0/test/test-79cf6614.c000066400000000000000000000012071442763353300160660ustar00rootroot00000000000000/* * Submitted-by: Thomas P. HIGDON * Ref.: https://groups.google.com/d/msg/proot_me/4WbUndy-aXI/lmKiDfoIK_IJ */ #include #include #include #include int main() { int status; struct timeval times[2] = { {.tv_sec = 52353, .tv_usec = 0}, { .tv_sec = 52353, .tv_usec = 0 } }; char tmp[] = "proot-XXXXXX"; mktemp(tmp); if (tmp[0] == '\0') exit(EXIT_FAILURE); (void) unlink(tmp); status = symlink("/etc/fstab", tmp); if (status < 0) exit(EXIT_FAILURE); status = lutimes(tmp, times); exit(status < 0 && errno != ENOSYS ? EXIT_FAILURE : EXIT_SUCCESS); } proot-5.4.0/test/test-82ba4ba1.c000066400000000000000000000031361442763353300162040ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include int main(void) { uid_t ruid = 13, euid = 13, suid = 13; gid_t rgid = 13, egid = 13, sgid = 13; int status; status = getresuid(&ruid, &euid, &suid); if (status != 0 || ruid != 0 || euid != 0 || suid != 0) { perror("getresuid"); fprintf(stderr, "%ld %ld %ld\n", (unsigned long) ruid, (unsigned long) euid, (unsigned long) suid); exit(EXIT_FAILURE); } status = getresgid(&rgid, &egid, &sgid); if (status != 0 || rgid != 0 || egid != 0 || sgid != 0) { perror("getresgid"); fprintf(stderr, "%ld %ld %ld\n", (unsigned long) ruid, (unsigned long) euid, (unsigned long) suid); exit(EXIT_FAILURE); } status = setresgid(1, 1, 1); if (status != 0) { perror("setresgid"); exit(EXIT_FAILURE); } status = getresgid(&rgid, &egid, &sgid); if (status != 0 || rgid != 1 || egid != 1 || sgid != 1) { perror("getresgid"); fprintf(stderr, "%ld %ld %ld\n", (unsigned long) rgid, (unsigned long) egid, (unsigned long) sgid); exit(EXIT_FAILURE); } if (status != 0 || rgid != 1 || egid != 1 || sgid != 1) { perror("getresgid"); fprintf(stderr, "%ld %ld %ld\n", (unsigned long) ruid, (unsigned long) euid, (unsigned long) suid); exit(EXIT_FAILURE); } status = setresuid(1, 1, 1); if (status != 0) { perror("setresuid"); exit(EXIT_FAILURE); } status = getresuid(&ruid, &euid, &suid); if (status != 0 || ruid != 1 || euid != 1 || suid != 1) { perror("getresuid"); fprintf(stderr, "%ld %ld %ld\n", (unsigned long) ruid, (unsigned long) euid, (unsigned long) suid); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-82ba4ba1.sh000066400000000000000000000025211442763353300163710ustar00rootroot00000000000000if [ ! -x /bin/true ] || [ -z `which id` ] || [ -z `which grep` ] || [ -z `which env` ] || [ -z `which chown` ] || [ -z `which chroot` ]; then exit 125; fi if [ `id -u` -eq 0 ]; then exit 125; fi ${PROOT} -i 123:456 id -u | grep ^123$ ${PROOT} -i 123:456 id -g | grep ^456$ ${PROOT} -i 123:456 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_UID:[[:space:]]*123$' ${PROOT} -i 123:456 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_EUID:[[:space:]]*123$' ${PROOT} -i 123:456 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_GID:[[:space:]]*456$' ${PROOT} -i 123:456 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_EGID:[[:space:]]*456$' ! ${PROOT} -i 123:456 chown root.root /root [ $? -eq 0 ] ! chroot / /bin/true EXPECTED=$? ! ${PROOT} -i 123:456 chroot / /bin/true [ $? -eq ${EXPECTED} ] ! ${PROOT} -i 123:456 chroot /tmp/.. /bin/true [ $? -eq ${EXPECTED} ] ! ${PROOT} -i 123:456 chroot /tmp /bin/true [ $? -eq 0 ] ${PROOT} -0 id -u | grep ^0$ ${PROOT} -0 id -g | grep ^0$ ${PROOT} -0 chown root.root /root ${PROOT} -0 chroot / /bin/true ${PROOT} -0 chroot /tmp/.. /bin/true ${PROOT} -0 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_UID:[[:space:]]*0$' ${PROOT} -0 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_EUID:[[:space:]]*0$' ${PROOT} -0 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_GID:[[:space:]]*0$' ${PROOT} -0 env LD_SHOW_AUXV=1 /bin/true | grep '^AT_EGID:[[:space:]]*0$' proot-5.4.0/test/test-88888888.c000066400000000000000000000024521442763353300157570ustar00rootroot00000000000000#include #include #include #include #include #include #include int main() { int fd; fd = open("/bin/true", O_RDONLY); if (fd < 0) { perror(NULL); exit(EXIT_FAILURE); } close(fd); fd = open("/bin/true/", O_RDONLY); if (fd >= 0 || errno != ENOTDIR) { perror(NULL); exit(EXIT_FAILURE); } close(fd); fd = open("/bin/true/.", O_RDONLY); if (fd >= 0 || errno != ENOTDIR) { perror(NULL); exit(EXIT_FAILURE); } close(fd); fd = open("/bin/true/..", O_RDONLY); if (fd >= 0 || errno != ENOTDIR) { perror(NULL); exit(EXIT_FAILURE); } close(fd); fd = open("/6a05942f08d5a72de56483487963deec", O_RDONLY); if (fd >= 0 || errno != ENOENT) { perror(NULL); exit(EXIT_FAILURE); } close(fd); fd = open("/6a05942f08d5a72de56483487963deec/", O_RDONLY); if (fd >= 0 || errno != ENOENT) { perror(NULL); exit(EXIT_FAILURE); } close(fd); fd = open("/6a05942f08d5a72de56483487963deec/.", O_RDONLY); if (fd >= 0 || errno != ENOENT) { perror(NULL); exit(EXIT_FAILURE); } close(fd); #if 0 /* This test fails in OBS, why? */ fd = open("/6a05942f08d5a72de56483487963deec/..", O_RDONLY); if (fd >= 0 || errno != ENOENT) { perror(NULL); exit(EXIT_FAILURE); } close(fd); #endif exit(EXIT_SUCCESS); } proot-5.4.0/test/test-8a83376a.sh000066400000000000000000000001331442763353300162460ustar00rootroot00000000000000if [ ! -e /bin/true ] || [ -z `which ldd` ]; then exit 125; fi ${PROOT} ldd /bin/true proot-5.4.0/test/test-8e5fa256.sh000066400000000000000000000044321442763353300163350ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/readlink ] || [ ! -x ${ROOTFS}/bin/symlink ] || [ -z `which mcookie` ] || [ -z `which rm` ] || [ -z `which ln` ] || [ -z `which mkdir` ]; then exit 125; fi LINK_NAME1=`mcookie` LINK_NAME2=`mcookie` rm -f /tmp/${LINK_NAME1} rm -f /tmp/${LINK_NAME2} mkdir -p ${ROOTFS}/tmp ln -s /tmp/ced-host /tmp/${LINK_NAME1} ln -s /tmp/ced-guest ${ROOTFS}/tmp/${LINK_NAME1} ${PROOT} -r ${ROOTFS} readlink /tmp/${LINK_NAME1} | grep ^/tmp/ced-guest$ ${PROOT} -b /tmp -r ${ROOTFS} readlink /tmp/${LINK_NAME1} | grep ^/tmp/ced-host$ ${PROOT} -b /tmp:/foo -r ${ROOTFS} readlink /foo/${LINK_NAME1} | grep ^/foo/ced-host$ ${PROOT} -b /tmp:/foo -r ${ROOTFS} readlink /tmp/${LINK_NAME1} | grep ^/tmp/ced-guest$ ${PROOT} -b /:/host-rootfs -r ${ROOTFS} readlink /tmp/${LINK_NAME1} | grep ^/tmp/ced-guest$ ${PROOT} -b /:/host-rootfs -b /tmp:/foo -r ${ROOTFS} readlink /tmp/${LINK_NAME1} | grep ^/tmp/ced-guest$ # Always use the deepest binding, deepest from the host point-of-view. ${PROOT} -b /:/host-rootfs -r ${ROOTFS} readlink /tmp/${LINK_NAME1} | grep ^/tmp/ced-guest$ ${PROOT} -b /:/host-rootfs -b /tmp -r ${ROOTFS} readlink /tmp/${LINK_NAME1} | grep ^/tmp/ced-host$ ${PROOT} -b /:/host-rootfs -b /tmp:/foo -r ${ROOTFS} readlink /foo/${LINK_NAME1} | grep ^/foo/ced-host$ ${PROOT} -b /:/host-rootfs -b /tmp -r ${ROOTFS} readlink /host-rootfs/tmp/${LINK_NAME1} | grep ^/tmp/ced-host$ ${PROOT} -b /:/host-rootfs -b /tmp:/foo -r ${ROOTFS} readlink /host-rootfs/tmp/${LINK_NAME1} | grep ^/foo/ced-host$ rm /tmp/${LINK_NAME1} rm ${ROOTFS}/tmp/${LINK_NAME1} ${PROOT} -b /:/host-rootfs -b /tmp -w /bin -r ${ROOTFS} symlink /bin/bar /bin/${LINK_NAME1} ${PROOT} -b /:/host-rootfs -b /tmp -w /bin -r ${ROOTFS} readlink ${LINK_NAME1} | grep ^/bin/bar$ rm ${ROOTFS}/bin/${LINK_NAME1} ${PROOT} -b /:/host-rootfs -b /tmp -w /tmp -r ${ROOTFS} symlink /bin/bar /tmp/${LINK_NAME1} ${PROOT} -b /:/host-rootfs -b /tmp -w /tmp -r ${ROOTFS} readlink ${LINK_NAME1} | grep ^/bin/bar$ ${PROOT} -b /:/host-rootfs -b /tmp:/foo -w /foo -r ${ROOTFS} symlink /foo/bar /foo/${LINK_NAME2} ${PROOT} -b /:/host-rootfs -b /tmp:/foo -w /foo -r ${ROOTFS} readlink ${LINK_NAME2} | grep ^/foo/bar$ ${PROOT} -b /:/host-rootfs -b /tmp -w /host-rootfs/tmp -r ${ROOTFS} readlink ${LINK_NAME2} | grep ^/foo/bar$ rm /tmp/${LINK_NAME2} rm /tmp/${LINK_NAME1} proot-5.4.0/test/test-99999999.sh000066400000000000000000000035421442763353300161600ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/readlink ] || [ -z `which readlink` ] || [ -z `which cut` ] || [ -z `which grep` ] || [ -z `which md5sum` ]; then exit 125; fi WHICH_READLINK=$(readlink -f $(which readlink)) ${PROOT} readlink /proc/self/exe | grep ^${WHICH_READLINK}$ ${PROOT} sh -c 'readlink /proc/self/exe' | grep ^${WHICH_READLINK}$ ${PROOT} bash -c 'readlink /proc/$$/exe' | grep ^${WHICH_READLINK}$ ${PROOT} -b /proc -r ${ROOTFS} readlink /proc/self/exe | grep ^/bin/readlink$ ${PROOT} readlink /proc/1/../self/exe | grep ^${WHICH_READLINK}$ ${PROOT} sh -c 'readlink /proc/1/../self/exe' | grep ^${WHICH_READLINK}$ ${PROOT} bash -c 'readlink /proc/1/../$$/exe' | grep ^${WHICH_READLINK}$ ${PROOT} -b /proc -r ${ROOTFS} readlink /proc/1/../self/exe | grep ^/bin/readlink$ ! ${PROOT} readlink /proc/self/exe/ [ $? -eq 0 ] ! ${PROOT} readlink /proc/self/exe/.. [ $? -eq 0 ] ! ${PROOT} readlink /proc/self/exe/../exe [ $? -eq 0 ] ! ${PROOT} -b /proc readlink /proc/self/exe/ [ $? -eq 0 ] ! ${PROOT} -b /proc readlink /proc/self/exe/.. [ $? -eq 0 ] ! ${PROOT} -b /proc readlink /proc/self/exe/../exe [ $? -eq 0 ] TEST=$(${PROOT} readlink /proc/self/fd/0 | grep -E "^/proc/[[:digit:]]+/fd/0$" | true) test -z $TEST TEST=$(${PROOT} -b /proc -r ${ROOTFS} readlink /proc/self/fd/0 | grep -E "^/proc/[[:digit:]]+/fd/0$" | true) test -z $TEST if [ ! -z $$ ]; then TEST=$(readlink -f /proc/$$/exe) # The `true` after the readlink makes sure that # the shell doesn't `exec` the `readlink` command and causes the process exe # to be readlink instead ${PROOT} sh -c 'readlink /proc/$$/exe; true' | grep ${TEST} fi MD5=$(md5sum $(which md5sum) | cut -f 1 -d ' ') MD5_PROOT=$(${PROOT} md5sum /proc/self/exe | cut -f 1 -d ' ') test ${MD5_PROOT} = ${MD5} MD5_PROOT=$(${PROOT} -b /proc md5sum /proc/self/exe | cut -f 1 -d ' ') test ${MD5_PROOT} = ${MD5} proot-5.4.0/test/test-9c07fad8.c000066400000000000000000000002431442763353300162210ustar00rootroot00000000000000#include int check = 0; static void __attribute__((constructor)) init(void) { if (check > 0) _exit(1); check++; } int main(void) { return 0; } proot-5.4.0/test/test-9f5eeb72.sh000066400000000000000000000074541442763353300164270ustar00rootroot00000000000000if [ -z `which mkdir` ] || [ -z `which chmod` ] || [ -z `which touch` ] || [ -z `which ln` ] || [ -z `which cpio` ] || [ -z `which stat` ] || [ -z `which cat` ] || [ -z `which readlink` ] || [ -z `which mcookie` ] || [ -z `which mknod` ]; then exit 125; fi if [ ! -e $CARE ]; then exit 125; fi unset PROOT TMP=/tmp/$(mcookie) mkdir ${TMP} cd ${TMP} export LANG=en_US.UTF-8 mkdir a echo "I'm a bee" > a/b echo "I'm a sea" > a/c chmod -w a touch ł ln ł d ln -s dangling_symlink e mknod f p mkdir -p x/y chmod -rwx x for BUNCH in \ "FORMAT=/ EXTRACT=''" \ "FORMAT=.raw EXTRACT='${CARE} -x'" \ "FORMAT=.cpio EXTRACT='${CARE} -x'" \ "FORMAT=.cpio.gz EXTRACT='${CARE} -x'" \ "FORMAT=.cpio.lzo EXTRACT='${CARE} -x'" \ "FORMAT=.tar EXTRACT='${CARE} -x'" \ "FORMAT=.tgz EXTRACT='${CARE} -x'" \ "FORMAT=.tar.gz EXTRACT='${CARE} -x'" \ "FORMAT=.tzo EXTRACT='${CARE} -x'" \ "FORMAT=.tar.lzo EXTRACT='${CARE} -x'" \ "FORMAT=.bin EXTRACT='${CARE} -x'" \ "FORMAT=.bin EXTRACT='sh -c'" \ "FORMAT=.gz.bin EXTRACT='sh -c'" \ "FORMAT=.lzo.bin EXTRACT='sh -c'" \ "FORMAT=.cpio.bin EXTRACT='sh -c'" \ "FORMAT=.cpio.gz.bin EXTRACT='sh -c'" \ "FORMAT=.cpio.lzo.bin EXTRACT='sh -c'" \ "FORMAT=.tar.bin EXTRACT='sh -c'" \ "FORMAT=.tgz.bin EXTRACT='sh -c'" \ "FORMAT=.tzo.bin EXTRACT='sh -c'" \ "FORMAT=.tar.gz.bin EXTRACT='sh -c'" \ "FORMAT=.tar.lzo.bin EXTRACT='sh -c'" do eval $BUNCH CWD=${PWD} if echo ${FORMAT} | grep '.bin' && ${CARE} -V | grep '(.bin): no'; then continue fi # Check: permissions, unordered archive, UTF-8, hard-links ${CARE} -o test${FORMAT} cat a/b ł d a/c if [ -n "${EXTRACT}" ]; then ! chmod +rwx -R test-${FORMAT}-1 rm -fr test-${FORMAT}-1 mkdir test-${FORMAT}-1 cd test-${FORMAT}-1 ${EXTRACT} ../test${FORMAT} fi test -d test/rootfs/${CWD}/a test -f test/rootfs/${CWD}/a/b test -f test/rootfs/${CWD}/a/c test -f test/rootfs/${CWD}/ł test -f test/rootfs/${CWD}/d INODE1=$(stat -c %i test/rootfs/${CWD}/d) INODE2=$(stat -c %i test/rootfs/${CWD}/ł) [ $INODE1 -eq $INODE2 ] PERM1=$(stat -c %a ${CWD}/a) PERM2=$(stat -c %a test/rootfs/${CWD}/a) [ $PERM1 -eq $PERM2 ] if [ -n "${EXTRACT}" ]; then cd .. else ! chmod +rwx -R test rm -fr test fi # Check: last archived version wins, symlinks ${CARE} -o test${FORMAT} sh -c 'ls a; ls a/b; ls -l e' if [ -n "${EXTRACT}" ]; then ! chmod +rwx -R test-${FORMAT}-2 rm -fr test-${FORMAT}-2 mkdir test-${FORMAT}-2 cd test-${FORMAT}-2 ${EXTRACT} ../test${FORMAT} fi B=$(cat test/rootfs/${CWD}/a/b) [ x"$B" != x ] [ "$B" = "I'm a bee" ] test -L test/rootfs/${CWD}/e F=$(readlink test/rootfs/${CWD}/e) [ x"$F" != x ] [ "$F" = "dangling_symlink" ] if [ -n "${EXTRACT}" ]; then cd .. else ! chmod +rwx -R test rm -fr test fi # Check: non-regular files are archived/extractable ${CARE} -d -p /dev -p /proc -o test${FORMAT} sh -c 'ls -l f' if [ -n "${EXTRACT}" ]; then ! chmod +rwx -R test-${FORMAT}-1 rm -fr test-${FORMAT}-1 mkdir test-${FORMAT}-1 cd test-${FORMAT}-1 ${EXTRACT} ../test${FORMAT} fi [ "fifo" = "$(stat -c %F test/rootfs/${CWD}/f)" ] if [ -n "${EXTRACT}" ]; then cd .. else ! chmod +rwx -R test rm -fr test fi # Check: extractable archive ${CARE} -o test${FORMAT} chmod -R +rwx x if [ -n "${EXTRACT}" ]; then ! chmod +rwx -R test-${FORMAT}-3 rm -fr test-${FORMAT}-3 mkdir test-${FORMAT}-3 cd test-${FORMAT}-3 ${EXTRACT} ../test${FORMAT} cd .. else ! chmod +rwx -R test rm -fr test fi done cd .. chmod +rwx -R ${TMP} rm -fr ${TMP} proot-5.4.0/test/test-a4d7ed70.sh000066400000000000000000000006361442763353300164110ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which ln` ] || [ -z `which ls` ] || [ -z `which rm` ] || [ -z `which cat` ]; then exit 125; fi TMP=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) mkdir ${TMP} ln -s /proc/self/fd ${TMP}/fd ln -s ${TMP}/fd/0 ${TMP}/stdin ${PROOT} \ls ${TMP}/stdin | grep ^${TMP}/stdin$ echo OK > ${TMP2} ${PROOT} cat ${TMP}/stdin < ${TMP2} | grep ^OK$ rm -fr ${TMP} ${TMP2} proot-5.4.0/test/test-a8e69d6f.c000066400000000000000000000013351442763353300162330ustar00rootroot00000000000000#include /* syscall(2), */ #include /* perror(3), fprintf(3), */ #include /* exit(3), */ #include /* SYS_lstat, */ #include /* struct stat, */ #include /* AT_FDCWD */ int main(void) { struct stat stat; int status; #if defined(SYS_lstat) status = syscall(SYS_lstat, "/proc/self/cwd/", &stat); #elif defined(SYS_newfstatat) status = syscall(SYS_newfstatat, AT_FDCWD, "/proc/self/cwd/", &stat, 0); #else #error "SYS_lstat and SYS_newfstatat doesn't exists" #endif if (status < 0) { perror("lstat()"); exit(EXIT_FAILURE); } if (S_ISLNK(stat.st_mode)) { fprintf(stderr, "trailing '/' ignored\n"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-aaaaaaaa.sh000066400000000000000000000034661442763353300166650ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/true ] || [ -z `which id` ] || [ -z `which mcookie` ] || [ -z `which ln` ] || [ -z `which rm` ]; then exit 125; fi if [ `id -u` -eq 0 ]; then exit 125; fi DONT_EXIST=/$(mcookie) ${PROOT} -r ${ROOTFS} true ! ${PROOT} ${DONT_EXIST} true [ $? -eq 0 ] ${PROOT} -r ${ROOTFS} true ${PROOT} -r /etc -r ${ROOTFS} true ! ${PROOT} -r ${ROOTFS} -r ${DONT_EXIST} true [ $? -eq 0 ] ! ${PROOT} -r ${DONT_EXIST} ${ROOTFS} true [ $? -eq 0 ] ! ${PROOT} ${ROOTFS} -r ${ROOTFS} true [ $? -eq 0 ] ! ${PROOT} -v [ $? -eq 0 ] ${PROOT} -b /bin/true:${DONT_EXIST} ${DONT_EXIST} ! ${PROOT} -r / -b /etc:/ true [ $? -eq 0 ] ! ${PROOT} -b /etc:/ true [ $? -eq 0 ] ${PROOT} -b /etc:/ -r / true TMP1=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) echo "${TMP1}" > ${TMP1} echo "${TMP2}" > ${TMP2} REGULAR=/tmp/$(mcookie) SYMLINK_TO_REGULAR=/tmp/$(mcookie) ln -s ${REGULAR} ${SYMLINK_TO_REGULAR} ${PROOT} -v -1 -b ${TMP1}:${REGULAR} -b ${TMP2}:${SYMLINK_TO_REGULAR} cat ${REGULAR} | grep "^${TMP2}$" ${PROOT} -v -1 -b ${TMP2}:${SYMLINK_TO_REGULAR} -b ${TMP1}:${REGULAR} cat ${REGULAR} | grep "^${TMP1}$" ${PROOT} -v -1 -b ${TMP1}:${REGULAR} -b ${TMP2}:${SYMLINK_TO_REGULAR}! cat ${REGULAR} | grep "^${TMP1}$" ${PROOT} -v -1 -b ${TMP1}:${REGULAR} -b ${TMP2}:${SYMLINK_TO_REGULAR}! cat ${SYMLINK_TO_REGULAR} | grep "^${TMP2}$" ${PROOT} -v -1 -b ${TMP1}:${REGULAR}! -b ${TMP2}:${SYMLINK_TO_REGULAR}! cat ${REGULAR} | grep "^${TMP1}$" ${PROOT} -v -1 -b ${TMP1}:${REGULAR}! -b ${TMP2}:${SYMLINK_TO_REGULAR}! cat ${SYMLINK_TO_REGULAR} | grep "^${TMP2}$" ${PROOT} -v -1 -b ${TMP1}:${REGULAR} -b ${TMP2}:${SYMLINK_TO_REGULAR} cat ${SYMLINK_TO_REGULAR} | grep "^${TMP2}$" ${PROOT} -v -1 -b ${TMP2}:${SYMLINK_TO_REGULAR} -b ${TMP1}:${REGULAR} cat ${SYMLINK_TO_REGULAR} | grep "^${TMP1}$" rm -fr ${TMP1} ${TMP2} ${REGULAR} $SYMLINK_TO_REGULAR} proot-5.4.0/test/test-af062114.c000066400000000000000000000010551442763353300160420ustar00rootroot00000000000000#include #include #include #include #include int main(void) { int fd; int status; bool stop = false; fd = open("/proc/self/cmdline", O_RDONLY); if (fd < 0) { perror("open()"); exit(EXIT_FAILURE); } do { char buffer; status = read(fd, &buffer, 1); if (status < 0) { perror("read()"); exit(EXIT_FAILURE); } stop = (status == 0); status = write(1, &buffer, 1); if (status < 0) { perror("write()"); exit(EXIT_FAILURE); } } while (!stop); exit(EXIT_SUCCESS); } proot-5.4.0/test/test-b161bc0a.sh000066400000000000000000000001761442763353300163700ustar00rootroot00000000000000if [ -z `which pwd` ] || [ -z `which grep` ]; then exit 125; fi ${PROOT} -w /tmp/a -m /etc:/tmp/a pwd | grep '^/tmp/a$' proot-5.4.0/test/test-b6df3cbe.sh000066400000000000000000000005511442763353300165440ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which cat` ] || [ -z `which tr` ] || [ -z `which grep` ] || [ -z `which grep` ] || [ -z `which chmod` ]; then exit 125; fi TMP=$(mcookie) cat > /tmp/${TMP} < /* execv(3), syscall(2), */ #include /* SYS_*, */ #include /* *rlimit(2), */ #include /* *rlimit(2), */ #include /* EXIT_*, exit(3), */ int main(int argc, char *argv[]) { char *const dummy_argv[] = { "test", "stage2", NULL }; long brk1, brk2; int status; struct rlimit rlimit; switch (argc) { case 1: /* 1st step: set the stack limit to the max. */ status = getrlimit(RLIMIT_STACK, &rlimit); if (status < 0) exit(EXIT_FAILURE); rlimit.rlim_cur = rlimit.rlim_max; status = setrlimit(RLIMIT_STACK, &rlimit); if (status < 0) exit(EXIT_FAILURE); return execv(argv[0], dummy_argv); default: /* 2nd step: try to allocate some heap space. */ brk1 = syscall(SYS_brk, 0); brk2 = syscall(SYS_brk, brk1 + 1024 * 1024); exit(brk1 != brk2 ? EXIT_SUCCESS : EXIT_FAILURE); } } proot-5.4.0/test/test-bug-138.c000066400000000000000000000066751442763353300161010ustar00rootroot00000000000000#include #include #include #include #include #include void cleanup(void) { chmod("top-bug-138", 0777); rmdir("top-bug-138/a/b"); rmdir("top-bug-138/a"); rmdir("top-bug-138"); } int main() { struct stat statbuf; int i = atexit(cleanup); if (i != 0) { fprintf(stderr, "cannot set atexit cleanup\n"); exit(EXIT_FAILURE); } int mydirtop=mkdir("top-bug-138", 0777); if (mydirtop != 0) { perror("mkdir top-bug-138"); exit(EXIT_FAILURE); } int mydira=mkdir("top-bug-138/a", 0777); if (mydira != 0) { perror("mkdir top-bug-138/a"); exit(EXIT_FAILURE); } int mydirb=mkdir("top-bug-138/a/b", 0777); if (mydirb != 0) { perror("mkdir top-bug-138/a/b"); exit(EXIT_FAILURE); } int myunlinka1=unlink("top-bug-138/a"); if (myunlinka1 == 0) { perror("unlink top-bug-138/a should have failed"); exit(EXIT_FAILURE); } if (errno != EISDIR) { perror("unlink top-bug-138/a should have failed with EISDIR"); exit(EXIT_FAILURE); } int myrmdira=rmdir("top-bug-138/a"); if (myrmdira == 0) { perror("rmdir top-bug-138/a should have failed"); exit(EXIT_FAILURE); } if (errno != ENOTEMPTY) { perror("rmdir top-bug-138/a should have failed with ENOTEMPTY"); exit(EXIT_FAILURE); } int mychmod1=chmod("top-bug-138", 0); if (mychmod1 != 0) { perror("chmod top-bug-138"); exit(EXIT_FAILURE); } int myunlinkab=unlink("top-bug-138/a/b"); if (myunlinkab == 0) { perror("unlink top-bug-138/a/b should have failed"); exit(EXIT_FAILURE); } if (errno != EACCES) { perror("unlink top-bug-138/a/b should have failed with EACCES"); exit(EXIT_FAILURE); } int myrmdirab=rmdir("top-bug-138/a/b"); if (myrmdirab == 0) { perror("rmdir top-bug-138/a/b should have failed"); exit(EXIT_FAILURE); } if (errno != EACCES) { perror("rmdir top-bug-138/a/b should have failed with EACCES"); exit(EXIT_FAILURE); } int mystat=stat("top-bug-138/a/b", &statbuf); if (mystat == 0) { perror("stat top-bug-138/a/b should have failed"); exit(EXIT_FAILURE); } if (errno != EACCES) { perror("stat top-bug-138/a/b should have failed with EACCES"); exit(EXIT_FAILURE); } int mychmod2=chmod("top-bug-138", 0700); if (mychmod2 != 0) { perror("chmod top-bug-138"); exit(EXIT_FAILURE); } int myunlinka=unlink("top-bug-138/a"); if (myunlinka == 0) { perror("unlink top-bug-138/a should have failed"); exit(EXIT_FAILURE); } if (errno != EISDIR) { perror("unlink top-bug-138/a should have failed with EISDIR"); exit(EXIT_FAILURE); } myrmdira=rmdir("top-bug-138/a"); if (myrmdira == 0) { perror("rmdir top-bug-138/a should have failed"); exit(EXIT_FAILURE); } if (errno != ENOTEMPTY) { perror("unlink top-bug-138/a should have failed with ENOTEMPTY"); exit(EXIT_FAILURE); } int myunlinktop=unlink("top-bug-138"); if (myunlinktop == 0) { perror("unlink top-bug-138 should have failed"); exit(EXIT_FAILURE); } if (errno != EISDIR) { perror("unlink top-bug-138 should have failed with EISDIR"); exit(EXIT_FAILURE); } int myrmdirtop=rmdir("top-bug-138"); if (myrmdirtop == 0) { perror("rmdir top-bug-138 should have failed"); exit(EXIT_FAILURE); } if (errno != ENOTEMPTY) { perror("rmdir top-bug-138 should have failed with ENOTEMPTY"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-c10e2073.c000066400000000000000000000021611442763353300160410ustar00rootroot00000000000000#include /* syscall(2), */ #include /* perror(3), fprintf(3), */ #include /* PATH_MAX, */ #include /* exit(3), */ #include /* strlen(3), */ #include /* SYS_readlink, SYS_getcwd, */ #include /* AT_FDCWD */ int main(void) { char path[PATH_MAX]; int status; #if defined(SYS_readlink) status = syscall(SYS_readlink, "/proc/self/cwd", path, PATH_MAX); #elif defined(SYS_readlinkat) status = syscall(SYS_readlinkat, AT_FDCWD, "/proc/self/cwd", path, PATH_MAX); #else #error "SYS_readlink and SYS_readlinkat doesn't exists" #endif if (status < 0) { perror("readlink()"); exit(EXIT_FAILURE); } path[status] = '\0'; if (status != strlen(path)) { fprintf(stderr, "readlink() returned the wrong size %d != %z.\n", status, strlen(path)); exit(EXIT_FAILURE); } status = syscall(SYS_getcwd, path, PATH_MAX); if (status < 0) { perror("getcwd()"); exit(EXIT_FAILURE); } if (status != strlen(path) + 1) { fprintf(stderr, "getcwd() returned the wrong size %d != %z.\n", status, strlen(path)); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-c15999f9.sh000066400000000000000000000006531442763353300162730ustar00rootroot00000000000000#!/bin/sh set -eu if [ -z "$(command -v mcookie)" ] || \ [ -z "$(command -v mkdir)" ] || \ [ -z "$(command -v test)" ] || \ [ -z "$(command -v grep)" ] || \ [ -z "$(command -v rm)" ]; then exit 125; fi TMP="/tmp/$(mcookie)" mkdir "${TMP}" # shellcheck disable=SC2230 "${PROOT}" -b "$(which true):${TMP}/true" "$(which true)" # shellcheck disable=SC2086 [ ! "$(test -e ${TMP}/true)" = "0" ] rm -fr "${TMP}" proot-5.4.0/test/test-c5a7a0f0.c000066400000000000000000000031101442763353300161760ustar00rootroot00000000000000#include #include #include #include #include #include #include static void *setuid_124(void *unused) { int status; status = setuid(124); if (status < 0) { perror("setuid"); pthread_exit((void *)-1); } if (getuid() != 124) { perror("getuid"); pthread_exit((void *)-1); } pthread_exit(NULL); } static void pterror(const char *message, int error) { fprintf(stderr, "%s: %s\n", message, strerror(error)); } int main(void) { pthread_t thread; int child_status; void *result; int status; switch(fork()) { case -1: perror("fork"); exit(EXIT_FAILURE); case 0: /* child */ status = setuid(123); if (status < 0) { perror("setuid"); exit(EXIT_FAILURE); } if (getuid() != 123) { perror("getuid"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); default: /* parent */ break; } status = wait(&child_status); if (status < 0 || child_status != 0) { perror("wait()"); exit(EXIT_FAILURE); } if (getuid() != 0) { fprintf(stderr, "getuid() == %d != 0\n", getuid()); exit(EXIT_FAILURE); } status = pthread_create(&thread, NULL, setuid_124, NULL); if (status != 0) { pterror("pthread_create", status); exit(EXIT_FAILURE); } status = pthread_join(thread, &result); if (status != 0) { pterror("pthread_create", status); exit(EXIT_FAILURE); } if (result != NULL) { fprintf(stderr, "result != NULL\n"); exit(EXIT_FAILURE); } if (getuid() != 124) { fprintf(stderr, "getuid() == %d != 124\n", getuid()); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-c68d18dc.sh000066400000000000000000000010451442763353300164110ustar00rootroot00000000000000if [ -z `which mkdir` ] || [ -z `which rm` ] || [ -z `which mcookie` ] || [ -z `which chmod` ] || [ -z `which ln` ]; then exit 125; fi if [ ! -e $CARE ]; then exit 125; fi unset PROOT SYMLINK=/tmp/$(mcookie) FOLDER=/tmp/$(mcookie) SCRIPT=${FOLDER}/script.sh ARCHIVE=/tmp/$(mcookie)/ mkdir ${FOLDER} echo "true" > ${SCRIPT} chmod +x ${SCRIPT} ln -s ${FOLDER} ${SYMLINK} cd ${SYMLINK} ${CARE} -r ${FOLDER} -o ${ARCHIVE} sh ./script.sh test -e ${ARCHIVE}/rootfs${SCRIPT} ${ARCHIVE}/re-execute.sh rm -fr ${SYMLINK} ${FOLDER} ${ARCHIVE} proot-5.4.0/test/test-c6b77b77.mk000066400000000000000000000001351442763353300163310ustar00rootroot00000000000000SHELL=/bin/bash FOO:=$(shell test -e /dev/null && echo OK) all: @/usr/bin/test -n "$(FOO)" proot-5.4.0/test/test-c6b77b77.sh000066400000000000000000000001301442763353300163270ustar00rootroot00000000000000if [ -z `which make` ]; then exit 125; fi ${PROOT} make -f ${PWD}/test-c6b77b77.mk proot-5.4.0/test/test-careauth.sh000066400000000000000000000013261442763353300167630ustar00rootroot00000000000000if [ -z `which env` ] || [ -z `which rm` ] || [ -z `which mcookie` ] || [ -z `which true` ]; then exit 125; fi if [ ! -e $CARE ]; then exit 125; fi unset PROOT TMP=$(mcookie) cd /tmp env ICEAUTHORITY= ${CARE} -o ${TMP} true ${CARE} -x ./${TMP} ./${TMP}/re-execute.sh rm -fr ${TMP} env XAUTHORITY= ${CARE} -o ${TMP} true ${CARE} -x ./${TMP} ./${TMP}/re-execute.sh rm -fr ${TMP} ${CARE} -o ${TMP} -p ../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../ true ${CARE} -x ./${TMP} ./${TMP}/re-execute.sh rm -fr ${TMP} proot-5.4.0/test/test-careexit.sh000066400000000000000000000005551442763353300167760ustar00rootroot00000000000000if [ -z `which cpio` ] || [ -z `which rm` ] || [ -z `which mcookie` ]; then exit 125; fi if [ ! -e $CARE ]; then exit 125; fi unset PROOT TMP=/tmp/$(mcookie) ${CARE} -o ${TMP}.cpio sh -c 'exit 0' cd /tmp cpio -idmuvF ${TMP}.cpio ${TMP}/re-execute.sh set +e ${TMP}/re-execute.sh sh -c 'exit 132' status=$? set -e [ $status -eq 132 ] rm -f ${TMP}.cpio proot-5.4.0/test/test-carehwcp.sh000066400000000000000000000007131442763353300167620ustar00rootroot00000000000000if [ ! -x /bin/true ] || [ -z `which grep` ] || [ -z `which env` ] || [ -z `which mcookie`] || [ -z `which rm` ]; then exit 125; fi if [ ! -e $CARE ]; then exit 125; fi TMP=/tmp/$(mcookie) ${CARE} -o ${TMP}/ env LD_SHOW_AUXV=1 true | grep '^AT_HWCAP:[[:space:]]*0\?$' ${TMP}/re-execute.sh | grep '^AT_HWCAP:[[:space:]]*0\?$' ${TMP}/re-execute.sh env LD_SHOW_AUXV=1 true | grep '^AT_HWCAP:[[:space:]]*0\?$' rm -fr ${TMP} proot-5.4.0/test/test-carequot.sh000066400000000000000000000005171442763353300170130ustar00rootroot00000000000000if [ -z `which env` ] || [ -z `which rm` ] || [ -z `which mcookie` ] || [ -z `which true` ]; then exit 125; fi if [ ! -e $CARE ]; then exit 125; fi unset PROOT TMP=/tmp/$(mcookie) env 'COMP_WORDBREAKS= "'\''><;|&(:' ${CARE} -o ${TMP}.raw true cd /tmp; ${CARE} -x ${TMP}.raw ${TMP}/re-execute.sh rm -fr ${TMP}.raw ${TMP} proot-5.4.0/test/test-cb1143ab.sh000066400000000000000000000025361442763353300163730ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which ln` ] || [ -z `which ls` ]; then exit 125; fi D1=`mcookie` D2=`mcookie` LINK=`mcookie` F=`mcookie` TMP=/tmp/${D1}/${D2} mkdir -p ${TMP} ln -s ${TMP}/./. ${TMP}/${LINK} ${PROOT} \ls ${TMP}/${LINK} | grep ^${LINK}$ ${PROOT} \ls ${TMP}/${LINK}/ | grep ^${LINK}$ ${PROOT} \ls ${TMP}/${LINK}/. | grep ^${LINK}$ ${PROOT} \ls ${TMP}/${LINK}/.. | grep ^${D2}$ ${PROOT} \ls ${TMP}/${LINK}/./.. | grep ^${D2}$ rm ${TMP}/${LINK} touch ${TMP}/${F} ln -s ${TMP}/${F} ${TMP}/${LINK} ${PROOT} \ls ${TMP}/${LINK} ! ${PROOT} \ls ${TMP}/${LINK}/ [ $? -eq 0 ] ! ${PROOT} \ls ${TMP}/${LINK}/. [ $? -eq 0 ] ! ${PROOT} \ls ${TMP}/${LINK}/.. [ $? -eq 0 ] ! ${PROOT} \ls ${TMP}/${LINK}/./.. [ $? -eq 0 ] ! ${PROOT} \ls ${TMP}/${LINK}/../.. [ $? -eq 0 ] ${PROOT} -b /tmp/${D1}:${TMP}/${F} \ls ${TMP}/${LINK} ${PROOT} -b /tmp/${D1}:${TMP}/${F} \ls ${TMP}/${LINK}/ ${PROOT} -b /tmp/${D1}:${TMP}/${F} \ls ${TMP}/${LINK}/. ${PROOT} -b /tmp/${D1}:${TMP}/${F} \ls ${TMP}/${LINK}/.. rm ${TMP}/${LINK} ln -s ${TMP}/${D1} ${TMP}/${LINK} ${PROOT} -b /tmp/${F}:${TMP}/${D1} \ls ${TMP}/${LINK} ! ${PROOT} -b /tmp/${F}:${TMP}/${D1} \ls ${TMP}/${LINK}/ [ $? -eq 0 ] ! ${PROOT} -b /tmp/${F}:${TMP}/${D1} \ls ${TMP}/${LINK}/. [ $? -eq 0 ] ! ${PROOT} -b /tmp/${F}:${TMP}/${D1} \ls ${TMP}/${LINK}/.. [ $? -eq 0 ] rm -fr /tmp/${D1} proot-5.4.0/test/test-cccccccc.sh000066400000000000000000000003531442763353300166750ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which rmdir` ] || [ -z `which mkdir` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir ${TMP} ! ${PROOT} rmdir ${TMP}/. [ $? -eq 0 ] ! ${PROOT} rmdir ${TMP}/./ [ $? -eq 0 ] ${PROOT} rmdir ${TMP} proot-5.4.0/test/test-cdd39012.sh000066400000000000000000000005031442763353300163140ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/ptrace ] || [ ! -x ${ROOTFS}/bin/ptrace-2 ] || [ ! -x ${ROOTFS}/bin/true ]; then exit 125; fi ${PROOT} -r ${ROOTFS} ptrace ${PROOT} -r ${ROOTFS} ptrace 2 ${PROOT} -r ${ROOTFS} ptrace-2 /bin/true ${PROOT} -r ${ROOTFS} ptrace-2 /bin/fork-wait ${PROOT} -r ${ROOTFS} ptrace-2 /bin/fork-wait 2 proot-5.4.0/test/test-cea75343.sh000066400000000000000000000017311442763353300163250ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which cat` ] || [ -z `which grep` ] || [ -z `which rm` ]; then exit 125; fi TMP1=/tmp/`mcookie` TMP2=/tmp/`mcookie` TMP3=/tmp/`mcookie` # /a/b/c # /a/b # /a/d # /a echo 'binding 1' > ${TMP1} echo 'binding 2' > ${TMP2} mkdir -p ${TMP3}/a/b BINDINGS="-b ${TMP1}:${TMP3}/a/b/c -b ${TMP3}/a/b -b ${TMP2}:${TMP3}/a/d -b ${TMP3}/a" ${PROOT} ${BINDINGS} cat ${TMP3}/a/b/c | grep '^binding 1$' BINDINGS="-b ${TMP3}/a -b ${TMP2}:${TMP3}/a/d -b ${TMP3}/a/b -b ${TMP1}:${TMP3}/a/b/c" ${PROOT} ${BINDINGS} cat ${TMP3}/a/d | grep '^binding 2$' mkdir -p ${TMP3}/c/b # /c/b/a # /c/b # /c/d # /c BINDINGS="-b ${TMP1}:${TMP3}/c/b/a -b ${TMP3}/c/b -b ${TMP2}:${TMP3}/c/d -b ${TMP3}/c" ${PROOT} ${BINDINGS} cat ${TMP3}/c/b/a | grep '^binding 1$' BINDINGS="-b ${TMP3}/c -b ${TMP2}:${TMP3}/c/d -b ${TMP3}/c/b -b ${TMP1}:${TMP3}/c/b/a" ${PROOT} ${BINDINGS} cat ${TMP3}/c/d | grep '^binding 2$' rm ${TMP1} rm ${TMP2} rm -fr ${TMP3} proot-5.4.0/test/test-chroot01.sh000066400000000000000000000002251442763353300166230ustar00rootroot00000000000000#!/bin/sh if [ ! -x ${ROOTFS}/bin/true -o ! -x ${ROOTFS}/bin/chroot ]; then exit 125; fi ${PROOT} -0 -r ${ROOTFS} -w / /bin/chroot . /bin/true proot-5.4.0/test/test-commmmmm.sh000066400000000000000000000013611442763353300170050ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which cat` ] || [ -z `which grep` ] || [ -z `which chmod` ] || [ -z `which cut` ] || [ -z `which rm` ] || [ -z `which ln` ] || [ -z `which env` ]; then exit 125; fi TMP=$(mcookie) TMP2=$(echo ${TMP} | cut -b 1-15) TMP3=$(mcookie) TMP4=$(echo ${TMP3} | cut -b 1-15) ${PROOT} cat /proc/self/comm | grep cat ${PROOT} $(which cat) /proc/self/comm | grep cat echo '#!/bin/sh' > /tmp/${TMP} chmod +x /tmp/${TMP} # TODO: (cd /tmp; ${PROOT} env LD_SHOW_AUXV=1 ./${TMP}) | grep ^AT_EXECFN:[[:space:]]*./${TMP}$ echo 'cat /proc/$$/comm' >> /tmp/${TMP} ${PROOT} /tmp/${TMP} | grep ^${TMP2}$ ln -s /tmp/${TMP} /tmp/${TMP3} ${PROOT} /tmp/${TMP3} /proc/self/comm | grep ^${TMP4}$ rm -f /tmp/${TMP} /tmp/${TMP3} proot-5.4.0/test/test-d1be631a.sh000066400000000000000000000004441442763353300163750ustar00rootroot00000000000000#!/bin/sh # shellcheck disable=SC2086 set -eu if [ -z "$(command -v mknod)" ] || \ [ "$(id -u)" -eq 0 ]; then exit 125 fi TMP="/tmp/$(mcookie)" [ ! "$(${PROOT} mknod ${TMP} b 1 1)" = "0" ] [ ! "$(${PROOT} -i 123:456 mknod ${TMP} b 1 1)" = "0" ] "${PROOT}" -0 mknod "${TMP}" b 1 1 proot-5.4.0/test/test-d1da0d8d.sh000066400000000000000000000004651442763353300164630ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/readlink ] || [ ! -e /proc/self/cwd ] || [ -z `which grep` ]; then exit 125; fi ${PROOT} -m /proc -m /tmp:/asym -w /asym -r ${ROOTFS} /bin/readlink /proc/self/cwd | grep '^/asym$' ${PROOT} -m /proc -m /tmp:/asym -w /tmp -r ${ROOTFS} /bin/readlink /proc/self/cwd | grep '^/tmp$' proot-5.4.0/test/test-d2175fc3.sh000066400000000000000000000007021442763353300163220ustar00rootroot00000000000000#!/bin/sh if [ ! -x "${ROOTFS}/bin/readlink" ] || [ -z "$(command -v grep)" ]; then exit 125; fi "${PROOT}" -r "${ROOTFS}" /bin/readlink /bin/abs-true | grep "$(command -v true)" "${PROOT}" -r "${ROOTFS}" /bin/readlink /bin/rel-true | grep '^\./true$' "${PROOT}" -b /:/host-rootfs -r "${ROOTFS}" /bin/readlink /bin/abs-true | grep "$(command -v true)" "${PROOT}" -b /:/host-rootfs -r "${ROOTFS}" /bin/readlink /bin/rel-true | grep '^./true$' proot-5.4.0/test/test-d2175fc4.c000066400000000000000000000015541442763353300161410ustar00rootroot00000000000000#include /* syscall(2), */ #include /* perror(3), fprintf(3), */ #include /* PATH_MAX, */ #include /* exit(3), */ #include /* bzero(3), */ #include /* SYS_readlink, */ #include /* AT_FDCWD */ int main(int argc, char *argv[]) { char path[PATH_MAX]; int status; bzero(path, sizeof(path)); #if defined(SYS_readlink) status = syscall(SYS_readlink, "/proc/self/exe", path, PATH_MAX); #elif defined(SYS_readlinkat) status = syscall(SYS_readlinkat, AT_FDCWD, "/proc/self/exe", path, PATH_MAX); #else #error "SYS_readlink and SYS_readlinkat doesn't exists" #endif if (status < 0) { perror("readlink()"); exit(EXIT_FAILURE); } if (status >= PATH_MAX) return 125; if (path[status] != '\0') { path[PATH_MAX - 1] = '\0'; puts(path); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-d92b57ca.sh000066400000000000000000000001721442763353300164050ustar00rootroot00000000000000if [ -z `which env` ] || [ -z `which true` ]; then exit 125; fi env PROOT_NO_SUBRECONF=1 ${PROOT} ${PROOT} -v 1 true proot-5.4.0/test/test-dddddddd.sh000066400000000000000000000014601442763353300167050ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which rm` ] || [ -z `which ln` ] || [ -z `which realpath` ] || [ -z `which mkdir` ] || [ -z `which rmdir` ]; then exit 125; fi CHECK1=$(realpath -e /proc/self/exe) CHECK2=$(realpath /proc/self/exe) if [ "${CHECK1}" != "${CHECK2}" ]; then exit 125; fi TMP="/tmp/$(mcookie)" TMP2="/tmp/$(mcookie)" RMDIR=$(realpath -e $(which rmdir)) MKDIR=$(realpath -e $(which mkdir)) export LANG=C ln -s /bin ${TMP} ! ${RMDIR} ${TMP} > ${TMP}.ref 2>&1 ! ${PROOT} -v -1 ${RMDIR} ${TMP} > ${TMP}.res 2>&1 cmp ${TMP}.ref ${TMP}.res ln -s /this/does/not/exist ${TMP2} ! ${MKDIR} ${TMP2} > ${TMP2}.ref 2>&1 ! ${PROOT} -v -1 ${MKDIR} ${TMP2} > ${TMP2}.res 2>&1 cmp ${TMP2}.ref ${TMP2}.res rm -f ${TMP} rm -f ${TMP}.ref rm -f ${TMP}.res rm -f ${TMP2} rm -f ${TMP2}.ref rm -f ${TMP2}.res proot-5.4.0/test/test-de756935.sh000066400000000000000000000004121442763353300162550ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which bash` ] || [ -z `which grep` ] || [ -z `which rm` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir -p ${TMP} cd ${TMP} ${PROOT} -b ${PWD}:/foo -w /foo bash -c 'pwd' | grep '^/foo$' rm -fr ${TMP} proot-5.4.0/test/test-df4de4db.sh000066400000000000000000000004021442763353300165410ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/fork-wait ] || [ -z `which strace` ]; then exit 125; fi ${PROOT} strace ${ROOTFS}/bin/fork-wait ${PROOT} strace ${ROOTFS}/bin/fork-wait 2 ${PROOT} strace -f ${ROOTFS}/bin/fork-wait ${PROOT} strace -f ${ROOTFS}/bin/fork-wait 2 proot-5.4.0/test/test-dfb0c3b6.sh000066400000000000000000000014711442763353300164610ustar00rootroot00000000000000if [ -z `which sh` ] || [ -z `which readlink` ] || [ -z `which grep` ] || [ -z `which echo` ] || [ -z `which mcookie` ] || [ ! -e /proc/self/fd/0 ]; then exit 125; fi ${PROOT} readlink /proc/self | grep -E "^[[:digit:]]+$" ! ${PROOT} readlink /proc/self/.. [ $? -eq 0 ] ${PROOT} readlink /proc/self/../self | grep -E "^[[:digit:]]+$" ${PROOT} sh -c 'echo "OK" | readlink /proc/self/fd/0' | grep -E "^pipe:\[[[:digit:]]+\]$" ! ${PROOT} sh -c 'echo "OK" | readlink /proc/self/fd/0/' [ $? -eq 0 ] ! ${PROOT} sh -c 'echo "OK" | readlink /proc/self/fd/0/..' [ $? -eq 0 ] ! ${PROOT} sh -c 'echo "OK" | readlink /proc/self/fd/0/../0' [ $? -eq 0 ] ${PROOT} sh -c 'echo "echo OK" | sh /proc/self/fd/0' | grep ^OK$ TMP=/tmp/$(mcookie) ${PROOT} sh -c "exec 6<>${TMP}; readlink /proc/self/fd/6" | grep ^${TMP} rm -f ${TMP} proot-5.4.0/test/test-docker.sh000066400000000000000000000012161442763353300164340ustar00rootroot00000000000000#!/bin/sh set -eu # Test can run using either builder BUILDER="docker" if [ -z "$(command -v ${BUILDER})" ] || ! docker run hello-world > /dev/null 2>&1; then BUILDER="img" if [ -z "$(command -v ${BUILDER})" ]; then exit 125 fi fi # Export for use in subshell export BUILDER export DOCKER_IMAGE="proot-me/proot" TMP=$(mcookie) TMP="/tmp/${TMP}" # Generate image list find . -name 'Dockerfile' -exec sh -c ' TAG=$(echo "${1}" | ../util/parse-docker-tag.awk) echo ${BUILDER} build -t ${DOCKER_IMAGE}:${TAG} -f ${1} .. ' sh {} \; | sort -k1 > "${TMP}" cat "${TMP}" # Build all images sh "${TMP}" rm "${TMP}" unset BUILDER proot-5.4.0/test/test-e87b34ae.c000066400000000000000000000012031442763353300162130ustar00rootroot00000000000000#include /* syscall(2), fork(2), usleep(3), */ #include /* perror(3), printf(3), */ #include /* PATH_MAX, */ #include /* exit(3), */ #include /* SYS_readlink, SYS_getcwd, */ #include /* errno, */ int main(void) { pid_t pid; int status; int i; for (i = 0; i < 1000; i++) { pid = fork(); switch (pid) { case -1: /* Is the maximum number of processes * reached? */ if (errno == EAGAIN) break; perror("fork()"); exit(EXIT_FAILURE); case 0: /* child */ exit(EXIT_SUCCESS); default: /* parent */ break; } } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-e87ca6ca.sh000066400000000000000000000005351442763353300164710ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which cp` ] || [ -z `which true` ] || [ -z `which setcap` ] || [ -z `which rm` ]; then exit 125; fi if [ `id -u` -eq 0 ]; then exit 125; fi TMP=/tmp/$(mcookie) cp $(which true) ${TMP} ! ${PROOT} -i 123:456 setcap cap_setuid+ep ${TMP} [ $? -eq 0 ] ${PROOT} -0 setcap cap_setuid+ep ${TMP} rm -f ${TMP} proot-5.4.0/test/test-e940896f.sh000066400000000000000000000011021442763353300162550ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/readdir ] || [ -z `which mcookie` ] || [ -z `which rm` ] || [ -z `which mkdir` ] || [ -z `which chmod` ] || [ -z `which rm` ]; then exit 125; fi TMP1=/tmp/$(mcookie) TMP2=${TMP1}/$(mcookie)/$(mcookie) rm -fr ${TMP1} rm -fr ${ROOTFS}/${TMP1} mkdir -p ${TMP2} mkdir -p ${ROOTFS}/${TMP1} chmod -w ${ROOTFS}/${TMP1} cd ${TMP2} ${PROOT} -r ${ROOTFS} -b . readdir ${TMP1} ${PROOT} -r ${ROOTFS} -b . readdir ${TMP2} ${PROOT} -r ${ROOTFS} -b . readdir ${TMP2}/.. ${PROOT} -r ${ROOTFS} -b . readdir ${TMP2}/../.. rm -fr ${TMP1} rm -fr ${ROOTFS}/${TMP1} proot-5.4.0/test/test-e99993c8.sh000066400000000000000000000005301442763353300162710ustar00rootroot00000000000000if [ -z `which uname` ] || [ -z `which grep` ]; then exit 125; fi kver=$(uname -r) LONG_RELEASE=0123456789012345678901234567890123456789012345678901234567890123456789 ${PROOT} -k $kver-3.33.333 uname -r | grep ^.*-3\.33\.333$ ${PROOT} -k ${LONG_RELEASE} uname -r | grep ^0123456789012345678901234567890123456789012345678901234567890123$ proot-5.4.0/test/test-eddeba0e.sh000066400000000000000000000001431442763353300166140ustar00rootroot00000000000000if ! `which pwd` -P || [ -z `which grep` ]; then exit 125; fi ${PROOT} pwd -P | grep "^$PWD$" proot-5.4.0/test/test-f7089d4f.sh000066400000000000000000000002271442763353300163410ustar00rootroot00000000000000if [ -z `which timeout` ] || [ -z `which msgmerge` ] || [ ! -e /dev/null ]; then exit 125; fi timeout 5s ${PROOT} msgmerge -q /dev/null /dev/null proot-5.4.0/test/test-fa205b56.c000066400000000000000000000010671442763353300161330ustar00rootroot00000000000000#include #include #include #include #define NUM_THREADS 5 void *exec(void *id) { char *const argv[] = { "true", NULL }; if ((long) id == NUM_THREADS - 1) execve("/usr/bin/true", argv, NULL); else sleep(50); pthread_exit(NULL); } int main() { pthread_t threads[NUM_THREADS]; int status; long i; exit(125); /* NYI */ for(i = 0; i < NUM_THREADS ; i++) { status = pthread_create(&threads[i], NULL, exec, (void *) i); if (status) exit(EXIT_FAILURE); sleep(1); } sleep(50); exit(EXIT_FAILURE); } proot-5.4.0/test/test-fbca9cc2.sh000066400000000000000000000004331442763353300165410ustar00rootroot00000000000000if [ -z `which strace` ] || [ -z `which true` ] || [ -z `which grep` ] || [ -z `which wc` ]; then exit 125; fi ${PROOT} strace -e trace=execve true 2>&1 | grep '^execve.*= 0$' RESULT=$(${PROOT} strace -e trace=execve true 2>&1 | grep '^execve' | wc -l) test "${RESULT}" = "1" proot-5.4.0/test/test-fdf487a0.c000066400000000000000000000010761442763353300162240ustar00rootroot00000000000000#include #include #include #include #include #include int main() { int fd; fd = openat(0, "foo", O_RDONLY); if (fd >= 0 || errno != ENOTDIR) { printf("1. %d %d\n", fd, (int) errno); exit(EXIT_FAILURE); } fd = openat(0, "", O_RDONLY); if (fd >= 0 || errno != ENOENT) { printf("2. %d %d\n", fd, (int) errno); exit(EXIT_FAILURE); } fd = openat(0, NULL, O_RDONLY); if (fd >= 0 || errno != EFAULT) { printf("3. %d %d\n", fd, (int) errno); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-ffffffff.sh000066400000000000000000000004371442763353300167300ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which touch` ] || [ -z `which stat` ] || [ -z `which grep` ] || [ -z `which rm` ]; then exit 125; fi TMP=/tmp/$(mcookie) touch ${TMP} ${PROOT} -0 stat -c %u:%g ${TMP} | grep 0:0 ${PROOT} -i 123:456 stat -c %u:%g ${TMP} | grep 123:456 rm ${TMP} proot-5.4.0/test/test-gdb-ptrace.sh000066400000000000000000000006561442763353300172040ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/gdb-ptrace-test ] || [ ! -x ${ROOTFS}/bin/gdb-ptrace-test-signal ]; then exit 125; fi ${ROOTFS}/bin/gdb-ptrace-test ${ROOTFS}/bin/gdb-ptrace-test 1 ${PROOT} ${ROOTFS}/bin/gdb-ptrace-test ${PROOT} ${ROOTFS}/bin/gdb-ptrace-test 1 ${ROOTFS}/bin/gdb-ptrace-test-signal ${ROOTFS}/bin/gdb-ptrace-test-signal 1 ${PROOT} ${ROOTFS}/bin/gdb-ptrace-test-signal ${PROOT} ${ROOTFS}/bin/gdb-ptrace-test-signal 1 proot-5.4.0/test/test-getres32.sh000066400000000000000000000020411442763353300166200ustar00rootroot00000000000000if [ -z `which id` ] || [ -z `which grep` ] || [ ! -x ${ROOTFS}/bin/exec-m32-suid ] || [ ! -x ${ROOTFS}/bin/exec-m32-sgid ] || [ ! -x ${ROOTFS}/bin/getresuid ] || [ ! -x ${ROOTFS}/bin/getresgid ]; then exit 125 fi MY_UID=$(id -u) MY_GID=$(id -g) ${PROOT} -i ${MY_UID}:${MY_GID} -R ${ROOTFS} exec-m32-suid /bin/getresuid | grep "^${MY_UID} 0 0$" ${PROOT} -i ${MY_UID}:${MY_GID} -R ${ROOTFS} exec-m32-suid /bin/getresgid | grep "^${MY_GID} ${MY_GID} ${MY_GID}$" ${PROOT} -i ${MY_UID}:${MY_GID} -R ${ROOTFS} exec-m32-sgid /bin/getresuid | grep "^${MY_UID} ${MY_UID} ${MY_UID}$" ${PROOT} -i ${MY_UID}:${MY_GID} -R ${ROOTFS} exec-m32-sgid /bin/getresgid | grep "^${MY_GID} 0 0$" ${PROOT} -R ${ROOTFS} exec-m32-suid /bin/getresuid | grep "^${MY_UID} ${MY_UID} ${MY_UID}$" ${PROOT} -R ${ROOTFS} exec-m32-suid /bin/getresgid | grep "^${MY_GID} ${MY_GID} ${MY_GID}$" ${PROOT} -R ${ROOTFS} exec-m32-sgid /bin/getresuid | grep "^${MY_UID} ${MY_UID} ${MY_UID}$" ${PROOT} -R ${ROOTFS} exec-m32-sgid /bin/getresgid | grep "^${MY_GID} ${MY_GID} ${MY_GID}$" proot-5.4.0/test/test-getresid.sh000066400000000000000000000017711442763353300170010ustar00rootroot00000000000000if [ -z `which id` ] || [ -z `which grep` ] || [ ! -x ${ROOTFS}/bin/exec-suid ] || [ ! -x ${ROOTFS}/bin/exec-sgid ] || [ ! -x ${ROOTFS}/bin/getresuid ] || [ ! -x ${ROOTFS}/bin/getresgid ]; then exit 125 fi MY_UID=$(id -u) MY_GID=$(id -g) ${PROOT} -i ${MY_UID}:${MY_GID} -R ${ROOTFS} exec-suid /bin/getresuid | grep "^${MY_UID} 0 0$" ${PROOT} -i ${MY_UID}:${MY_GID} -R ${ROOTFS} exec-suid /bin/getresgid | grep "^${MY_GID} ${MY_GID} ${MY_GID}$" ${PROOT} -i ${MY_UID}:${MY_GID} -R ${ROOTFS} exec-sgid /bin/getresuid | grep "^${MY_UID} ${MY_UID} ${MY_UID}$" ${PROOT} -i ${MY_UID}:${MY_GID} -R ${ROOTFS} exec-sgid /bin/getresgid | grep "^${MY_GID} 0 0$" ${PROOT} -R ${ROOTFS} exec-suid /bin/getresuid | grep "^${MY_UID} ${MY_UID} ${MY_UID}$" ${PROOT} -R ${ROOTFS} exec-suid /bin/getresgid | grep "^${MY_GID} ${MY_GID} ${MY_GID}$" ${PROOT} -R ${ROOTFS} exec-sgid /bin/getresuid | grep "^${MY_UID} ${MY_UID} ${MY_UID}$" ${PROOT} -R ${ROOTFS} exec-sgid /bin/getresgid | grep "^${MY_GID} ${MY_GID} ${MY_GID}$" proot-5.4.0/test/test-gggggggg.sh000066400000000000000000000010211442763353300167260ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which env` ] || [ -z `which mkdir` ] || [ -z `which rm` ] || [ ! -x ${ROOTFS}/bin/readdir ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir ${TMP} ! env PROOT_DONT_POLLUTE_ROOTFS=1 ${PROOT} -b /bin:${TMP}/dont/create ${ROOTFS}/bin/readdir ${TMP} | grep -w dont [ $? -eq 0 ] env PROOT_DONT_POLLUTE_ROOTFS=1 ${PROOT} -b /bin:${TMP}/dont/create test -e ${TMP}/dont ${PROOT} -b /bin:${TMP}/dont/create test -e ${TMP}/dont ! test -e ${TMP}/dont [ $? -eq 0 ] chmod +rx -R ${TMP} rm -fr ${TMP} proot-5.4.0/test/test-hhhhhhhh.sh000066400000000000000000000011271442763353300167450ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/true ] || [ -h /bin/true ] || [ -h /bin ] || [ -z `which mcookie` ] || [ -z `which true` ] || [ -z `which mkdir` ] || [ -z `which ln` ] || [ -z `which rm` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir -p ${ROOTFS}/${TMP} A=$(mcookie) B=$(mcookie) ! ln -s /bin/true -r ${ROOTFS}/${TMP}/${A} ! ln -s ${TMP}/${A} -r ${ROOTFS}/${TMP}/${B} if [ ! -e ${ROOTFS}/${TMP}/${A} ]; then exit 125; fi env PATH=${TMP} ${PROOT} -r ${ROOTFS} ${B} rm -f ${TMP}/${B} # just in case it also exists in the host env. ${PROOT} -r ${ROOTFS} /${TMP}/${B} rm -fr ${ROOTFS}/${TMP} proot-5.4.0/test/test-iiiiiiii.c000066400000000000000000000022021442763353300165600ustar00rootroot00000000000000#include #include #include #include #include int main(void) { int result; int status; char *path; int fd; path = strdup("/tmp/proot-test-iiiiiiii-XXXXXX"); if (path == NULL) { result = 125; goto end; } mktemp(path); if (path[0] == '\0') { result = 125; goto end; } status = symlink("/this_shall_not_exist_outside_proot", path); if (status < 0) { result = 125; goto end; } /* For faccessat(2) and fchmodat(2) syscalls, the fourth * parameter is *not* used by the kernel, only the libc uses * it. As a consequence, PRoot shall ignore this flag. * * To be sure this parameter is really ignored by PRoot, we * set it to NOFOLLOW when performing a direct faccessat(2) to * a symlink which is broken from the host point-of-view, but * valid from a guest point-of-view. When PRoot does not * honor this flag, the faccessat(2) is performed against the * referee anyway. */ status = syscall(SYS_faccessat, AT_FDCWD, path, X_OK, AT_SYMLINK_NOFOLLOW); result = (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); end: (void) unlink(path); exit(result); } proot-5.4.0/test/test-killexit.sh000066400000000000000000000010601442763353300170070ustar00rootroot00000000000000if [ -z `which setsid` ]; then exit 125 fi cleanup() { _code=$? trap - INT TERM EXIT [ ! -f "${tmpfile-}" ] || rm -f "$tmpfile" exit $_code } trap cleanup INT TERM EXIT tmpfile=`mktemp` # Check that kill on exit option is recognized ${PROOT} --kill-on-exit true # Check that detached sleep does not block proot # I.e. in the file we must have "success" first, not "fail" ${PROOT} --kill-on-exit sh -c "setsid sh -c \"sleep 2; echo fail >>$tmpfile\" &" echo "success" >>$tmpfile read status_ <$tmpfile [ "$status_" = success ] || exit 1 proot-5.4.0/test/test-kkkkkkkk.c000066400000000000000000000064411442763353300166110ustar00rootroot00000000000000#include /* syscall(2), sysconf(3), */ #include /* SYS_brk, */ #include /* puts(3), */ #include /* exit(3), EXIT_*, */ #include /* uint*_t, */ #include /* mmap(2), MAP_*, */ #include /* memset(3), */ int main() { int exit_status = EXIT_SUCCESS; uint8_t *current_brk = 0; uint8_t *initial_brk; uint8_t *new_brk; uint8_t *old_brk; int failure = 0; long page_size; int i; page_size = sysconf(_SC_PAGE_SIZE); if (page_size <= 0) return 125; void test_brk(int increment, int expected_result) { new_brk = (uint8_t *) syscall(SYS_brk, current_brk + increment); if ((new_brk == current_brk) == expected_result) failure = 1; current_brk = (uint8_t *) syscall(SYS_brk, 0); } void test_result() { if (!failure) puts("OK"); else { puts("failure"); exit_status = EXIT_FAILURE; } } void test_title(const char *title) { failure = 0; printf("%-45s : ", title); fflush(stdout); } test_title("Initialization"); test_brk(0, 1); initial_brk = current_brk; test_result(); test_title("Don't set the \"brk\" below its initial value"); test_brk(page_size, 1); // Try to shrink the size of the heap by more than it's current size // and it should fail. test_brk(-1000 * page_size, 0); test_brk(-page_size, 1); test_result(); test_title("Don't overlap \"brk\" pages"); test_brk(page_size, 1); test_brk(page_size, 1); test_result(); /* Preparation for the test "Re-allocated heap is initialized". */ old_brk = current_brk - page_size; memset(old_brk, 0xFF, page_size); test_title("Don't allocate the same \"brk\" page twice"); test_brk(-page_size, 1); test_brk(page_size, 1); test_result(); test_title("Re-allocated \"brk\" pages are initialized"); for (i = 0; i < page_size; i++) { if (old_brk[i] != 0) { printf("(index = %d, value = 0x%x) ", i, old_brk[i]); failure = 1; break; } } test_result(); #if 0 test_title("Don't allocate \"brk\" pages over \"mmap\" pages"); new_brk = mmap(current_brk, page_size / 2, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); if (new_brk == (void *) -1) puts("unknown"); else { test_brk(page_size, 0); test_result(); } #endif test_title("All \"brk\" pages are writable (please wait)"); #if 0 if (munmap(current_brk, page_size / 2) != 0) puts("unknown"); else { #endif while (current_brk - initial_brk < 512*1024*1024UL) { old_brk = current_brk; test_brk(page_size, -1); if (old_brk == current_brk) break; for (i = 0; i < page_size; i++) old_brk[i] = 0xAA; } test_result(); #if 0 } #endif test_title("Maximum size of the heap >= 1MB"); failure = (current_brk - initial_brk) < 1024 * 1024; test_result(); test_title("All \"brk\" pages are cleared (please wait)"); test_brk(initial_brk - current_brk, 1); if (current_brk != initial_brk) puts("unknown"); else { while (current_brk - initial_brk < 1024*1024*1024UL) { old_brk = current_brk; test_brk(3 * page_size, -1); if (old_brk == current_brk) break; for (i = 0; i < 3 * page_size; i++) { if (old_brk[i] != 0) { printf("(index = %d, value = 0x%x) ", i, old_brk[i]); failure = 1; goto end; } } } end: test_result(); } exit(exit_status); } proot-5.4.0/test/test-mmmmmmmm.sh000066400000000000000000000004141442763353300170130ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which rmdir` ] || [ -z `which mkdir` ]; then exit 125; fi TMP=$(mcookie) cd /tmp ${PROOT} mkdir ./${TMP} ${PROOT} rmdir ./${TMP} ${PROOT} mkdir ${TMP}/ ${PROOT} rmdir ${TMP}/ ${PROOT} mkdir ./${TMP}/ ${PROOT} rmdir ./${TMP}/ proot-5.4.0/test/test-nnnnnnnn.c000066400000000000000000000032511442763353300166350ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include int main() { const char *sockname = "/test-nnnnnnnn-socket"; struct sockaddr_un sockaddr; socklen_t socklen; mode_t mask; int status; int fd; /* root can create $hostfs/test-nnnnnnnn-socket. */ if (getuid() == 0) return 125; /* clean-up previous socket. */ (void) unlink(sockname); fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); exit(EXIT_FAILURE); } bzero(&sockaddr, sizeof(sockaddr)); sockaddr.sun_family = AF_UNIX; strcpy(sockaddr.sun_path, sockname); mask = umask(S_IXUSR|S_IXGRP|S_IRWXO); status = bind(fd, (const struct sockaddr *) &sockaddr, SUN_LEN(&sockaddr)); if (status < 0) { perror("bind"); exit(EXIT_FAILURE); } umask(mask); status = listen(fd, 50); if (status < 0) { perror("listen"); exit(EXIT_FAILURE); } bzero(&sockaddr, sizeof(sockaddr)); socklen = sizeof(sockaddr); status = getsockname(fd, (struct sockaddr *) &sockaddr, &socklen); if (status < 0) { perror("getsockname"); exit(EXIT_FAILURE); } if (socklen != SUN_LEN(&sockaddr) + 1) { fprintf(stderr, "socklen: %d != %d + 1\n", socklen, SUN_LEN(&sockaddr)); exit(EXIT_FAILURE); } if (sockaddr.sun_family != AF_UNIX) { fprintf(stderr, "! AF_UNIX\n"); exit(EXIT_FAILURE); } if (socklen == sizeof(sockaddr) + 1) status = strncmp(sockaddr.sun_path, sockname, sizeof(sockaddr.sun_path)); else status = strcmp(sockaddr.sun_path, sockname); if (status != 0) { fprintf(stderr, "! %s\n", sockname); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-oooooooo.c000066400000000000000000000011551442763353300166460ustar00rootroot00000000000000#include #include #include #include int main(void) { int i; for (i = 0; i < 999; i++) { int status; int fds[2]; pid_t pid; status = pipe(fds); if (status < 0) { perror("pipe"); break; } pid = fork(); switch (pid) { case -1: perror("fork"); exit(EXIT_FAILURE); case 0: /* child */ do status = write(fds[1], "!", 1); while (status > 0); perror("write"); exit(EXIT_FAILURE); default: /* parent */ status = kill(pid, SIGKILL); if (status < 0) { perror("kill"); exit(EXIT_FAILURE); } } } exit(EXIT_SUCCESS); } proot-5.4.0/test/test-pppppppp.sh000066400000000000000000000004711442763353300170460ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which true` ] || [ -z `which mkdir` ]|| [ -z `which env` ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir -p ${TMP}/true ! ${PROOT} true if [ $? -eq 0 ]; then exit 125; fi env PATH=${TMP}:${PATH} ${PROOT} true env PATH=${TMP}:${PATH} ${PROOT} env true rm -fr ${TMP} proot-5.4.0/test/test-proocare.sh000066400000000000000000000007711442763353300170040ustar00rootroot00000000000000if [ ! -e /boot ] || [ -z `which mcookie` ] || [ -z `which ln` ] || [ -z `which mkdir` ] || [ -z `which rm` ]; then exit 125; fi if [ ! -e $CARE ]; then exit 125; fi unset PROOT TMP_PROOT=/tmp/$(mcookie) TMP_OUTPUT=/tmp/$(mcookie)/ ln -s ${CARE} ${TMP_PROOT} mkdir ${TMP_OUTPUT} ${TMP_PROOT} -b /boot:/toob ${CARE} -o ${TMP_OUTPUT}/ ls /toob test -e ${TMP_OUTPUT}/rootfs/toob ! test -e ${TMP_OUTPUT}/rootfs/boot [ $? -eq 0 ] ${TMP_OUTPUT}/re-execute.sh rm -fr ${TMP_PROOT} ${TMP_OUTPUT} proot-5.4.0/test/test-ptrace-exec-trap.sh000066400000000000000000000013461442763353300203350ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which cmp` ] || [ -z `which rm` ] || [ ! -x ${ROOTFS}/bin/ptrace-3 ] || [ ! -x ${ROOTFS}/bin/true ]; then exit 125; fi TMP1=/tmp/$(mcookie) TMP2=/tmp/$(mcookie) ${ROOTFS}/bin/ptrace-3 ${ROOTFS}/bin/true 2>&1 >${TMP1} ${PROOT} ${ROOTFS}/bin/ptrace-3 ${ROOTFS}/bin/true 2>&1 >${TMP2} cmp ${TMP1} ${TMP2} PTRACER_BEHAVIOR_1=1 ${ROOTFS}/bin/ptrace-3 ${ROOTFS}/bin/true 2>&1 >${TMP1} PTRACER_BEHAVIOR_1=1 ${PROOT} ${ROOTFS}/bin/ptrace-3 ${ROOTFS}/bin/true 2>&1 >${TMP2} cmp ${TMP1} ${TMP2} PTRACER_BEHAVIOR_2=1 ${ROOTFS}/bin/ptrace-3 ${ROOTFS}/bin/true 2>&1 >${TMP1} PTRACER_BEHAVIOR_2=1 ${PROOT} ${ROOTFS}/bin/ptrace-3 ${ROOTFS}/bin/true 2>&1 >${TMP2} cmp ${TMP1} ${TMP2} rm -f ${TMP1} ${TMP2} proot-5.4.0/test/test-ptrace00.c000066400000000000000000000035041442763353300164150ustar00rootroot00000000000000/* Extracted from strace-4.8/strace.c:test_ptrace_setoptions_for_all. */ #include #include #include #include #include #include #include #include #include int main(void) { bool does_work = false; int status; pid_t pid; switch(pid = fork()) { case -1: perror("fork"); exit(EXIT_FAILURE); case 0: status = ptrace(PTRACE_TRACEME, 0, 0, 0); if (status < 0) { perror("ptrace(PTRACE_TRACEME)"); exit(EXIT_FAILURE); } kill(getpid(), SIGSTOP); exit(EXIT_SUCCESS); default: break; } while (1) { fprintf(stderr, ">>> pid = wait(&status)\n"); pid = wait(&status); if (pid < 0) { perror("wait"); exit(EXIT_FAILURE); } else if (WIFEXITED(status)) { if (WEXITSTATUS(status) == 0) { fprintf(stderr, ">>> EXITSTATUS(status) == 0\n"); break; } fprintf(stderr, "WEXITSTATUS != 0\n"); exit(EXIT_FAILURE); } else if (WIFSIGNALED(status)) { fprintf(stderr, "WIFSIGNALED\n"); exit(EXIT_FAILURE); } else if (!WIFSTOPPED(status)) { fprintf(stderr, "!WIFSTOPPED\n"); exit(EXIT_FAILURE); } else if (WSTOPSIG(status) == SIGSTOP) { fprintf(stderr, ">>> ptrace(PTRACE_SETOPTIONS, ...)\n"); status = ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC); if (status < 0) { perror("ptrace(PTRACE_SETOPTIONS)"); exit(EXIT_FAILURE); } } else if (WSTOPSIG(status) == (SIGTRAP | 0x80)) { fprintf(stderr, ">>> does_work = true\n"); does_work = true; } fprintf(stderr, ">>> ptrace(PTRACE_SYSCALL, ...)\n"); status = ptrace(PTRACE_SYSCALL, pid, 0, 0); if (status < 0) { perror("ptrace(PTRACE_SYSCALL)"); exit(EXIT_FAILURE); } } fprintf(stderr, ">>> exit(...)\n"); exit(does_work ? EXIT_SUCCESS : EXIT_FAILURE); } proot-5.4.0/test/test-ptrace01.c000066400000000000000000000016211442763353300164140ustar00rootroot00000000000000#include /* fork(2), */ #include /* perror(3), fprintf(3), */ #include /* exit(3), */ #include /* ptrace(2), */ #include /* waitpid(2), */ #include /* waitpid(2), */ int main(void) { int child_status, status; pid_t pid; pid = fork(); switch (pid) { case -1: perror("fork()"); exit(EXIT_FAILURE); case 0: /* child */ status = ptrace(PTRACE_TRACEME, 0, NULL, NULL); if (status < 0) { perror("ptrace(TRACEME)"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); default: /* parent */ status = waitpid(pid, &child_status, 0); if (status < 0) { perror("waitpid()"); exit(EXIT_FAILURE); } if (!WIFEXITED(child_status) || WEXITSTATUS(child_status) != 0) { perror("unexpected child status\n"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } /* Unreachable. */ exit(EXIT_FAILURE); } proot-5.4.0/test/test-python01.sh000066400000000000000000000054271442763353300166570ustar00rootroot00000000000000#!/bin/sh # Test for Python Extension for PRoot set -eu # Check for test dependencies for cmd in mcookie cat grep rm find; do if ! command -v "${cmd}" > /dev/null; then exit 125 fi done # Check for PRoot binary if [ ! -e "${PROOT}" ]; then exit 125 fi # Check for python flag if ! "${PROOT}" --help | grep -- -p | grep string; then exit 125 fi TMP="$(mcookie)_hide.py" # The following python script will hide all files cat > "/tmp/${TMP}" < 0: addr = peek_reg(tracee, CURRENT, SYSARG_2) dents = get_getdents(tracee, res, addr) dents = filter_dents(dents) put_dents(tracee, addr, dents) return 0 EOF # If script passes result from find will be empty if [ "$(env PROOT_NO_SECCOMP=1 "${PROOT}" -p "/tmp/${TMP}" find . -maxdepth 1 -type f)" ]; then exit 1 fi rm -rf "/tmp/${TMP}" "/tmp/${TMP}c" proot-5.4.0/test/test-rrrrrrrr.sh000066400000000000000000000003271442763353300170660ustar00rootroot00000000000000if [ ! -x ${ROOTFS}/bin/readlink ] || [ -z `which realpath` ] || [ -z `which grep` ]; then exit 125; fi RESULT=$(realpath ${ROOTFS}) ${PROOT} -b /proc -r ${ROOTFS} readlink /proc/self/root | grep ^${RESULT}$ proot-5.4.0/test/test-socket01.sh000066400000000000000000000000671442763353300166210ustar00rootroot00000000000000#!/bin/sh cd sockets || exit 125 sh testtcpsocket.sh proot-5.4.0/test/test-socket02.sh000066400000000000000000000002411442763353300166140ustar00rootroot00000000000000#!/bin/sh # Skip test when run on Travis, as it doesn't support IPv6 if [ "${TRAVIS}" ]; then exit 125; fi cd sockets || exit 125 sh testtcpsocketipv6.sh proot-5.4.0/test/test-socket03.sh000066400000000000000000000000731442763353300166200ustar00rootroot00000000000000#!/bin/sh cd sockets || exit 125 sh testtcpsocketauto.sh proot-5.4.0/test/test-ssssssss.c000066400000000000000000000022051442763353300167030ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include int main() { struct sockaddr_un sockaddr; socklen_t socklen; char *sockname; mode_t mask; int status; int fd; sockname = strdup("proot-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxXXXXXX"); if (sockname == NULL) return 125; (void) mktemp(sockname); if (sockname[0] == '\0') return 125; fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); exit(EXIT_FAILURE); } bzero(&sockaddr, sizeof(sockaddr)); sockaddr.sun_family = AF_UNIX; assert(strlen(sockname) == sizeof(sockaddr.sun_path)); memcpy(sockaddr.sun_path, sockname, sizeof(sockaddr.sun_path)); chdir("/tmp"); (void) unlink(sockaddr.sun_path); status = bind(fd, (const struct sockaddr *) &sockaddr, sizeof(sockaddr)); if (status < 0) { perror("bind"); exit(EXIT_FAILURE); } (void) unlink(sockaddr.sun_path); exit(EXIT_SUCCESS); } proot-5.4.0/test/test-sysexit.c000066400000000000000000000017201442763353300165050ustar00rootroot00000000000000/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- */ #include #include #include /* wait(2), */ #include #include /* Related to github issue #106. * Test if sysexit handlers execute, using uname handling * in the kompat extension. The test case is meant to be * run with "-k 3.4242XX" cmdline argument. * The bug could occur during the first traced syscall, * before seccomp got enabled. * However there was some kind of a random factor there, * so we fork out many processes to make it likely that at least * some would hit this problem. */ int main() { int status; struct utsname s; for (int i = 0; i < 5; i++) { fork(); } uname(&s); int child_status; while ((status = wait(&child_status)) >= 0) { if (!WIFEXITED(child_status) || (WEXITSTATUS(child_status) == EXIT_FAILURE)) exit(EXIT_FAILURE); } if (strcmp("3.4242XX", s.release) == 0) { exit(EXIT_SUCCESS); } exit(EXIT_FAILURE); } proot-5.4.0/test/test-tempdire.sh000066400000000000000000000007231442763353300170000ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which cp` ] || [ -z `which rm` ] || [ -z `which chmod` ] || [ -z `which env` ] || [ -z `which true` ]; then exit 125 fi TMP=/tmp/$(mcookie) mkdir ${TMP} cp $(which true) ${TMP}/ if [ ! -x ${TMP}/true ]; then rm -fr ${TMP} exit 125 fi rm -f ${TMP}/true chmod -w ${TMP} ! env PROOT_TMP_DIR=${TMP} ${PROOT} true [ $? -eq 0 ] chmod +w ${TMP} env PROOT_TMP_DIR=${TMP} ${PROOT} true rm -fr ${TMP} proot-5.4.0/test/test-wwwwwwww.sh000066400000000000000000000004101442763353300171270ustar00rootroot00000000000000if [ -z `which mcookie` ] || [ -z `which mkdir` ] || [ -z `which rm` ] || [ ! -x ${ROOTFS}/bin/pwd ]; then exit 125; fi TMP=/tmp/$(mcookie) mkdir ${TMP} cd ${TMP} ${PROOT} sh -c "cd ..; rm -r ${TMP}; mkdir ${TMP}; cd ${TMP}; ${ROOTFS}/bin/pwd" rm -fr ${TMP} proot-5.4.0/test/test-xxxxxxxx.c000066400000000000000000000017141442763353300167570ustar00rootroot00000000000000#include #include #include #include #include extern char *environ[]; #define CONTENT "this isn't an executable" int main(void) { char * const argv[] = { "argv0", "argv1", "argv2", NULL }; char tmp_name[] = "/tmp/proot-XXXXXX"; int status; int fd; status = execve("/tmp", argv, environ); if (errno != EACCES) { perror("execve (1)"); exit(EXIT_FAILURE); } fd = mkstemp(tmp_name); if (fd < 0) { perror("mkstemp"); exit(EXIT_FAILURE); } status = write(fd, CONTENT, sizeof(CONTENT)); if (status != sizeof(CONTENT)) { perror("write"); exit(EXIT_FAILURE); } close(fd); status = chmod(tmp_name, 0700); if (status < 0) { perror("chmod"); exit(EXIT_FAILURE); } status = execve(tmp_name, argv, environ); if (errno != ENOEXEC) { perror("execve (2)"); exit(EXIT_FAILURE); } printf("Check the stack integrity: %F + %F\n", (double) status, (double) errno); exit(EXIT_SUCCESS); } proot-5.4.0/test/test-yyyyyyyy.sh000066400000000000000000000007331442763353300171570ustar00rootroot00000000000000if [ -z `which id` ] || [ -z `which uname` ] || [ -z `which grep` ]; then exit 125; fi ${PROOT} -v -1 -i $(id -u):$(id -g) -0 id -u | grep "^0$" ${PROOT} -v -1 -0 -i $(id -u):$(id -g) id -u | grep "^$(id -u)$" ${PROOT} -v -1 -0 -i $(id -u):$(id -g) -0 id -u | grep "^0$" ${PROOT} -v -1 -k $(uname -r) -k 5.0 uname -r | grep "^5.0$" ${PROOT} -v -1 -k 5.0 -k $(uname -r) uname -r | grep "^$(uname -r)$" ${PROOT} -v -1 -k 5.0 -k $(uname -r) -k 5.0 uname -r | grep "^5.0$" proot-5.4.0/test/true.c000066400000000000000000000000351442763353300147750ustar00rootroot00000000000000int main(void) { return 0; } proot-5.4.0/test/vagrant/000077500000000000000000000000001442763353300153165ustar00rootroot00000000000000proot-5.4.0/test/vagrant/alpine/000077500000000000000000000000001442763353300165665ustar00rootroot00000000000000proot-5.4.0/test/vagrant/alpine/Vagrantfile000066400000000000000000000015471442763353300207620ustar00rootroot00000000000000Vagrant.configure("2") do |config| config.vm.box = "generic/alpine310" config.vm.synced_folder "../../..", "/usr/src/proot", type: "sshfs" config.vm.provider "virtualbox" do |vb| vb.memory = 1024 vb.cpus = 2 vb.linked_clone = true if Vagrant::VERSION =~ /^1.8/ end config.vm.provision "shell", inline: <<-SHELL sudo apk update sudo apk add bash \ bsd-compat-headers \ clang \ clang-analyzer \ coreutils \ gcc \ git \ grep \ libarchive-dev \ linux-headers \ lzo \ make \ mcookie \ musl-dev \ python2-dev \ swig \ talloc-dev \ uthash-dev SHELL end proot-5.4.0/test/vagrant/centos/000077500000000000000000000000001442763353300166115ustar00rootroot00000000000000proot-5.4.0/test/vagrant/centos/Vagrantfile000066400000000000000000000015121442763353300207750ustar00rootroot00000000000000Vagrant.configure("2") do |config| config.vm.box = "centos/7" config.vm.synced_folder "../../..", "/usr/src/proot", type: "sshfs" config.vm.provider "virtualbox" do |vb| vb.memory = 1024 vb.cpus = 2 vb.linked_clone = true if Vagrant::VERSION =~ /^1.8/ end config.vm.provision "shell", inline: <<-SHELL sudo yum update -y sudo yum install -y yum-utils sudo yum-config-manager --enable extras sudo yum makecache sudo yum group install -y 'Development Tools' sudo yum install -y clang \ clang-analyzer \ git \ libarchive-devel \ libtalloc-devel \ python-devel \ strace \ swig \ uthash-devel SHELL end proot-5.4.0/test/vagrant/debian/000077500000000000000000000000001442763353300165405ustar00rootroot00000000000000proot-5.4.0/test/vagrant/debian/Vagrantfile000066400000000000000000000015111442763353300207230ustar00rootroot00000000000000Vagrant.configure("2") do |config| config.vm.box = "debian/stretch64" config.vm.synced_folder "../../..", "/usr/src/proot", type: "sshfs" config.vm.provider "virtualbox" do |vb| vb.memory = 1024 vb.cpus = 2 vb.linked_clone = true if Vagrant::VERSION =~ /^1.8/ end config.vm.provision "shell", inline: <<-SHELL sudo apt update sudo apt install -y clang \ clang-tools-4.0 \ curl \ gcc \ gdb \ git \ lcov \ libarchive-dev \ libtalloc-dev \ make \ sloccount \ strace \ swig \ uthash-dev SHELL end proot-5.4.0/test/validation.mk000066400000000000000000000077541442763353300163540ustar00rootroot00000000000000libuv-version = 0.10.27 coreutils-version = 8.21 perl-version = 5.18.1 ltp-version = 20140422 opt-version = 20140422 gdb-version = 7.6.1 proot-version = 3.2.2 glibc-version = 2.17 libuv = libuv-$(libuv-version) coreutils = coreutils-$(coreutils-version) perl = perl-$(perl-version) ltp = ltp-$(ltp-version) opt = opt-$(opt-version) gdb = gdb-$(gdb-version) proot = PRoot-$(proot-version) glibc = glibc-$(glibc-version) testsuites = $(libuv) $(perl) $(ltp) $(opt) $(gdb) $(proot) $(coreutils) # $(glibc) too long. logs = $(testsuites:=.log) logs: $(logs) .PHONY: clean clean: rm -f $(logs) rm -fr $(testsuites) .PHONY: distclean distclean: clean rm -f $(testsuites:=.tar.*) ###################################################################### $(libuv).tar.gz: wget https://github.com/joyent/libuv/archive/v$(libuv-version).tar.gz -O $@ $(libuv).log: $(libuv).tar.gz rm -fr $(libuv) tar -xf $< $(MAKE) -C $(libuv) ($(MAKE) -C $(libuv) test 2>&1 || true) | tee $@ ###################################################################### $(coreutils).tar.xz: wget http://ftp.gnu.org/gnu/coreutils/$(coreutils).tar.xz $(coreutils).log: $(coreutils).tar.xz rm -fr $(coreutils) tar -xf $< cd $(coreutils) && ./configure $(MAKE) -C $(coreutils) ($(MAKE) -C $(coreutils) check || true) | tee $@ ###################################################################### $(perl).tar.gz: wget http://www.cpan.org/src/5.0/$(perl).tar.gz $(perl).log: $(perl).tar.gz rm -fr $(perl) tar -xf $< cd $(perl) && ./configure.gnu $(MAKE) -C $(perl) ($(MAKE) -C $(perl) check || true) | tee $@ ###################################################################### $(ltp).tar.gz: wget https://github.com/linux-test-project/ltp/archive/$(ltp-version).tar.gz -O $@ $(ltp).log: $(ltp).tar.gz rm -fr $(ltp) tar -xf $< $(MAKE) -C $(ltp) autotools cd $(ltp) && ./configure --prefix=$(PWD)/$(ltp)/install $(MAKE) -C $(ltp) $(MAKE) -C $(ltp) install sed -i s/^msgctl10/#/ $(ltp)/install/runtest/syscalls # is too CPU intensive sed -i s/^msgctl11/#/ $(ltp)/install/runtest/syscalls # is too CPU intensive ($(ltp)/install/runltp -f syscalls || true) | tee $@ ###################################################################### $(opt).log: $(ltp).tar.gz rm -fr $(opt) mkdir $(opt) tar -C $(opt) -xf $< $(ltp)/testcases/open_posix_testsuite $(MAKE) -C $(opt)/$(ltp)/testcases/open_posix_testsuite -j 1 # has broken // build ($(MAKE) -C $(opt)/$(ltp)/testcases/open_posix_testsuite -j 1 test || true) | tee $@ ###################################################################### $(gdb).tar.gz: wget http://ftp.gnu.org/gnu/gdb/$(gdb).tar.gz $(gdb).log: $(gdb).tar.gz rm -fr $(gdb) tar -xf $< cd $(gdb) && ./configure $(MAKE) -C $(gdb) rm -f $(gdb)/gdb/testsuite/gdb.base/attach-twice.exp # kills PRoot explicitly ($(MAKE) -C $(gdb)/gdb/testsuite check-gdb.base1 check-gdb.base2 check-gdb.server || true) | tee $@ ###################################################################### $(glibc).tar.xz: wget http://ftp.gnu.org/gnu/glibc/$(glibc).tar.xz -O $@ $(glibc).log: $(glibc).tar.xz rm -fr $(glibc) tar -xf $< mkdir -p $(glibc)/build/prefix cd $(glibc)/build && ../configure --prefix=$(PWD)/prefix $(MAKE) -C $(glibc)/build cp /usr/lib*/libgcc_s.so.1 $(glibc)/build cp /usr/lib*/libstdc++.so.6 $(glibc)/build sed -i s/tst-atexit3//g $(glibc)/dlfcn/Makefile # fails natively on Slack64-14.1 sed -i s/tst-cputimer1//g $(glibc)/rt/Makefile # fails natively on Slack64-14.1 sed -i 's/tests: check-abi/tests: /g' $(glibc)/Makerules # fails natively on Slack64-14.1 ($(MAKE) -j 1 -C $(glibc)/build check || true) | tee $@ # has broken // build ###################################################################### $(proot).tar.gz: wget https://github.com/cedric-vincent/proot/archive/v$(proot-version).tar.gz -O $@ $(proot).log: $(proot).tar.gz rm -fr $(proot) tar -xf $< $(MAKE) -C $(proot)/src ($(MAKE) -C $(proot)/test || true) | tee $@ proot-5.4.0/util/000077500000000000000000000000001442763353300136525ustar00rootroot00000000000000proot-5.4.0/util/coverage.sh000066400000000000000000000007361442763353300160070ustar00rootroot00000000000000#!/bin/sh set -eu # build configuration and loaders make -C src loader.elf loader-m32.elf build.h # compile with required flags CFLAGS='-Wall -Werror -O0 --coverage' LDFLAGS='-ltalloc -Wl,-z,noexecstack --coverage' make -eC src proot care # run testsuite make -C test || true # ignore failing tests (for now) # capture coverage data lcov --capture --directory src --output-file coverage.info # generate coverage report genhtml coverage.info --output-directory gcov-latest proot-5.4.0/util/dist.sh000066400000000000000000000016241442763353300151540ustar00rootroot00000000000000#!/bin/sh set -eu # create directories mkdir -p public/bin mkdir -p public/reports mkdir -p public/reports/lcov mkdir -p public/reports/scan-build # copy distributable artifacts cp dist/* public/bin/ # copy redirect template cp doc/template/redirect.html public/index.html # copy reports cp -R gcov-latest public/reports/ cp -R scan-build-latest public/reports/ # copy templates cp public/index.html public/reports/scan-build/ cp public/index.html public/reports/lcov/ # insert job id sed -i "s|{{ page.source_url }}|https://proot-me.github.io|g" public/index.html sed -i "s|{{ page.source_url }}|https://proot.gitlab.io/-/proot/-/jobs/${CI_JOB_ID}/artifacts/public/reports/scan-build-latest/index.html|g" public/reports/scan-build/index.html sed -i "s|{{ page.source_url }}|https://proot.gitlab.io/-/proot/-/jobs/${CI_JOB_ID}/artifacts/public/reports/gcov-latest/index.html|g" public/reports/lcov/index.html proot-5.4.0/util/parse-docker-tag.awk000077500000000000000000000004721442763353300175140ustar00rootroot00000000000000#!/usr/bin/awk -f # Convert Dockerfile path to image tag # input: ./docker/alpine/x86_64/gcc/Dockerfile # output: alpine-x86_64-gcc { gsub(/\//,"-"); # replace / with - split($0,a,"docker-"); # remove leading directories split(a[2],b,"-Docker"); # remove trailing Dockerfile print b[1] # print final tag } proot-5.4.0/util/site.sh000066400000000000000000000007651442763353300151620ustar00rootroot00000000000000#!/bin/sh set -eu # configure git git config --global user.name "PRoot" git config --global user.email "proot_me@googlegroups.com" # clone site repository git clone "https://${GITHUB_USER-}:${GITHUB_ACCESS_TOKEN-}@github.com/proot-me/proot-me.github.io" doc/public_html # compile site from documentation make -eC doc dist # navigate to output directory cd doc/public_html # add any changes git add . # commit changes git commit -m "publishing changes to GitHub Pages" # push to github git push