From 2e30d26ed611cb4fe8c23ca87b6dc037043ebb3a Mon Sep 17 00:00:00 2001 From: ruffsl Date: Fri, 2 Aug 2019 11:28:45 -0700 Subject: [PATCH 1/7] Create new package for AppArmor profiles Signed-off-by: ruffsl --- sros2_apparmor/CMakeLists.txt | 17 +++++++++++++++++ sros2_apparmor/package.xml | 29 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 sros2_apparmor/CMakeLists.txt create mode 100644 sros2_apparmor/package.xml 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/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 + + From 1adec94775a9f886502e8bd47484f2ef46909bdc Mon Sep 17 00:00:00 2001 From: ruffsl Date: Fri, 2 Aug 2019 11:36:52 -0700 Subject: [PATCH 2/7] Add profile tunables Signed-off-by: ruffsl --- sros2_apparmor/apparmor.d/tunables/ros | 15 +++++++++++++++ sros2_apparmor/apparmor.d/tunables/ros.d/home | 5 +++++ sros2_apparmor/apparmor.d/tunables/ros.d/install | 3 +++ 3 files changed, 23 insertions(+) create mode 100644 sros2_apparmor/apparmor.d/tunables/ros create mode 100644 sros2_apparmor/apparmor.d/tunables/ros.d/home create mode 100644 sros2_apparmor/apparmor.d/tunables/ros.d/install 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/* From af9a28641477d641297065337a08d601127621a1 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Fri, 2 Aug 2019 11:44:46 -0700 Subject: [PATCH 3/7] Include sub profiles for library Signed-off-by: ruffsl --- sros2_apparmor/apparmor.d/ros/node | 2 ++ sros2_apparmor/apparmor.d/ros/node.d/base | 2 ++ sros2_apparmor/apparmor.d/ros/node.d/lib | 2 ++ sros2_apparmor/apparmor.d/ros/node.d/log | 2 ++ sros2_apparmor/apparmor.d/ros/node.d/net | 2 ++ sros2_apparmor/apparmor.d/ros/node.d/security | 4 +++ sros2_apparmor/apparmor.d/ros/node_py | 3 +++ sros2_apparmor/apparmor.d/ros/python.d/python | 26 +++++++++++++++++++ 8 files changed, 43 insertions(+) create mode 100644 sros2_apparmor/apparmor.d/ros/node create mode 100644 sros2_apparmor/apparmor.d/ros/node.d/base create mode 100644 sros2_apparmor/apparmor.d/ros/node.d/lib create mode 100644 sros2_apparmor/apparmor.d/ros/node.d/log create mode 100644 sros2_apparmor/apparmor.d/ros/node.d/net create mode 100644 sros2_apparmor/apparmor.d/ros/node.d/security create mode 100644 sros2_apparmor/apparmor.d/ros/node_py create mode 100644 sros2_apparmor/apparmor.d/ros/python.d/python 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..3231eb5f --- /dev/null +++ b/sros2_apparmor/apparmor.d/ros/python.d/python @@ -0,0 +1,26 @@ +# Include python abstractions needed for ros with python +#include +#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, From 0e78e5e4c001708532a4fc30538a14079cfe453a Mon Sep 17 00:00:00 2001 From: ruffsl Date: Fri, 2 Aug 2019 11:49:18 -0700 Subject: [PATCH 4/7] Remove duplicate import As sros2_apparmor/apparmor.d/ros/node_py already contains: This `#include ` aka sros2_apparmor/apparmor.d/ros/node is the same: Signed-off-by: ruffsl --- sros2_apparmor/apparmor.d/ros/python.d/python | 1 - 1 file changed, 1 deletion(-) diff --git a/sros2_apparmor/apparmor.d/ros/python.d/python b/sros2_apparmor/apparmor.d/ros/python.d/python index 3231eb5f..8c2628d7 100644 --- a/sros2_apparmor/apparmor.d/ros/python.d/python +++ b/sros2_apparmor/apparmor.d/ros/python.d/python @@ -1,5 +1,4 @@ # Include python abstractions needed for ros with python -#include #include # CHECKME: Which are general for other languages? From 2b010b42154159e767da698faadab34fc95fdc97 Mon Sep 17 00:00:00 2001 From: ruffsl Date: Fri, 2 Aug 2019 11:56:03 -0700 Subject: [PATCH 5/7] Add example profiles for talker listener shipped in the disable folder by default Signed-off-by: ruffsl --- .../disable/opt.ros.distro.lib.demo_nodes_cpp | 12 ++++++++++++ .../disable/opt.ros.distro.lib.demo_nodes_py | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 sros2_apparmor/apparmor.d/disable/opt.ros.distro.lib.demo_nodes_cpp create mode 100644 sros2_apparmor/apparmor.d/disable/opt.ros.distro.lib.demo_nodes_py 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, +} From f4d758aa15f59c5d0607ae87a4b9ce776abdefcc Mon Sep 17 00:00:00 2001 From: ruffsl Date: Fri, 2 Aug 2019 11:58:52 -0700 Subject: [PATCH 6/7] Stage work in progress for ros2_cli profile Signed-off-by: ruffsl --- .../disable/opt.ros.distro.bin.ros2 | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 sros2_apparmor/apparmor.d/disable/opt.ros.distro.bin.ros2 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, +} From b293e997e05210dafcc361a9cd779335d2e0737b Mon Sep 17 00:00:00 2001 From: ruffsl Date: Fri, 2 Aug 2019 13:02:05 -0700 Subject: [PATCH 7/7] Add README doc with further descriptions notes on installation and use and example demonstration Signed-off-by: ruffsl --- sros2_apparmor/CHANGELOG.rst | 3 ++ sros2_apparmor/README.md | 66 ++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 sros2_apparmor/CHANGELOG.rst create mode 100644 sros2_apparmor/README.md 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/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 +```