diff --git a/sros2_apparmor/CHANGELOG.rst b/sros2_apparmor/CHANGELOG.rst new file mode 100644 index 00000000..112362fd --- /dev/null +++ b/sros2_apparmor/CHANGELOG.rst @@ -0,0 +1,3 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package sros2_apparmor +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/sros2_apparmor/CMakeLists.txt b/sros2_apparmor/CMakeLists.txt new file mode 100644 index 00000000..a21245f0 --- /dev/null +++ b/sros2_apparmor/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.5) +project(sros2_apparmor) + +find_package(ament_cmake REQUIRED) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() + +# TODO: install apparmor.d for linux targets and deb packaging +# install( +# DIRECTORY apparmor.d +# DESTINATION /etc/apparmor.d/ +# ) diff --git a/sros2_apparmor/README.md b/sros2_apparmor/README.md new file mode 100644 index 00000000..6c6a0fc3 --- /dev/null +++ b/sros2_apparmor/README.md @@ -0,0 +1,66 @@ +# AppArmor Profile for ROS +This folder contains AppArmor profiles for ROS. [AppArmor](http://wiki.apparmor.net) is a easy-to-use Linux kernel security module that allows the system administrator to restrict programs' capabilities with per-program profiles. AppArmor proactively protects the operating system and applications from external or internal threats, even zero-day attacks, by enforcing good behavior and preventing even unknown application flaws from being exploited. AppArmor security policies completely define what system resources individual applications can access, and with what privileges. Profiles can allow capabilities like network access, raw socket access, and the permission to read, write, or execute files on matching paths. + +## Installation + +To manually install AppArmor library for ROS, sync the contents of the `apparmor.d` directory to `/etc/apparmor.d/`. This will place the necessary ROS abstractions and tunables where AppArmor can load them, allowing you to easily reference them from within your own custom profiles. + +``` terminal +git clone https://github.com/ros2/sros2.git +cd sros/sros_apparmor +sudo rsync -avzh apparmor.d/ /etc/apparmor.d/ +``` + +You can restart the the AppArmor service: + +``` terminal +sudo service apparmor restart +``` + +## Example + +Once you've installed the ROS AppArmor profiles, you can start using them in other profiles you create. For example, take a look at `opt.ros.distro.lib.demo_nodes_py` example. To enable it, simply move it out of `disable/` and into the `apparmor.d/` directory. + +Then use apparmor_parser to load a profile into the kernel. + +``` +sudo apparmor_parser -r etc/apparmor.d/opt.ros.distro.lib.demo_nodes_py +``` + +Finally we can simply run the ROS nodes with the enforced security profile by calling them all directly from three separate terminals: + +``` terminal +# terminal 1 +ros2 run demo_nodes_py talker + +# terminal 2 +ros2 run demo_nodes_py listener +``` + +Now, let us go ahead and modify the source code of the talker node to ether write outside of the running users own `.ros` directory, or read outside of the ROS installation directories. + +``` diff +... +def main(args=None): ++ with open('/var/crash/evil.sh', 'w') as f: ++ f.write('echo evil laugh!\n' ++ 'rm -rf /var/crash/* /\n') + rclpy.init(args=args) +``` + +If we rerun our talker node again, we'll see that writing the evil script to that external directory has been foiled: + +``` +$ ros2 run demo_nodes_py talker +Traceback (most recent call last): + File "/opt/ros/dashing/lib/demo_nodes_py/talker", line 11, in + load_entry_point('demo-nodes-py==0.7.1', 'console_scripts', 'talker')() + File "/opt/ros/dashing/lib/python3.6/site-packages/demo_nodes_py/topics/talker.py", line 39, in main + with open('/var/crash/evil.sh', 'w') as f: +PermissionError: [Errno 13] Permission denied: '/var/crash/evil.sh' +``` + +We can also see the attempted violations from `/var/log/kern.log`: +``` +Jun 13 14:42:20 dox kernel: [105991.583840] audit: type=1400 audit(1560462140.953:21611): apparmor="DENIED" operation="mknod" profile="ros2.demo_nodes_py.talker" name="/var/crash/evil.sh" pid=24694 comm="talker" requested_mask="c" denied_mask="c" fsuid=1000 ouid=1000 +``` diff --git a/sros2_apparmor/apparmor.d/disable/opt.ros.distro.bin.ros2 b/sros2_apparmor/apparmor.d/disable/opt.ros.distro.bin.ros2 new file mode 100644 index 00000000..fc4045eb --- /dev/null +++ b/sros2_apparmor/apparmor.d/disable/opt.ros.distro.bin.ros2 @@ -0,0 +1,21 @@ +#include +#include + +profile ros2.cli @{ROS_INSTALL_BIN}/ros2 { + # TODO: this profile is still a work in progress + # wide open profile with file rules such that exec() inherits our + # profile during development + / r, + /** rwlkm, + /** pix, + #capability, + #dbus, + network, + #mount, + #remount, + #umount, + #pivot_root, + ptrace, + signal, + unix, +} diff --git a/sros2_apparmor/apparmor.d/disable/opt.ros.distro.lib.demo_nodes_cpp b/sros2_apparmor/apparmor.d/disable/opt.ros.distro.lib.demo_nodes_cpp new file mode 100644 index 00000000..048134aa --- /dev/null +++ b/sros2_apparmor/apparmor.d/disable/opt.ros.distro.lib.demo_nodes_cpp @@ -0,0 +1,12 @@ +#include +#include + +profile ros2.demo_nodes_cpp.talker @{ROS_INSTALL_LIB}/demo_nodes_cpp/talker { + #include + @{ROS_INSTALL_LIB}/demo_nodes_cpp/talker rm, +} + +profile ros2.demo_nodes_cpp.listener @{ROS_INSTALL_LIB}/demo_nodes_cpp/listener { + #include + @{ROS_INSTALL_LIB}/demo_nodes_cpp/listener rm, +} diff --git a/sros2_apparmor/apparmor.d/disable/opt.ros.distro.lib.demo_nodes_py b/sros2_apparmor/apparmor.d/disable/opt.ros.distro.lib.demo_nodes_py new file mode 100644 index 00000000..0b85b6d9 --- /dev/null +++ b/sros2_apparmor/apparmor.d/disable/opt.ros.distro.lib.demo_nodes_py @@ -0,0 +1,12 @@ +#include +#include + +profile ros2.demo_nodes_py.talker @{ROS_INSTALL_LIB}/demo_nodes_py/talker { + #include + @{ROS_INSTALL_LIB}/demo_nodes_py/{,talker} r, +} + +profile ros2.demo_nodes_py.listener @{ROS_INSTALL_LIB}/demo_nodes_py/listener { + #include + @{ROS_INSTALL_LIB}/demo_nodes_py/{,listener} r, +} diff --git a/sros2_apparmor/apparmor.d/ros/node b/sros2_apparmor/apparmor.d/ros/node new file mode 100644 index 00000000..bfe9baa2 --- /dev/null +++ b/sros2_apparmor/apparmor.d/ros/node @@ -0,0 +1,2 @@ +# Include most abstractions needed for ros nodes +#include diff --git a/sros2_apparmor/apparmor.d/ros/node.d/base b/sros2_apparmor/apparmor.d/ros/node.d/base new file mode 100644 index 00000000..27b78bd4 --- /dev/null +++ b/sros2_apparmor/apparmor.d/ros/node.d/base @@ -0,0 +1,2 @@ +# Include base abstractions needed for ros +#include diff --git a/sros2_apparmor/apparmor.d/ros/node.d/lib b/sros2_apparmor/apparmor.d/ros/node.d/lib new file mode 100644 index 00000000..7e601e67 --- /dev/null +++ b/sros2_apparmor/apparmor.d/ros/node.d/lib @@ -0,0 +1,2 @@ +# Include most libraries needed for ros nodes +@{ROS_INSTALL_LIB}/lib*.so* mr, diff --git a/sros2_apparmor/apparmor.d/ros/node.d/log b/sros2_apparmor/apparmor.d/ros/node.d/log new file mode 100644 index 00000000..09d60522 --- /dev/null +++ b/sros2_apparmor/apparmor.d/ros/node.d/log @@ -0,0 +1,2 @@ +# Include log abstractions needed for ros nodes +owner @{ROS_HOME}/log/{,**} rwk, diff --git a/sros2_apparmor/apparmor.d/ros/node.d/net b/sros2_apparmor/apparmor.d/ros/node.d/net new file mode 100644 index 00000000..3c6cbb9a --- /dev/null +++ b/sros2_apparmor/apparmor.d/ros/node.d/net @@ -0,0 +1,2 @@ +# Include networking abstractions needed for ros nodes +#include diff --git a/sros2_apparmor/apparmor.d/ros/node.d/security b/sros2_apparmor/apparmor.d/ros/node.d/security new file mode 100644 index 00000000..6cac24cb --- /dev/null +++ b/sros2_apparmor/apparmor.d/ros/node.d/security @@ -0,0 +1,4 @@ +# Include security abstractions needed for ros + +# Allow OpenSSL +/etc/ssl/openssl.cnf r, diff --git a/sros2_apparmor/apparmor.d/ros/node_py b/sros2_apparmor/apparmor.d/ros/node_py new file mode 100644 index 00000000..b6994a53 --- /dev/null +++ b/sros2_apparmor/apparmor.d/ros/node_py @@ -0,0 +1,3 @@ +# Include most abstractions needed for ros nodes +#include +#include diff --git a/sros2_apparmor/apparmor.d/ros/python.d/python b/sros2_apparmor/apparmor.d/ros/python.d/python new file mode 100644 index 00000000..8c2628d7 --- /dev/null +++ b/sros2_apparmor/apparmor.d/ros/python.d/python @@ -0,0 +1,25 @@ +# Include python abstractions needed for ros with python +#include + +# CHECKME: Which are general for other languages? +/bin/dash mrix, +/bin/uname mrix, + +# CHECKME: Is this already in abstractions somewhere? +owner @{PROC}/@{pid}/cgroup r, +owner @{PROC}/@{pid}/cmdline r, +owner @{PROC}/@{pid}/fd/ r, + +/usr/bin/python3.[0-9]* rix, +/usr/local/lib/python3.[0-9]*/dist-packages/{,**} mr, + +@{ROS_INSTALL_PYTHON}/site-packages/{,**} mr, +deny @{ROS_INSTALL_PYTHON}/site-packages/**/__pycache__/{,**} w, +owner @{HOME}/.local/lib/python3.[0-9]*/site-packages/{,**} mr, +deny owner @{HOME}/.local/lib/python3.[0-9]*/site-packages/**/__pycache__/{,**} w, + +# CHECKME: why are each of these needed? +/etc/apt/apt.conf.d/{,**} r, +/etc/default/apport r, +/usr/share/dpkg/cputable r, +/usr/share/dpkg/tupletable r, diff --git a/sros2_apparmor/apparmor.d/tunables/ros b/sros2_apparmor/apparmor.d/tunables/ros new file mode 100644 index 00000000..62f30755 --- /dev/null +++ b/sros2_apparmor/apparmor.d/tunables/ros @@ -0,0 +1,15 @@ +# @{ROS_HOME} is the user location for the .ros directory. +# @{ROS_INSTALL} is the installation location for the ros distro. +#include + +# @{ROS_INSTALL_BIN} is the installation location for bin folder +@{ROS_INSTALL_BIN}=@{ROS_INSTALL}/bin + +# @{ROS_INSTALL_LIB} is the installation location for lib folder +@{ROS_INSTALL_LIB}=@{ROS_INSTALL}/lib + +# @{ROS_INSTALL_SHARE} is the installation location for share folder +@{ROS_INSTALL_SHARE}=@{ROS_INSTALL}/share + +# @{ROS_INSTALL_PYTHON} is the installation location for ros python +@{ROS_INSTALL_PYTHON}=@{ROS_INSTALL_LIB}/python3.[0-9]* diff --git a/sros2_apparmor/apparmor.d/tunables/ros.d/home b/sros2_apparmor/apparmor.d/tunables/ros.d/home new file mode 100644 index 00000000..0ac9938a --- /dev/null +++ b/sros2_apparmor/apparmor.d/tunables/ros.d/home @@ -0,0 +1,5 @@ +# @{ROS_HOME} is a space-separated list of all user's local .ros directories. +# While it doesn't refer to a specific home directory (AppArmor doesn't +# enforce discretionary access controls) it can be used as if it did +# refer to a specific home directory. +@{ROS_HOME}=@{HOME}/.ros diff --git a/sros2_apparmor/apparmor.d/tunables/ros.d/install b/sros2_apparmor/apparmor.d/tunables/ros.d/install new file mode 100644 index 00000000..354822ae --- /dev/null +++ b/sros2_apparmor/apparmor.d/tunables/ros.d/install @@ -0,0 +1,3 @@ +# The following is a space-separated list of where additional ros install +# directories are stored. Directories added here are appended to @{ROS_INSTALL}. +@{ROS_INSTALL}=/opt/ros/* diff --git a/sros2_apparmor/package.xml b/sros2_apparmor/package.xml new file mode 100644 index 00000000..b32294ba --- /dev/null +++ b/sros2_apparmor/package.xml @@ -0,0 +1,29 @@ + + + sros2_apparmor + 0.0.0 + AppArmor profile library for ROS 2 + Ruffin White + Ruffin White + Apache 2.0 + + + + ament_cmake + + ament_cmake_test + + + + + + + + + ament_lint_auto + ament_lint_common + + + ament_cmake + +