diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ace91dd..91affcde 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 3.5) project(rcutils) +option(RCUTILS_NO_THREAD_SUPPORT "Disable thread support." OFF) +option(RCUTILS_NO_FILESYSTEM "Disable filesystem usage." OFF) +option(RCUTILS_AVOID_DYNAMIC_ALLOCATION "Disable dynamic allocations." OFF) + # Default to C11 if(NOT CMAKE_C_STANDARD) set(CMAKE_C_STANDARD 11) @@ -15,6 +19,7 @@ include(CheckLibraryExists) find_package(ament_cmake_python REQUIRED) find_package(ament_cmake_ros REQUIRED) +find_package(tinydir_vendor REQUIRED) ament_python_install_package(${PROJECT_NAME}) @@ -53,7 +58,9 @@ set(rcutils_sources src/time.c ${time_impl_c} src/uint8_array.c + src/security_directory.c ) + set_source_files_properties( ${rcutils_sources} PROPERTIES language "C") @@ -91,6 +98,7 @@ add_custom_command(OUTPUT include/rcutils/logging_macros.h ) list(APPEND rcutils_sources include/rcutils/logging_macros.h) +include_directories("${CMAKE_CURRENT_BINARY_DIR}/include") add_library( ${PROJECT_NAME} @@ -104,6 +112,11 @@ target_include_directories(${PROJECT_NAME} PUBLIC # which is appropriate when building the dll but not consuming it. target_compile_definitions(${PROJECT_NAME} PRIVATE "RCUTILS_BUILDING_DLL") +configure_file( + "${PROJECT_SOURCE_DIR}/include/rcutils/configuration_flags.h.in" + "${PROJECT_BINARY_DIR}/include/rcutils/configuration_flags.h" +) + target_link_libraries(${PROJECT_NAME} ${CMAKE_DL_LIBS}) # Needed if pthread is used for thread local storage. @@ -116,11 +129,18 @@ install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME} LIBRARY DESTINATION lib RUNTIME DESTINATION bin) +ament_target_dependencies(${PROJECT_NAME} + "tinydir_vendor" +) + if(BUILD_TESTING) if(NOT WIN32) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") endif() + set(test_resources_dir_name "test/resources") + add_definitions(-DTEST_RESOURCES_DIRECTORY="${CMAKE_CURRENT_BINARY_DIR}/${test_resources_dir_name}") + find_package(ament_cmake_gmock REQUIRED) find_package(ament_cmake_gtest REQUIRED) find_package(ament_cmake_pytest REQUIRED) @@ -428,6 +448,16 @@ if(BUILD_TESTING) endif() + rcutils_custom_add_gtest(test_security_directory + test/test_security_directory.cpp + ) + if(TARGET test_security_directory) + target_link_libraries(test_security_directory ${PROJECT_NAME} osrf_testing_tools_cpp::memory_tools) + endif() + + install(DIRECTORY ${test_resources_dir_name} + DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + ) endif() ament_export_dependencies(ament_cmake) diff --git a/include/rcutils/allocator.h b/include/rcutils/allocator.h index e84706cb..7089adf7 100644 --- a/include/rcutils/allocator.h +++ b/include/rcutils/allocator.h @@ -83,6 +83,21 @@ RCUTILS_WARN_UNUSED rcutils_allocator_t rcutils_get_zero_initialized_allocator(void); +/// Set rcutils default allocators. +/** + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | No + * Thread-Safe | Yes + * Uses Atomics | No + * Lock-Free | Yes + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +bool +rcutils_set_default_allocator(rcutils_allocator_t * allocator); + /// Return a properly initialized rcutils_allocator_t with default values. /** * This defaults to: diff --git a/include/rcutils/configuration_flags.h.in b/include/rcutils/configuration_flags.h.in new file mode 100644 index 00000000..346c6a33 --- /dev/null +++ b/include/rcutils/configuration_flags.h.in @@ -0,0 +1,18 @@ + +#ifndef RCUTILS__CONFIGURATION_FLAGS_H_ +#define RCUTILS__CONFIGURATION_FLAGS_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#cmakedefine RCUTILS_NO_FILESYSTEM +#cmakedefine RCUTILS_AVOID_DYNAMIC_ALLOCATION +#cmakedefine RCUTILS_NO_THREAD_SUPPORT + +#ifdef __cplusplus +} +#endif + +#endif // RCUTILS__CONFIGURATION_FLAGS_H_ \ No newline at end of file diff --git a/include/rcutils/error_handling.h b/include/rcutils/error_handling.h index 9f19b834..51f93770 100644 --- a/include/rcutils/error_handling.h +++ b/include/rcutils/error_handling.h @@ -38,15 +38,18 @@ extern "C" #include "rcutils/snprintf.h" #include "rcutils/types/rcutils_ret.h" #include "rcutils/visibility_control.h" +#include "rcutils/configuration_flags.h" -#ifdef __STDC_LIB_EXT1__ +#if defined(__STDC_LIB_EXT1__) && !defined(RCUTILS_NO_FILESYSTEM) // Limit the buffer size in the `fwrite` call to give an upper bound to buffer overrun in the case // of non-null terminated `msg`. #define RCUTILS_SAFE_FWRITE_TO_STDERR(msg) \ do {fwrite(msg, sizeof(char), strnlen_s(msg, 4096), stderr);} while (0) -#else +#elif !defined(RCUTILS_NO_FILESYSTEM) #define RCUTILS_SAFE_FWRITE_TO_STDERR(msg) \ do {fwrite(msg, sizeof(char), strlen(msg), stderr);} while (0) +#else + #define RCUTILS_SAFE_FWRITE_TO_STDERR(msg) #endif // fixed constraints @@ -199,8 +202,12 @@ rcutils_set_error_state(const char * error_string, const char * file, size_t lin * * \param[in] msg The error message to be set. */ +#ifdef RCUTILS_AVOID_DYNAMIC_ALLOCATION + #define RCUTILS_SET_ERROR_MSG(msg) +#else #define RCUTILS_SET_ERROR_MSG(msg) \ do {rcutils_set_error_state(msg, __FILE__, __LINE__);} while (0) +#endif // RCUTILS_AVOID_DYNAMIC_ALLOCATION /// Set the error message using a format string and format arguments. /** @@ -211,6 +218,9 @@ rcutils_set_error_state(const char * error_string, const char * file, size_t lin * \param[in] format_string The string to be used as the format of the error message. * \param[in] ... Arguments for the format string. */ +#ifdef RCUTILS_AVOID_DYNAMIC_ALLOCATION + #define RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(format_string, ...) +#else #define RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(format_string, ...) \ do { \ char output_msg[RCUTILS_ERROR_MESSAGE_MAX_LENGTH]; \ @@ -221,6 +231,8 @@ rcutils_set_error_state(const char * error_string, const char * file, size_t lin RCUTILS_SET_ERROR_MSG(output_msg); \ } \ } while (0) +#endif // RCUTILS_AVOID_DYNAMIC_ALLOCATION + /// Return `true` if the error is set, otherwise `false`. RCUTILS_PUBLIC diff --git a/include/rcutils/macros.h b/include/rcutils/macros.h index 27736a58..0c8c467e 100644 --- a/include/rcutils/macros.h +++ b/include/rcutils/macros.h @@ -20,6 +20,8 @@ extern "C" { #endif +#include "rcutils/configuration_flags.h" + #ifndef _WIN32 #define RCUTILS_WARN_UNUSED __attribute__((warn_unused_result)) #else @@ -28,7 +30,9 @@ extern "C" // Note: this block was migrated from rmw/macros.h // This block either sets RCUTILS_THREAD_LOCAL or RCUTILS_THREAD_LOCAL_PTHREAD. -#if defined _WIN32 || defined __CYGWIN__ +#if defined(RCUTILS_NO_THREAD_SUPPORT) + #define RCUTILS_THREAD_LOCAL +#elif defined _WIN32 || defined __CYGWIN__ // Windows or Cygwin #define RCUTILS_THREAD_LOCAL __declspec(thread) #elif defined __APPLE__ diff --git a/include/rcutils/security_directory.h b/include/rcutils/security_directory.h new file mode 100644 index 00000000..9d2c6806 --- /dev/null +++ b/include/rcutils/security_directory.h @@ -0,0 +1,67 @@ +// Copyright 2018 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef RCUTILS__SECURITY_DIRECTORY_H_ +#define RCUTILS__SECURITY_DIRECTORY_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "rcutils/allocator.h" +#include "rcutils/visibility_control.h" + +#ifndef ROS_SECURITY_NODE_DIRECTORY_VAR_NAME + #define ROS_SECURITY_NODE_DIRECTORY_VAR_NAME "ROS_SECURITY_NODE_DIRECTORY" +#endif + +#ifndef ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME + #define ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "ROS_SECURITY_ROOT_DIRECTORY" +#endif + +#ifndef ROS_SECURITY_LOOKUP_TYPE_VAR_NAME + #define ROS_SECURITY_LOOKUP_TYPE_VAR_NAME "ROS_SECURITY_LOOKUP_TYPE" +#endif + +/// Return the secure root directory associated with a node given its validated name and namespace. +/** + * E.g. for a node named "c" in namespace "/a/b", the secure root path will be + * "a/b/c", where the delimiter "/" is native for target file system (e.g. "\\" for _WIN32). + * If no exact match is found for the node name, a best match would be used instead + * (by performing longest-prefix matching). + * + * However, this expansion can be overridden by setting the secure node directory environment + * variable, allowing users to explicitly specify the exact secure root directory to be utilized. + * Such an override is useful for where the FQN of a node is non-deterministic before runtime, + * or when testing and using additional tools that may not otherwise be easily provisioned. + * + * \param[in] node_name validated node name (a single token) + * \param[in] node_namespace validated, absolute namespace (starting with "/") + * \param[in] allocator the allocator to use for allocation + * \returns machine specific (absolute) node secure root path or NULL on failure + * returned pointer must be deallocated by the caller of this function + */ +RCUTILS_PUBLIC +char * rcutils_get_secure_root( + const char * node_name, + const char * node_namespace, + const rcutils_allocator_t * allocator +); + +#ifdef __cplusplus +} +#endif + +#endif // RCUTILS__SECURITY_DIRECTORY_H_ diff --git a/package.xml b/package.xml index fea8335e..f1906c0d 100644 --- a/package.xml +++ b/package.xml @@ -10,6 +10,9 @@ ament_cmake_ros python3-empy + tinydir_vendor + tinydir_vendor + ament_cmake_gmock ament_cmake_gtest ament_cmake_pytest diff --git a/src/allocator.c b/src/allocator.c index 92ef82d0..ab1972c7 100644 --- a/src/allocator.c +++ b/src/allocator.c @@ -69,16 +69,31 @@ rcutils_get_zero_initialized_allocator(void) return zero_allocator; } -rcutils_allocator_t -rcutils_get_default_allocator() -{ - static rcutils_allocator_t default_allocator = { +static rcutils_allocator_t default_allocator = { .allocate = __default_allocate, .deallocate = __default_deallocate, .reallocate = __default_reallocate, .zero_allocate = __default_zero_allocate, .state = NULL, }; + +bool +rcutils_set_default_allocator(rcutils_allocator_t * allocator){ + if (rcutils_allocator_is_valid(allocator)) + { + default_allocator.allocate = allocator->allocate; + default_allocator.deallocate = allocator->deallocate; + default_allocator.reallocate = allocator->reallocate; + default_allocator.zero_allocate = allocator->zero_allocate; + default_allocator.state = NULL; + return true; + } + return false; +} + +rcutils_allocator_t +rcutils_get_default_allocator() +{ return default_allocator; } diff --git a/src/filesystem.c b/src/filesystem.c index 923bfd81..f89ebbd2 100644 --- a/src/filesystem.c +++ b/src/filesystem.c @@ -22,9 +22,13 @@ extern "C" #include #include #include +#ifndef RCUTILS_NO_FILESYSTEM #include +#endif #ifndef _WIN32 +#ifndef RCUTILS_NO_FILESYSTEM #include +#endif #include #else #include @@ -33,6 +37,7 @@ extern "C" #include "rcutils/format_string.h" #include "rcutils/repl_str.h" +#include "rcutils/error_handling.h" #ifdef _WIN32 # define RCUTILS_PATH_DELIMITER "\\" @@ -43,6 +48,10 @@ extern "C" bool rcutils_get_cwd(char * buffer, size_t max_length) { +#ifdef RCUTILS_NO_FILESYSTEM + RCUTILS_SET_ERROR_MSG("not available filesystem"); + return false; +#else if (NULL == buffer || max_length == 0) { return false; } @@ -56,11 +65,16 @@ rcutils_get_cwd(char * buffer, size_t max_length) } #endif // _WIN32 return true; +#endif // _RCUTILS_NO_FILESYSTEM } bool rcutils_is_directory(const char * abs_path) { +#ifdef RCUTILS_NO_FILESYSTEM + RCUTILS_SET_ERROR_MSG("not available filesystem"); + return false; +#else struct stat buf; if (stat(abs_path, &buf) < 0) { return false; @@ -70,11 +84,16 @@ rcutils_is_directory(const char * abs_path) #else return S_ISDIR(buf.st_mode); #endif // _WIN32 +#endif // _RCUTILS_NO_FILESYSTEM } bool rcutils_is_file(const char * abs_path) { +#ifdef RCUTILS_NO_FILESYSTEM + RCUTILS_SET_ERROR_MSG("not available filesystem"); + return false; +#else struct stat buf; if (stat(abs_path, &buf) < 0) { return false; @@ -84,21 +103,31 @@ rcutils_is_file(const char * abs_path) #else return S_ISREG(buf.st_mode); #endif // _WIN32 +#endif // _RCUTILS_NO_FILESYSTEM } bool rcutils_exists(const char * abs_path) { +#ifdef RCUTILS_NO_FILESYSTEM + RCUTILS_SET_ERROR_MSG("not available filesystem"); + return false; +#else struct stat buf; if (stat(abs_path, &buf) < 0) { return false; } return true; +#endif // _RCUTILS_NO_FILESYSTEM } bool rcutils_is_readable(const char * abs_path) { +#ifdef RCUTILS_NO_FILESYSTEM + RCUTILS_SET_ERROR_MSG("not available filesystem"); + return false; +#else struct stat buf; if (stat(abs_path, &buf) < 0) { return false; @@ -111,11 +140,16 @@ rcutils_is_readable(const char * abs_path) return false; } return true; +#endif // _RCUTILS_NO_FILESYSTEM } bool rcutils_is_writable(const char * abs_path) { +#ifdef RCUTILS_NO_FILESYSTEM + RCUTILS_SET_ERROR_MSG("not available filesystem"); + return false; +#else struct stat buf; if (stat(abs_path, &buf) < 0) { return false; @@ -128,11 +162,16 @@ rcutils_is_writable(const char * abs_path) return false; } return true; +#endif // _RCUTILS_NO_FILESYSTEM } bool rcutils_is_readable_and_writable(const char * abs_path) { +#ifdef RCUTILS_NO_FILESYSTEM + RCUTILS_SET_ERROR_MSG("not available filesystem"); + return false; +#else struct stat buf; if (stat(abs_path, &buf) < 0) { return false; @@ -147,6 +186,7 @@ rcutils_is_readable_and_writable(const char * abs_path) return false; } return true; +#endif // _RCUTILS_NO_FILESYSTEM } char * @@ -215,6 +255,12 @@ rcutils_mkdir(const char * abs_path) size_t rcutils_calculate_directory_size(const char * directory_path, rcutils_allocator_t allocator) { + +#ifdef RCUTILS_NO_FILESYSTEM + RCUTILS_SET_ERROR_MSG("not available filesystem"); + return 0; +#else + size_t dir_size = 0; if (!rcutils_is_directory(directory_path)) { @@ -260,11 +306,18 @@ rcutils_calculate_directory_size(const char * directory_path, rcutils_allocator_ closedir(dir); return dir_size; #endif + +#endif // _RCUTILS_NO_FILESYSTEM } size_t rcutils_get_file_size(const char * file_path) { + +#ifdef RCUTILS_NO_FILESYSTEM + RCUTILS_SET_ERROR_MSG("not available filesystem"); + return 0; +#else if (!rcutils_is_file(file_path)) { fprintf(stderr, "Path is not a file: %s\n", file_path); return 0; @@ -273,6 +326,7 @@ rcutils_get_file_size(const char * file_path) struct stat stat_buffer; int rc = stat(file_path, &stat_buffer); return rc == 0 ? (size_t)(stat_buffer.st_size) : 0; +#endif // _RCUTILS_NO_FILESYSTEM } #ifdef __cplusplus diff --git a/src/process.c b/src/process.c index 832c3d1a..288fa2a5 100644 --- a/src/process.c +++ b/src/process.c @@ -50,7 +50,13 @@ char * rcutils_get_executable_name(rcutils_allocator_t allocator) #if defined __APPLE__ || defined __FreeBSD__ || (defined __ANDROID__ && __ANDROID_API__ >= 21) const char * appname = getprogname(); #elif defined __GNUC__ - const char * appname = program_invocation_name; + #if defined __linux__ || defined __linux || defined __gnu_linux__ || defined linux + const char * appname = program_invocation_name; + #else + // Some embedded OS compile with __GNUC__ but are not quite conformant with GNU-specific extensions. + // They may fake to have a GLIBC in their custom C library implementation. + const char * appname = ""; + #endif #elif defined _WIN32 || defined __CYGWIN__ char appname[MAX_PATH]; int32_t size = GetModuleFileNameA(NULL, appname, MAX_PATH); diff --git a/src/security_directory.c b/src/security_directory.c new file mode 100644 index 00000000..4bcba2d7 --- /dev/null +++ b/src/security_directory.c @@ -0,0 +1,278 @@ +// Copyright 2018 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "rcutils/security_directory.h" + +#include "rcutils/error_handling.h" +#include "rcutils/filesystem.h" +#include "rcutils/get_env.h" +#include "rcutils/format_string.h" + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wembedded-directive" +#endif + +#ifndef RCUTILS_NO_FILESYSTEM +#include "tinydir/tinydir.h" +#endif + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + +/** + * A security lookup function takes in the node's name, namespace, a security root directory and an allocator; + * It returns the relevant information required to load the security credentials, + * which is currently a path to a directory on the filesystem containing DDS Security permission files. + */ +typedef char * (* security_lookup_fn_t) ( + const char * node_name, + const char * node_namespace, + const char * ros_secure_root_env, + const rcutils_allocator_t * allocator +); + +char * exact_match_lookup( + const char * node_name, + const char * node_namespace, + const char * ros_secure_root_env, + const rcutils_allocator_t * allocator +); + +char * prefix_match_lookup( + const char * node_name, + const char * node_namespace, + const char * ros_secure_root_env, + const rcutils_allocator_t * allocator +); + +security_lookup_fn_t g_security_lookup_fns[] = { + NULL, + exact_match_lookup, + prefix_match_lookup, +}; + +typedef enum ros_security_lookup_type_e +{ + ROS_SECURITY_LOOKUP_NODE_OVERRIDE = 0, + ROS_SECURITY_LOOKUP_MATCH_EXACT = 1, + ROS_SECURITY_LOOKUP_MATCH_PREFIX = 2, +} ros_security_lookup_type_t; + +char * g_security_lookup_type_strings[] = { + "NODE_OVERRIDE", + "MATCH_EXACT", + "MATCH_PREFIX" +}; + +/// Return the directory whose name most closely matches node_name (longest-prefix match), +/// scanning under base_dir. +/** + * By using a prefix match, a node named e.g. "my_node_123" will be able to load and use the + * directory "my_node" if no better match exists. + * \param[in] base_dir + * \param[in] node_name + * \param[out] matched_name must be a valid memory address allocated with at least + * _TINYDIR_FILENAME_MAX characters. + * \return true if a match was found + */ +static bool get_best_matching_directory( + const char * base_dir, + const char * node_name, + char * matched_name) +{ +#ifdef RCUTILS_NO_FILESYSTEM + RCUTILS_SET_ERROR_MSG("not available filesystem"); + return false; +#else +size_t max_match_length = 0; + tinydir_dir dir; + if (NULL == base_dir || NULL == node_name || NULL == matched_name) { + return false; + } + if (-1 == tinydir_open(&dir, base_dir)) { + return false; + } + while (dir.has_next) { + tinydir_file file; + if (-1 == tinydir_readfile(&dir, &file)) { + goto cleanup; + } + if (file.is_dir) { + size_t matched_name_length = strnlen(file.name, sizeof(file.name) - 1); + if (0 == + strncmp(file.name, node_name, + matched_name_length) && matched_name_length > max_match_length) + { + max_match_length = matched_name_length; + memcpy(matched_name, file.name, max_match_length); + } + } + if (-1 == tinydir_next(&dir)) { + goto cleanup; + } + } +cleanup: + tinydir_close(&dir); + return max_match_length > 0; +#endif // _RCUTILS_NO_FILESYSTEM +} + +char * exact_match_lookup( + const char * node_name, + const char * node_namespace, + const char * ros_secure_root_env, + const rcutils_allocator_t * allocator) +{ + // Perform an exact match for the node's name in directory /. + char * node_secure_root = NULL; + // "/" case when root namespace is explicitly passed in + if (1 == strlen(node_namespace)) { + node_secure_root = rcutils_join_path(ros_secure_root_env, node_name, *allocator); + } else { + char * node_fqn = NULL; + char * node_root_path = NULL; + // Combine node namespace with node name + // TODO(ros2team): remove the hard-coded value of the root namespace + node_fqn = rcutils_format_string(*allocator, "%s%s%s", node_namespace, "/", node_name); + // Get native path, ignore the leading forward slash + // TODO(ros2team): remove the hard-coded length, use the length of the root namespace instead + node_root_path = rcutils_to_native_path(node_fqn + 1, *allocator); + node_secure_root = rcutils_join_path(ros_secure_root_env, node_root_path, *allocator); + allocator->deallocate(node_fqn, allocator->state); + allocator->deallocate(node_root_path, allocator->state); + } + return node_secure_root; +} + +char * prefix_match_lookup( + const char * node_name, + const char * node_namespace, + const char * ros_secure_root_env, + const rcutils_allocator_t * allocator) +{ +#ifdef RCUTILS_NO_FILESYSTEM + RCUTILS_SET_ERROR_MSG("not available filesystem"); + return false; +#else + // Perform longest prefix match for the node's name in directory /. + char * node_secure_root = NULL; + char matched_dir[_TINYDIR_FILENAME_MAX] = {0}; + char * base_lookup_dir = NULL; + if (strlen(node_namespace) == 1) { + base_lookup_dir = (char *) ros_secure_root_env; + } else { + // TODO(ros2team): remove the hard-coded length, use the length of the root namespace instead. + base_lookup_dir = rcutils_join_path(ros_secure_root_env, node_namespace + 1, *allocator); + } + if (get_best_matching_directory(base_lookup_dir, node_name, matched_dir)) { + node_secure_root = rcutils_join_path(base_lookup_dir, matched_dir, *allocator); + } + if (base_lookup_dir != ros_secure_root_env && NULL != base_lookup_dir) { + allocator->deallocate(base_lookup_dir, allocator->state); + } + return node_secure_root; +#endif // _RCUTILS_NO_FILESYSTEM +} + +char * rcutils_get_secure_root( + const char * node_name, + const char * node_namespace, + const rcutils_allocator_t * allocator) +{ + bool ros_secure_node_override = true; + + // find out if either of the configuration environment variables are set + const char * env_buf = NULL; + if (NULL == node_name) { + return NULL; + } + if (rcutils_get_env(ROS_SECURITY_NODE_DIRECTORY_VAR_NAME, &env_buf)) { + return NULL; + } + if (!env_buf) { + return NULL; + } + size_t ros_secure_root_size = strlen(env_buf); + if (!ros_secure_root_size) { + // check root directory if node directory environment variable is empty + if (rcutils_get_env(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME, &env_buf)) { + return NULL; + } + if (!env_buf) { + return NULL; + } + ros_secure_root_size = strlen(env_buf); + if (!ros_secure_root_size) { + return NULL; // environment variable was empty + } else { + ros_secure_node_override = false; + } + } + + // found a usable environment variable, copy into our memory before overwriting with next lookup + char * ros_secure_root_env = + (char *)allocator->allocate(ros_secure_root_size + 1, allocator->state); + memcpy(ros_secure_root_env, env_buf, ros_secure_root_size + 1); + // TODO(ros2team): This make an assumption on the value and length of the root namespace. + // This should likely come from another (rcl/rmw?) function for reuse. + // If the namespace is the root namespace ("/"), the secure root is just the node name. + + char * lookup_strategy = NULL; + char * node_secure_root = NULL; + if (ros_secure_node_override) { + node_secure_root = (char *)allocator->allocate(ros_secure_root_size + 1, allocator->state); + memcpy(node_secure_root, ros_secure_root_env, ros_secure_root_size + 1); + lookup_strategy = g_security_lookup_type_strings[ROS_SECURITY_LOOKUP_NODE_OVERRIDE]; + + } else { + // Check which lookup method to use and invoke the relevant function. + const char * ros_security_lookup_type = NULL; + if (rcutils_get_env(ROS_SECURITY_LOOKUP_TYPE_VAR_NAME, &ros_security_lookup_type)) { + allocator->deallocate(ros_secure_root_env, allocator->state); + return NULL; + } + if (0 == strcmp(ros_security_lookup_type, + g_security_lookup_type_strings[ROS_SECURITY_LOOKUP_MATCH_PREFIX])) + { + node_secure_root = g_security_lookup_fns[ROS_SECURITY_LOOKUP_MATCH_PREFIX] + (node_name, node_namespace, ros_secure_root_env, allocator); + lookup_strategy = g_security_lookup_type_strings[ROS_SECURITY_LOOKUP_MATCH_PREFIX]; + } else { /* Default is MATCH_EXACT */ + node_secure_root = g_security_lookup_fns[ROS_SECURITY_LOOKUP_MATCH_EXACT] + (node_name, node_namespace, ros_secure_root_env, allocator); + lookup_strategy = g_security_lookup_type_strings[ROS_SECURITY_LOOKUP_MATCH_EXACT]; + } + } + + if (NULL == node_secure_root || !rcutils_is_directory(node_secure_root)) { + // Check node_secure_root is not NULL before checking directory + if (NULL == node_secure_root) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "SECURITY ERROR: unable to find a folder matching the node name in %s%s." + "Lookup strategy: %s", + ros_secure_root_env, node_namespace, lookup_strategy); + } else { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "SECURITY ERROR: directory %s does not exist. Lookup strategy: %s", + node_secure_root, lookup_strategy); + } + allocator->deallocate(ros_secure_root_env, allocator->state); + allocator->deallocate(node_secure_root, allocator->state); + return NULL; + } + allocator->deallocate(ros_secure_root_env, allocator->state); + return node_secure_root; +} diff --git a/src/shared_library.c b/src/shared_library.c index a0ce9a7e..bd3b906a 100644 --- a/src/shared_library.c +++ b/src/shared_library.c @@ -19,6 +19,10 @@ extern "C" #include #include +#include "rcutils/configuration_flags.h" + +#ifndef RCUTILS_NO_FILESYSTEM + #ifndef _WIN32 #include #else @@ -26,6 +30,8 @@ extern "C" C_ASSERT(sizeof(void *) == sizeof(HINSTANCE)); #endif // _WIN32 +#endif //RCUTILS_NO_FILESYSTEM + #include "rcutils/error_handling.h" #include "rcutils/shared_library.h" #include "rcutils/strdup.h" @@ -45,7 +51,10 @@ rcutils_load_shared_library( rcutils_shared_library_t * lib, const char * library_path, rcutils_allocator_t allocator) -{ +{ + +#ifndef RCUTILS_NO_FILESYSTEM + RCUTILS_CHECK_ARGUMENT_FOR_NULL(lib, RCUTILS_RET_INVALID_ARGUMENT); RCUTILS_CHECK_ARGUMENT_FOR_NULL(library_path, RCUTILS_RET_INVALID_ARGUMENT); RCUTILS_CHECK_ALLOCATOR(&allocator, return RCUTILS_RET_INVALID_ARGUMENT); @@ -76,6 +85,11 @@ rcutils_load_shared_library( return RCUTILS_RET_ERROR; } return RCUTILS_RET_OK; + +#else + return RCUTILS_RET_ERROR; +#endif //RCUTILS_NO_FILESYSTEM + } void * diff --git a/src/time_unix.c b/src/time_unix.c index 028fb453..ea52711d 100644 --- a/src/time_unix.c +++ b/src/time_unix.c @@ -39,7 +39,7 @@ extern "C" // This id an appropriate check for clock_gettime() according to: // http://man7.org/linux/man-pages/man2/clock_gettime.2.html # if !defined(_POSIX_TIMERS) || !_POSIX_TIMERS -# error no monotonic clock function available +# warning no monotonic clock function available # endif // !defined(_POSIX_TIMERS) || !_POSIX_TIMERS #endif // !defined(__MACH__) diff --git a/test/resources/test_security_directory/dummy_node/.gitkeep b/test/resources/test_security_directory/dummy_node/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/test/test_security_directory.cpp b/test/test_security_directory.cpp new file mode 100644 index 00000000..bf1b7f2b --- /dev/null +++ b/test/test_security_directory.cpp @@ -0,0 +1,216 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include "rcutils/security_directory.h" +#include "rcutils/filesystem.h" +#include "osrf_testing_tools_cpp/scope_exit.hpp" +#include "rcutils/error_handling.h" + +#define ROOT_NAMESPACE "/" +#define TEST_SECURITY_DIRECTORY_RESOURCES_DIR_NAME "test_security_directory" +#define TEST_NODE_NAME "dummy_node" +#define TEST_NODE_NAMESPACE ROOT_NAMESPACE TEST_SECURITY_DIRECTORY_RESOURCES_DIR_NAME + +char g_envstring[512] = {0}; + +static int putenv_wrapper(const char * env_var) +{ +#ifdef _WIN32 + return _putenv(env_var); +#else + return putenv(reinterpret_cast(const_cast(env_var))); +#endif +} + +static int unsetenv_wrapper(const char * var_name) +{ +#ifdef _WIN32 + // On windows, putenv("VAR=") deletes VAR from environment + std::string var(var_name); + var += "="; + return _putenv(var.c_str()); +#else + return unsetenv(var_name); +#endif +} + +class TestGetSecureRoot : public ::testing::Test +{ +protected: + void SetUp() final + { + // Reset rcutils error global state in case a previously + // running test has failed. + rcutils_reset_error(); + + // Always make sure the variable we set is unset at the beginning of a test + unsetenv_wrapper(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME); + unsetenv_wrapper(ROS_SECURITY_NODE_DIRECTORY_VAR_NAME); + unsetenv_wrapper(ROS_SECURITY_LOOKUP_TYPE_VAR_NAME); + allocator = rcutils_get_default_allocator(); + root_path = nullptr; + secure_root = nullptr; + base_lookup_dir_fqn = nullptr; + } + + void TearDown() final + { + OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({ + allocator.deallocate(root_path, allocator.state); + }); + OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({ + allocator.deallocate(secure_root, allocator.state); + }); + OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({ + allocator.deallocate(base_lookup_dir_fqn, allocator.state); + }); + } + + void set_base_lookup_dir_fqn(const char * resource_dir, const char * resource_dir_name) + { + base_lookup_dir_fqn = rcutils_join_path(resource_dir, + resource_dir_name, allocator); + std::string putenv_input = ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "="; + putenv_input += base_lookup_dir_fqn; + memcpy(g_envstring, putenv_input.c_str(), + std::min(putenv_input.length(), sizeof(g_envstring) - 1)); + putenv_wrapper(g_envstring); + } + + rcutils_allocator_t allocator; + char * root_path; + char * secure_root; + char * base_lookup_dir_fqn; +}; + +TEST_F(TestGetSecureRoot, failureScenarios) { + ASSERT_EQ(rcutils_get_secure_root(TEST_NODE_NAME, TEST_NODE_NAMESPACE, &allocator), + (char *) NULL); + + putenv_wrapper(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); + + /* Security directory is set, but there's no matching directory */ + /// Wrong namespace + ASSERT_EQ(rcutils_get_secure_root(TEST_NODE_NAME, "/some_other_namespace", &allocator), + (char *) NULL); + /// Wrong node name + ASSERT_EQ(rcutils_get_secure_root("not_" TEST_NODE_NAME, TEST_NODE_NAMESPACE, &allocator), + (char *) NULL); +} + +TEST_F(TestGetSecureRoot, successScenarios_local_exactMatch) { + putenv_wrapper(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); + + /* -------------------------- + * Namespace : Custom (local) + * Match type : Exact + * -------------------------- + * Root: ${CMAKE_BINARY_DIR}/tests/resources + * Namespace: /test_security_directory + * Node: dummy_node + */ + secure_root = rcutils_get_secure_root(TEST_NODE_NAME, TEST_NODE_NAMESPACE, &allocator); + std::string secure_root_str(secure_root); + ASSERT_STREQ(TEST_NODE_NAME, + secure_root_str.substr(secure_root_str.size() - (sizeof(TEST_NODE_NAME) - 1)).c_str()); +} + +TEST_F(TestGetSecureRoot, successScenarios_local_prefixMatch) { + putenv_wrapper(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); + secure_root = rcutils_get_secure_root(TEST_NODE_NAME, TEST_NODE_NAMESPACE, &allocator); + + /* -------------------------- + * Namespace : Custom (local) + * Match type : Prefix + * -------------------------- + * Root: ${CMAKE_BINARY_DIR}/tests/resources + * Namespace: /test_security_directory + * Node: dummy_node_and_some_suffix_added */ + root_path = rcutils_get_secure_root(TEST_NODE_NAME "_and_some_suffix_added", + TEST_NODE_NAMESPACE, &allocator); + ASSERT_STRNE(root_path, secure_root); + putenv_wrapper(ROS_SECURITY_LOOKUP_TYPE_VAR_NAME "=MATCH_PREFIX"); + root_path = rcutils_get_secure_root(TEST_NODE_NAME "_and_some_suffix_added", + TEST_NODE_NAMESPACE, &allocator); + ASSERT_STREQ(root_path, secure_root); +} + +TEST_F(TestGetSecureRoot, successScenarios_root_exactMatch) { + putenv_wrapper(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); + putenv_wrapper(ROS_SECURITY_LOOKUP_TYPE_VAR_NAME "=MATCH_PREFIX"); + secure_root = rcutils_get_secure_root(TEST_NODE_NAME, TEST_NODE_NAMESPACE, &allocator); + + /* Include the namespace as part of the root security directory and test root namespace */ + set_base_lookup_dir_fqn(TEST_RESOURCES_DIRECTORY, TEST_SECURITY_DIRECTORY_RESOURCES_DIR_NAME); + /* -------------------------- + * Namespace : Root + * Match type : Exact + * -------------------------- + * Root: ${CMAKE_BINARY_DIR}/tests/resources/test_security_directory + * Namespace: / + * Node: dummy_node */ + root_path = rcutils_get_secure_root(TEST_NODE_NAME, ROOT_NAMESPACE, &allocator); + ASSERT_STREQ(root_path, secure_root); +} + +TEST_F(TestGetSecureRoot, successScenarios_root_prefixMatch) { + putenv_wrapper(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); + putenv_wrapper(ROS_SECURITY_LOOKUP_TYPE_VAR_NAME "=MATCH_PREFIX"); + secure_root = rcutils_get_secure_root(TEST_NODE_NAME, TEST_NODE_NAMESPACE, &allocator); + + /* Include the namespace as part of the root security directory and test root namespace */ + set_base_lookup_dir_fqn(TEST_RESOURCES_DIRECTORY, TEST_SECURITY_DIRECTORY_RESOURCES_DIR_NAME); + /* -------------------------- + * Namespace : Root + * Match type : Prefix + * -------------------------- + * Root dir: ${CMAKE_BINARY_DIR}/tests/resources/test_security_directory + * Namespace: / + * Node: dummy_node_and_some_suffix_added */ + root_path = rcutils_get_secure_root(TEST_NODE_NAME "_and_some_suffix_added", + ROOT_NAMESPACE, &allocator); + ASSERT_STREQ(root_path, secure_root); +} + +TEST_F(TestGetSecureRoot, nodeSecurityDirectoryOverride_validDirectory) { + /* Specify a valid directory */ + putenv_wrapper(ROS_SECURITY_NODE_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); + root_path = rcutils_get_secure_root("name shouldn't matter", + "namespace shouldn't matter", &allocator); + ASSERT_STREQ(root_path, TEST_RESOURCES_DIRECTORY); +} + +TEST_F(TestGetSecureRoot, + nodeSecurityDirectoryOverride_validDirectory_overrideRootDirectoryAttempt) { + /* Setting root dir has no effect */ + putenv_wrapper(ROS_SECURITY_NODE_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); + root_path = rcutils_get_secure_root("name shouldn't matter", + "namespace shouldn't matter", &allocator); + putenv_wrapper(ROS_SECURITY_ROOT_DIRECTORY_VAR_NAME "=" TEST_RESOURCES_DIRECTORY); + ASSERT_STREQ(root_path, TEST_RESOURCES_DIRECTORY); +} + +TEST_F(TestGetSecureRoot, nodeSecurityDirectoryOverride_invalidDirectory) { + /* The override provided should exist. Providing correct node/namespace/root dir won't help + * if the node override is invalid. */ + putenv_wrapper( + ROS_SECURITY_NODE_DIRECTORY_VAR_NAME + "=TheresN_oWayThi_sDirectory_Exists_hence_this_would_fail"); + ASSERT_EQ(rcutils_get_secure_root(TEST_NODE_NAME, TEST_NODE_NAMESPACE, &allocator), + (char *) NULL); +}