From f24871adb2f01ef3f7cbd8a53e60939518045587 Mon Sep 17 00:00:00 2001 From: Simo Piiroinen Date: Fri, 28 May 2021 13:20:33 +0300 Subject: [PATCH 1/2] Revert "[launcherlib] Add checks for invoker" This reverts commit 88bf4689e4d1838c847fae25b448710e96b35278. --- src/launcherlib/booster.cpp | 8 ---- src/launcherlib/connection.cpp | 84 +--------------------------------- src/launcherlib/connection.h | 13 ------ 3 files changed, 2 insertions(+), 103 deletions(-) diff --git a/src/launcherlib/booster.cpp b/src/launcherlib/booster.cpp index 61c4f6c..f4008e4 100644 --- a/src/launcherlib/booster.cpp +++ b/src/launcherlib/booster.cpp @@ -61,7 +61,6 @@ Booster::Booster() : m_boostedApplication("default"), m_bootMode(false) { - Connection::setMountNamespace(Connection::getMountNamespace(getpid())); } Booster::~Booster() @@ -240,13 +239,6 @@ bool Booster::receiveDataFromInvoker(int socketFd) // Accept a new invocation. if (m_connection->accept(m_appData)) { - // Check that the caller is allowed to invoke apps - if (!m_connection->isPermitted()) - { - m_connection->close(); - return false; - } - // Receive application data from the invoker if (!m_connection->receiveApplicationData(m_appData)) { diff --git a/src/launcherlib/connection.cpp b/src/launcherlib/connection.cpp index a883885..4ca2b5e 100644 --- a/src/launcherlib/connection.cpp +++ b/src/launcherlib/connection.cpp @@ -25,8 +25,6 @@ #include #include /* for getsockopt */ #include /* for chmod */ -#include -#include #include #include #include @@ -34,10 +32,6 @@ #include #include -#define INVOKER_PATH "/usr/bin/invoker" - -std::pair Connection::s_mntNS = std::pair(); - Connection::Connection(int socketFd, bool testMode) : m_testMode(testMode), m_fd(-1), @@ -79,8 +73,9 @@ int Connection::getFd() const return m_fd; } -bool Connection::accept(AppData*) +bool Connection::accept(AppData *appData) { + (void)appData; // unused if (!m_testMode) { m_fd = ::accept(m_curSocket, NULL, NULL); @@ -113,47 +108,6 @@ void Connection::close() } } - -bool Connection::isPermitted() -{ - // Check that caller is in the same namespace and it is invoker - // and not something else. This is done to avoid sandboxed app - // that sees the socket from escaping the sandbox through booster - if (m_fd != -1) - { - pid_t pid = peerPid(); - if (pid == 0) - { - Logger::logError("Connection: %s: getting peer pid failed", __FUNCTION__); - } - else - { - // There is a possibility for a race here: if caller can exit - // and then invoke a process with the correct binary path and - // pid at the right time they could fool us - - if (!s_mntNS.first || getMountNamespace(pid) != s_mntNS) - { - Logger::logWarning("Connection: %s: invocation from %s (%d) came from different namespace", __FUNCTION__, getExecutablePath(pid), pid); - } - else - { - std::string executablePath = getExecutablePath(pid); - if (executablePath != INVOKER_PATH) - { - Logger::logWarning("Connection: %s: executable %s (%d) is not invoker", __FUNCTION__, executablePath, pid); - } - else - { - Logger::logDebug("Connection: %s: invoker (%d) called, allowing", __FUNCTION__, pid); - return true; - } - } - } - } - return false; -} - bool Connection::sendMsg(uint32_t msg) { if (!m_testMode) @@ -594,37 +548,3 @@ pid_t Connection::peerPid() return cr.pid; } - -void Connection::setMountNamespace(std::pair mntNS) -{ - s_mntNS = mntNS; -} - -std::pair Connection::getMountNamespace(pid_t pid) -{ - struct stat sb; - std::ostringstream path; - path << "/proc/" << pid << "/ns/mnt"; - if (stat(path.str().c_str(), &sb) == -1) - { - Logger::logError("Connection: %s: stat failed for pid %d: %s", __FUNCTION__, pid, strerror(errno)); - return std::pair(); - } - return std::pair(sb.st_dev, sb.st_ino); -} - -std::string Connection::getExecutablePath(pid_t pid) -{ - std::ostringstream path; - char exe[PATH_MAX]; - ssize_t len; - static_assert(sizeof(INVOKER_PATH) < sizeof(exe)); - path << "/proc/" << pid << "/exe"; - len = readlink(path.str().c_str(), exe, sizeof(exe)); - if (len < 0 || (size_t)len >= sizeof(exe)) - { - Logger::logError("Connection: %s: readlink failed for pid %d: %s", __FUNCTION__, pid, strerror(errno)); - return std::string(); - } - return std::string(exe, len); -} diff --git a/src/launcherlib/connection.h b/src/launcherlib/connection.h index 2fcb49e..6216094 100644 --- a/src/launcherlib/connection.h +++ b/src/launcherlib/connection.h @@ -84,15 +84,6 @@ public: //! \brief Send application exit value bool sendExitValue(int value); - //! \brief Check if caller is permitted to invoke apps - bool isPermitted(); - - //! \brief Get device id and inode number pair of a mount namespace - static std::pair getMountNamespace(pid_t pid); - - //! \brief Set required mount namespace for invoker processes - static void setMountNamespace(std::pair mntNS); - private: /*! \brief Receive actions. @@ -142,9 +133,6 @@ private: //! Send process pid bool sendPid(pid_t pid); - //! Helper method: Get executable path for pid - static std::string getExecutablePath(pid_t pid); - //! Send message to a socket. This is a virtual to help unit testing. virtual bool sendMsg(uint32_t msg); @@ -173,7 +161,6 @@ private: gid_t m_gid; uid_t m_uid; - static std::pair s_mntNS; #ifdef UNIT_TEST friend class Ut_Connection; From 833b551af14f6ebb51f82fe80dc8a2344513610b Mon Sep 17 00:00:00 2001 From: Simo Piiroinen Date: Fri, 28 May 2021 06:39:10 +0300 Subject: [PATCH 2/2] [mapplauncerd] Sandboxed application boosters. JB#53844 OMP#JOLLA-43 When booster is executing in sandbox as an applicatiom booster, it needs to verify that command line received from invoker matches Exec line in application desktop file, application launch is allowed, and permissions granted are as was expected at the time of booster launch. Provide booster-generic@.service that can be used for instantiating sandboxed application boosters. D-Bus ipc with sailjaild is modified version of similar code in sailjailclient. The biggest difference is that this version uses private connection via libdbus to avoid leaving stray dbus connections or threads behind when transferring control to application code without use of exec*() functions. Remove cap_sys_ptrace from booster executable as makes it impossible to run the booster within a no-new-privs sandbox. Fix socket passing from booster instance to booster daemon so that it works also when invoker is running in different namespace than booster instance (invoker pid might be unresolvable). Replace ad-hoc booster argument parsing with getopt_long(). Fix issues with argv handling: using const pointers for non-const data, passing data by reference between objects that might have different lifespans and never releasing the dynamically allocated arrays. Fix issues with env passing: duplicating invoker env at booster side as-is can lead to problems like loss of customg session bus socket address that has been set up by firejail. If booster bumps into command read problems, bailout immediately instead of relying on out-of sequence data possibly triggering exit due to unknown commands. As an enabler for sharing code between invoker (written in c) and daemon (written in c++), modify Logger class used by c++ code so that it is just a wrapper for logging functionality used by invoker. Signed-off-by: Simo Piiroinen --- README | 4 - rpm/mapplauncherd.spec | 6 +- src/booster-generic/CMakeLists.txt | 1 + src/booster-generic/booster-generic@.service | 11 + src/common/report.c | 119 ++-- src/common/report.h | 45 +- src/common/sailjail.c | 559 +++++++++++++++++++ src/common/sailjail.h | 13 + src/invoker/invokelib.c | 2 +- src/invoker/invoker.c | 40 +- src/launcherlib/CMakeLists.txt | 12 +- src/launcherlib/appdata.cpp | 23 +- src/launcherlib/appdata.h | 2 +- src/launcherlib/booster.cpp | 14 +- src/launcherlib/connection.cpp | 141 +++-- src/launcherlib/connection.h | 6 +- src/launcherlib/daemon.cpp | 202 ++++--- src/launcherlib/daemon.h | 3 +- src/launcherlib/logger.cpp | 89 +-- src/launcherlib/logger.h | 10 +- 20 files changed, 984 insertions(+), 318 deletions(-) create mode 100644 src/booster-generic/booster-generic@.service create mode 100644 src/common/sailjail.c create mode 100644 src/common/sailjail.h diff --git a/README b/README index 040ebfe..b52d32b 100644 --- a/README +++ b/README @@ -59,10 +59,6 @@ name of the application. Then the booster process waits for a connection from the invoker with the information about which application should be launched. -Before launching application boosters check that the calling process is allowed -to invoke applications. This requires CAP_SYS_PTRACE. All boosters must have -that set to function. - Contributors ============================== diff --git a/rpm/mapplauncherd.spec b/rpm/mapplauncherd.spec index 477e1b1..0765f9d 100644 --- a/rpm/mapplauncherd.spec +++ b/rpm/mapplauncherd.spec @@ -8,13 +8,13 @@ Source0: %{name}-%{version}.tar.bz2 Source1: booster-cgroup-mount.service Requires: systemd-user-session-targets Requires(post): /sbin/ldconfig -Requires(post): /usr/sbin/setcap Requires(postun): /sbin/ldconfig Requires(pre): sailfish-setup BuildRequires: pkgconfig(libshadowutils) BuildRequires: pkgconfig(systemd) BuildRequires: pkgconfig(dbus-1) BuildRequires: pkgconfig(libcap) +BuildRequires: pkgconfig(glib-2.0) BuildRequires: cmake Provides: meegotouch-applauncherd > 3.0.3 Obsoletes: meegotouch-applauncherd <= 3.0.3 @@ -75,7 +75,6 @@ install -D -m 0755 scripts/booster-cgroup-mount %{buildroot}/usr/lib/startup/boo %post /sbin/ldconfig -/usr/sbin/setcap cap_sys_ptrace+pe %{_libexecdir}/mapplauncherd/booster-generic || : %postun -p /sbin/ldconfig @@ -87,7 +86,8 @@ install -D -m 0755 scripts/booster-cgroup-mount %{buildroot}/usr/lib/startup/boo %{_bindir}/single-instance %{_libdir}/libapplauncherd.so* %attr(2755, root, privileged) %{_libexecdir}/mapplauncherd/booster-generic -%{_userunitdir}//booster-generic.service +%{_userunitdir}/booster-generic.service +%{_userunitdir}/booster-generic@.service %{_userunitdir}/user-session.target.wants/booster-generic.service %files devel diff --git a/src/booster-generic/CMakeLists.txt b/src/booster-generic/CMakeLists.txt index c26b867..bda181a 100644 --- a/src/booster-generic/CMakeLists.txt +++ b/src/booster-generic/CMakeLists.txt @@ -21,4 +21,5 @@ install(TARGETS booster-generic DESTINATION ${CMAKE_INSTALL_FULL_LIBEXECDIR}/map if(INSTALL_SYSTEMD_UNITS) install(FILES booster-generic.service DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/systemd/user/) + install(FILES booster-generic@.service DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/systemd/user/) endif() diff --git a/src/booster-generic/booster-generic@.service b/src/booster-generic/booster-generic@.service new file mode 100644 index 0000000..161bd7d --- /dev/null +++ b/src/booster-generic/booster-generic@.service @@ -0,0 +1,11 @@ +[Unit] +Description=Generic application launch booster (sandboxed) +Requires=dbus.socket booster-silica-session.path lipstick.service +After=dbus.service booster-silica-session.path lipstick.service + +[Service] +Type=simple +ExecStart=/usr/bin/invoker --type=silica-session -- /usr/bin/sailjail --profile=%i -- /usr/libexec/mapplauncherd/booster-generic --application=%i +Restart=always +RestartSec=1 +OOMScoreAdjust=-250 diff --git a/src/common/report.c b/src/common/report.c index bf6b11a..ea7f4c4 100644 --- a/src/common/report.c +++ b/src/common/report.c @@ -17,16 +17,39 @@ ** ****************************************************************************/ +#define _GNU_SOURCE + #include #include -#include -#include +#include #include #include +#include +#include #include "report.h" static enum report_output output = report_guess; +static enum report_type level = report_warning; + +static const char *progname(void) +{ + static const char *name = NULL; + if (!name) { + char buff[PATH_MAX]; + char path[PATH_MAX]; + snprintf(path, sizeof path, "/proc/%d/exe", (int)getpid()); + int n = readlink(path, buff, sizeof buff); + if (n > 0 && n < (int)sizeof buff) { + buff[n] = 0; + /* Note: this is intentionally never released */ + name = strdup(basename(buff)); + } + if (!name) + name = "unknown"; + } + return name; +} static char *strip(char *str) { @@ -49,64 +72,94 @@ static char *strip(char *str) return str; } -void report_set_output(enum report_output new_output) +extern enum report_type report_get_type(void) { - if (output == new_output) - return; + return level; +} - if (output == report_syslog) - closelog(); +static enum report_type normalize_type(enum report_type type) +{ + if (type < report_minimum) + return report_minimum; + if (type > report_maximum) + return report_maximum; + return type; +} - if (new_output == report_syslog) - openlog(PROG_NAME_INVOKER, LOG_PID, LOG_DAEMON); +extern void report_set_type(enum report_type type) +{ + level = normalize_type(type); +} - output = new_output; +enum report_output report_get_output(void) +{ + if (output == report_guess) + report_set_output(isatty(STDIN_FILENO) ? report_console : report_syslog); + return output; } -static void vreport(enum report_type type, const char *msg, va_list arg) +void report_set_output(enum report_output new_output) { - char str[400]; - char *str_type = ""; - int log_type; + if (output != new_output) { + if (output == report_syslog) + closelog(); + + output = new_output; + + if (output == report_syslog) + openlog(PROG_NAME_INVOKER, LOG_PID, LOG_DAEMON); + } +} + +void vreport(enum report_type type, const char *msg, va_list arg) +{ + /* Any errors during logging must not change errno */ + int saved = errno; + + if ((type = normalize_type(type)) > level) + goto EXIT; - switch (type) - { + char *str_type = ""; + switch (type) { case report_debug: - log_type = LOG_DEBUG; + str_type = "debug: "; break; - default: case report_info: - log_type = LOG_INFO; + str_type = "info: "; + break; + case report_notice: + str_type = "notice: "; break; case report_warning: str_type = "warning: "; - log_type = LOG_WARNING; break; case report_error: str_type = "error: "; - log_type = LOG_ERR; break; case report_fatal: str_type = "died: "; - log_type = LOG_ERR; + break; + default: break; } + char str[400]; vsnprintf(str, sizeof(str), msg, arg); - if (output == report_guess) { - if (isatty(STDIN_FILENO)) - output = report_console; - else - output = report_syslog; - } - - if (output == report_console) { - fprintf(stderr, "%s: %s%s\n", PROG_NAME_INVOKER, str_type, strip(str)); + switch (report_get_output()) { + case report_console: + fprintf(stderr, "%s: %s%s\n", progname(), str_type, strip(str)); fflush(stderr); - } else if (output == report_syslog) { - syslog(log_type, "%s%s", str_type, str); + break; + case report_syslog: + syslog(type, "%s%s", str_type, str); + break; + default: + break; } + +EXIT: + errno = saved; } void report(enum report_type type, const char *msg, ...) diff --git a/src/common/report.h b/src/common/report.h index a6f904f..6490824 100644 --- a/src/common/report.h +++ b/src/common/report.h @@ -22,6 +22,13 @@ #ifndef REPORT_H #define REPORT_H +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + #ifdef __GNUC__ #define ATTR_NORET __attribute__((noreturn)) #else @@ -32,31 +39,39 @@ enum report_output { report_guess, report_console, report_syslog, - report_none + report_none, }; enum report_type { - report_debug, - report_info, - report_warning, - report_error, - report_fatal + report_fatal = LOG_CRIT, + report_error = LOG_ERR, + report_warning = LOG_WARNING, + report_notice = LOG_NOTICE, + report_info = LOG_INFO, + report_debug = LOG_DEBUG, + report_default = report_warning, + report_minimum = report_fatal, + report_maximum = report_debug, }; +extern enum report_output report_get_output(void); extern void report_set_output(enum report_output new_output); +extern enum report_type report_get_type(void); +extern void report_set_type(enum report_type type); extern void report(enum report_type type, const char *msg, ...) __attribute__((format(printf, 2, 3))); +extern void vreport(enum report_type type, const char *msg, va_list arg); +extern void ATTR_NORET die(int status, const char *msg, ...); -#ifndef DEBUG_LOGGING_DISABLED -#define debug(msg, ...) report(report_debug, msg, ##__VA_ARGS__) -#else -#define debug(...) do {} while (0) -#endif - -#define info(msg, ...) report(report_info, msg, ##__VA_ARGS__) -#define warning(msg, ...) report(report_warning, msg, ##__VA_ARGS__) #define error(msg, ...) report(report_error, msg, ##__VA_ARGS__) +#define warning(msg, ...) report(report_warning, msg, ##__VA_ARGS__) +#define notice(msg, ...) report(report_notice, msg, ##__VA_ARGS__) +#define info(msg, ...) report(report_info, msg, ##__VA_ARGS__) +#define debug(msg, ...) report(report_debug, msg, ##__VA_ARGS__) -extern void ATTR_NORET die(int status, const char *msg, ...); +#define HERE warning("%s:%d: %s() ...", __FILE__, __LINE__, __func__); +#ifdef __cplusplus +}; #endif +#endif // REPORT_H diff --git a/src/common/sailjail.c b/src/common/sailjail.c new file mode 100644 index 0000000..cad0674 --- /dev/null +++ b/src/common/sailjail.c @@ -0,0 +1,559 @@ +#include "sailjail.h" +#include "report.h" + +#include +#include + +#include + +/* Standard desktop properties */ +#define DESKTOP_SECTION "Desktop Entry" +#define DESKTOP_KEY_NAME "Name" +#define DESKTOP_KEY_TYPE "Type" +#define DESKTOP_KEY_ICON "Icon" +#define DESKTOP_KEY_EXEC "Exec" +#define DESKTOP_KEY_NO_DISPLAY "NoDisplay" + +/* Maemo desktop properties */ +#define MAEMO_SECTION "Desktop Entry" +#define MAEMO_KEY_SERVICE "X-Maemo-Service" +#define MAEMO_KEY_OBJECT "X-Maemo-Object-Path" +#define MAEMO_KEY_METHOD "X-Maemo-Method" + +/* Sailjail desktop properties */ +#define SAILJAIL_SECTION_PRIMARY "X-Sailjail" +#define SAILJAIL_SECTION_SECONDARY "Sailjail" +#define SAILJAIL_KEY_ORGANIZATION_NAME "OrganizationName" +#define SAILJAIL_KEY_APPLICATION_NAME "ApplicationName" +#define SAILJAIL_KEY_PERMISSIONS "Permissions" +#define NEMO_KEY_APPLICATION_TYPE "X-Nemo-Application-Type" +#define NEMO_KEY_SINGLE_INSTANCE "X-Nemo-Single-Instance" +#define MAEMO_KEY_FIXED_ARGS "X-Maemo-Fixed-Args" +#define OSSO_KEY_SERVICE "X-Osso-Service" + +/* Sailjaild D-Bus service */ +#define PERMISSIONMGR_SERVICE "org.sailfishos.sailjaild1" +#define PERMISSIONMGR_INTERFACE "org.sailfishos.sailjaild1" +#define PERMISSIONMGR_OBJECT "/org/sailfishos/sailjaild1" +#define PERMISSIONMGR_METHOD_PROMPT "PromptLaunchPermissions" +#define PERMISSIONMGR_METHOD_QUERY "QueryLaunchPermissions" +#define PERMISSIONMGR_METHOD_GET_APPLICATIONS "GetApplications" +#define PERMISSIONMGR_METHOD_GET_APPINFO "GetAppInfo" +#define PERMISSIONMGR_METHOD_GET_LICENSE "GetLicenseAgreed" +#define PERMISSIONMGR_METHOD_SET_LICENSE "SetLicenseAgreed" +#define PERMISSIONMGR_METHOD_GET_LAUNCHABLE "GetLaunchAllowed" +#define PERMISSIONMGR_METHOD_SET_LAUNCHABLE "SetLaunchAllowed" +#define PERMISSIONMGR_METHOD_GET_GRANTED "GetGrantedPermissions" +#define PERMISSIONMGR_METHOD_SET_GRANTED "SetGrantedPermissions" +#define PERMISSIONMGR_SIGNAL_APP_ADDED "ApplicationAdded" +#define PERMISSIONMGR_SIGNAL_APP_CHANGED "ApplicationChanged" +#define PERMISSIONMGR_SIGNAL_APP_REMOVED "ApplicationRemoved" + +static DBusConnection * +sailjail_connect_bus(void) +{ + DBusError err = DBUS_ERROR_INIT; + DBusConnection *con = 0; + + if (!(con = dbus_bus_get_private(DBUS_BUS_SYSTEM, &err))) { + error("system bus connect failed: %s: %s", + err.name, err.message); + } else { + warning("PRIVATE CONNECTION %p CONNECTED", con); + dbus_connection_set_exit_on_disconnect(con, false); + } + + dbus_error_free(&err); + return con; +} + +static void +sailjail_disconnect_bus(DBusConnection *con) +{ + if (con) { + warning("PRIVATE CONNECTION %p DISCONNECTED", con); + dbus_connection_close(con); + dbus_connection_unref(con); + } +} + +static GVariant * +appinfo_variant(GHashTable *info, const char *key) +{ + return g_hash_table_lookup(info, key); +} + +static const char * +appinfo_string(GHashTable *info, const char *key) +{ + const char *value = NULL; + GVariant *variant = appinfo_variant(info, key); + if (variant) { + const GVariantType *type = g_variant_get_type(variant); + if (g_variant_type_equal(type, G_VARIANT_TYPE_STRING)) + value = g_variant_get_string(variant, NULL); + } + return value; +} + +static const char ** +appinfo_strv(GHashTable *info, const char *key) +{ + const char **value = NULL; + GVariant *variant = appinfo_variant(info, key); + if (variant) { + const GVariantType *type = g_variant_get_type(variant); + if (g_variant_type_equal(type, G_VARIANT_TYPE("as"))) + value = g_variant_get_strv(variant, NULL); + } + return value; +} + +static bool +iter_at(DBusMessageIter *iter, int type) +{ + int have = dbus_message_iter_get_arg_type(iter); + if (have != type && type != 0) + warning("Expected: %c got: %c", type ?: '?', have ?: '?'); + return have == type; +} + +static GHashTable * +sailjail_application_info(DBusConnection *con, const char *desktop) +{ + GHashTable *info = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref); + DBusError err = DBUS_ERROR_INIT; + DBusMessage *req = NULL; + DBusMessage *rsp = NULL; + + if (!(req = dbus_message_new_method_call(PERMISSIONMGR_SERVICE, + PERMISSIONMGR_OBJECT, + PERMISSIONMGR_INTERFACE, + PERMISSIONMGR_METHOD_GET_APPINFO))) { + error("failed to create dbus method call"); + goto EXIT; + } + + if (!dbus_message_append_args(req, DBUS_TYPE_STRING, &desktop, DBUS_TYPE_INVALID)) { + error("failed to add method call args"); + goto EXIT; + } + + if (!(rsp = dbus_connection_send_with_reply_and_block(con, req, DBUS_TIMEOUT_INFINITE, &err))) { + error("method call failed: %s: %s", err.name, err.message); + goto EXIT; + } + + if (dbus_set_error_from_message(&err, rsp)) { + error("error reply received: %s: %s", err.name, err.message); + goto EXIT; + } + + DBusMessageIter bodyIter; + if (!dbus_message_iter_init(rsp, &bodyIter)) { + error("empty reply received"); + goto EXIT; + } + + if (!iter_at(&bodyIter, DBUS_TYPE_ARRAY)) { + error("reply is not an array"); + goto EXIT; + } + + DBusMessageIter ArrayIter; + dbus_message_iter_recurse(&bodyIter, &ArrayIter); + while (!iter_at(&ArrayIter, DBUS_TYPE_INVALID)) { + if (!iter_at(&ArrayIter, DBUS_TYPE_DICT_ENTRY)) { + error("reply is not an array of dict entries"); + goto EXIT; + } + DBusMessageIter dictIter; + dbus_message_iter_recurse(&ArrayIter, &dictIter); + dbus_message_iter_next(&ArrayIter); + if (!iter_at(&dictIter, DBUS_TYPE_STRING)) { + error("key is not a string"); + goto EXIT; + } + const char *key = NULL; + dbus_message_iter_get_basic(&dictIter, &key); + dbus_message_iter_next(&dictIter); + if (!iter_at(&dictIter, DBUS_TYPE_VARIANT)) { + error("values is not a variant"); + goto EXIT; + } + DBusMessageIter variantIter; + dbus_message_iter_recurse(&dictIter, &variantIter); + dbus_message_iter_next(&dictIter); + DBusBasicValue value; + switch (dbus_message_iter_get_arg_type(&variantIter)) { + case DBUS_TYPE_INT32: + dbus_message_iter_get_basic(&variantIter, &value); + warning("%s = int32:%d", key, value.i32); + g_hash_table_insert(info, g_strdup(key), g_variant_new_int32(value.i32)); + break; + case DBUS_TYPE_UINT32: + dbus_message_iter_get_basic(&variantIter, &value); + warning("%s = uint32:%d", key, value.u32); + g_hash_table_insert(info, g_strdup(key), g_variant_new_uint32(value.u32)); + break; + case DBUS_TYPE_BOOLEAN: + dbus_message_iter_get_basic(&variantIter, &value); + warning("%s = bool:%d", key, value.bool_val); + g_hash_table_insert(info, g_strdup(key), g_variant_new_boolean(value.bool_val)); + break; + case DBUS_TYPE_STRING: + dbus_message_iter_get_basic(&variantIter, &value); + warning("%s = string:'%s'", key, value.str); + g_hash_table_insert(info, g_strdup(key), g_variant_new_string(value.str)); + break; + case DBUS_TYPE_ARRAY: + if (dbus_message_iter_get_element_type(&variantIter) != DBUS_TYPE_STRING) { + error("only arrays of strings are supported"); + } else { + int n = dbus_message_iter_get_element_count(&variantIter); + DBusMessageIter valueIter; + dbus_message_iter_recurse(&variantIter, &valueIter); + char **v = g_malloc0_n(n + 1, sizeof *v); + int i = 0; + while (i < n) { + if (!iter_at(&valueIter, DBUS_TYPE_STRING)) + break; + dbus_message_iter_get_basic(&valueIter, &value); + warning("%s[%d] = string:'%s'", key, i, value.str); + + dbus_message_iter_next(&valueIter); + v[i++] = g_strdup(value.str); + } + v[i] = NULL; + g_hash_table_insert(info, g_strdup(key), g_variant_new_strv((const gchar *const *)v, i)); + g_strfreev(v); + } + break; + default: + warning("reply contains unhandled variant types"); + break; + } + } + +EXIT: + if (info && g_hash_table_size(info) < 1) { + /* There should always be at least app id key. If we get + * empty hash table, either parser is not working or there + * are other kinds of problems. + */ + error("no information about application '%s'", desktop); + g_hash_table_destroy(info), info = NULL; + } + dbus_error_free(&err); + if (rsp) + dbus_message_unref(rsp); + if (req) + dbus_message_unref(req); + warning("info received = %s", info ? "true" : "false"); + return info; +} + +static char ** +sailjail_prompt_permissions(DBusConnection *con, const char *desktop) +{ + char **granted = NULL; + DBusError err = DBUS_ERROR_INIT; + DBusMessage *req = NULL; + DBusMessage *rsp = NULL; + int len = 0; + + if (!(req = dbus_message_new_method_call(PERMISSIONMGR_SERVICE, + PERMISSIONMGR_OBJECT, + PERMISSIONMGR_INTERFACE, + PERMISSIONMGR_METHOD_PROMPT))) { + error("failed to create dbus method call"); + goto EXIT; + } + + if (!dbus_message_append_args(req, + DBUS_TYPE_STRING, &desktop, + DBUS_TYPE_INVALID)) { + error("failed to add method call args"); + goto EXIT; + } + + if (!(rsp = dbus_connection_send_with_reply_and_block(con, req, DBUS_TIMEOUT_INFINITE, &err))) { + error("method call failed: %s: %s", err.name, err.message); + goto EXIT; + } + + if (dbus_set_error_from_message(&err, rsp)) { + error("error reply received: %s: %s", err.name, err.message); + goto EXIT; + } + + if (!dbus_message_get_args(rsp, &err, + DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &granted, &len, + DBUS_TYPE_INVALID)) { + error("parsing reply failed: %s: %s", err.name, err.message); + dbus_free_string_array(granted), granted = NULL; + goto EXIT; + } + +EXIT: + dbus_error_free(&err); + if (rsp) + dbus_message_unref(rsp); + if (req) + dbus_message_unref(req); + + warning("launch permitted = %s", granted ? "true" : "false"); + return granted; +} + +static int +sailjailclient_get_field_code(const char *arg) +{ + // Non-null string starting with a '%' followed by exactly one character + return arg && arg[0] == '%' && arg[1] && !arg[2] ? arg[1] : 0; +} + +static bool +sailjailclient_is_option(const char *arg) +{ + // Non-null string starting with a hyphen + return arg && arg[0] == '-'; +} + +static bool +sailjailclient_ignore_arg(const char *arg) +{ + return !g_strcmp0(arg, "-prestart"); +} + +static bool +sailjailclient_match_argv(const char **tpl_argv, const char **app_argv) +{ + bool matching = false; + + /* Rule out template starting with a field code */ + if (sailjailclient_get_field_code(*tpl_argv)) { + error("Exec line starts with field code"); + goto EXIT; + } + + /* Match each arg in template */ + for (;;) { + const char *want = *tpl_argv++; + + /* Allow some slack e.g. regarding "-prestart" options */ + while (*app_argv && g_strcmp0(*app_argv, want) && + sailjailclient_ignore_arg(*app_argv)) { + warning("ignoring argument: %s", *app_argv); + ++app_argv; + } + + if (!want) { + /* Template args exhausted */ + if (*app_argv) { + /* Excess application args */ + error("argv has unwanted '%s'", *app_argv); + goto EXIT; + } + break; + } + + int field_code = sailjailclient_get_field_code(want); + + if (!field_code) { + /* Exact match needed */ + if (g_strcmp0(*app_argv, want)) { + /* Application args has something else */ + error("argv is missing '%s'", want); + goto EXIT; + } + ++app_argv; + continue; + } + + /* Field code explanations from "Desktop Entry Specification" + * + * https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables + */ + int code_args = 0; + switch (field_code) { + case 'f': /* A single file name (or none) */ + case 'u': /* A single URL (or none) */ + code_args = -1; + break; + case 'c': /* The translated name of the application */ + case 'k': /* The location of the desktop file */ + code_args = 1; + break; + case 'F': /* A list of files */ + case 'U': /* A list of URLs */ + code_args = INT_MIN; + break; + case 'i': + /* The Icon key of the desktop entry expanded as two + * arguments, first --icon and then the value of the + * Icon key. Should not expand to any arguments if + * the Icon key is empty or missing. + */ + if (!g_strcmp0(*app_argv, "--icon")) + ++app_argv, code_args = 1; + break; + case 'd': + case 'D': + case 'n': + case 'N': + case 'v': + case 'm': + /* Deprecated */ + error("Exec line has deprecated field code '%s'", want); + goto EXIT; + default: + /* Unknown */ + error("Exec line has unknown field code '%s'", want); + goto EXIT; + } + + if (code_args < 0) { + /* Variable number of args */ + if (sailjailclient_get_field_code(*tpl_argv)) { + error("Can't validate '%s %s' combination", want, *tpl_argv); + goto EXIT; + } + for (; code_args < 0; ++code_args) { + if (!*app_argv || !g_strcmp0(*app_argv, *tpl_argv)) + break; + if (sailjailclient_is_option(*app_argv)) { + error("option '%s' at field code '%s'", *app_argv, want); + goto EXIT; + } + ++app_argv; + } + } else { + /* Specified number of args */ + for (; code_args > 0; --code_args) { + if (!*app_argv) { + error("missing args for field code '%s'", want); + goto EXIT; + } + if (sailjailclient_is_option(*app_argv)) { + error("option '%s' at field code '%s'", *app_argv, want); + goto EXIT; + } + ++app_argv; + } + } + } + + matching = true; + +EXIT: + return matching; +} + +static bool +sailjailclient_validate_argv(const char *exec, const gchar **app_argv) +{ + bool validated = false; + GError *err = NULL; + gchar **exec_argv = NULL; + + if (!app_argv || !*app_argv) { + error("application argv not defined"); + goto EXIT; + } + + /* Split desktop Exec line into argv */ + if (!g_shell_parse_argv(exec, NULL, &exec_argv, &err)) { + error("Exec line parse failure: %s", err->message); + goto EXIT; + } + + if (!exec_argv || !*exec_argv) { + error("Exec line not defined"); + goto EXIT; + } + + /* Expectation: Exec line might have leading 'wrapper' executables + * such as sailjail, invoker, etc -> make an attempt to skip those + * by looking for argv[0] for command we are about to launch. + */ + const char **tpl_argv = (const char **)exec_argv; + for (; *tpl_argv; ++tpl_argv) { + if (!g_strcmp0(*tpl_argv, app_argv[0])) + break; + } + + if (!*tpl_argv) { + error("Exec line does not contain '%s'", *app_argv); + goto EXIT; + } + + if (!sailjailclient_match_argv(tpl_argv, app_argv)) { + gchar *args = g_strjoinv(" ", (gchar **)app_argv); + error("Application args do not match Exec line template"); + error("exec: %s", exec); + error("args: %s", args); + g_free(args); + goto EXIT; + } + + validated = true; + +EXIT: + g_strfreev(exec_argv); + g_clear_error(&err); + + return validated; +} + +bool +sailjail_verify_launch(const char *desktop, const char **argv) +{ + bool allowed = false; + DBusConnection *con = NULL; + char **granted = NULL; + GHashTable *info = NULL; + const char **requested = NULL; + const char *exec = NULL; + + if (!(con = sailjail_connect_bus())) + goto EXIT; + + if (!(info = sailjail_application_info(con, desktop))) + goto EXIT; + + if (!(exec = appinfo_string(info, DESKTOP_KEY_EXEC))) { + error("no Exec line defined for application '%s'", desktop); + goto EXIT; + } + + if (!sailjailclient_validate_argv(exec, argv)) + goto EXIT; + + if (!(requested = appinfo_strv(info, SAILJAIL_KEY_PERMISSIONS))) { + error("no permissions defined for application '%s'", desktop); + goto EXIT; + } + + debug("prompting permissions for application '%s'", desktop); + if (!(granted = sailjail_prompt_permissions(con, desktop))) + goto EXIT; + + for (int i = 0; requested[i]; ++i) { + if (!g_strv_contains((const gchar *const *)granted, requested[i])) { + error("application '%s' has not been granted '%s' permission", + desktop, requested[i]); + goto EXIT; + } + } + + allowed = true; + +EXIT: + if (info) + g_hash_table_destroy(info); + g_strfreev(granted); + sailjail_disconnect_bus(con); + + return allowed; +} diff --git a/src/common/sailjail.h b/src/common/sailjail.h new file mode 100644 index 0000000..ae1d095 --- /dev/null +++ b/src/common/sailjail.h @@ -0,0 +1,13 @@ +#ifndef SAILJAIL_H_ +# define SAILJAIL_H_ + +# include +# include + +G_BEGIN_DECLS + +bool sailjail_verify_launch(const char *desktop, const char **argv); + +G_END_DECLS + +#endif // SAILJAIL_H_ diff --git a/src/invoker/invokelib.c b/src/invoker/invokelib.c index 2de5013..6a278d7 100644 --- a/src/invoker/invokelib.c +++ b/src/invoker/invokelib.c @@ -53,7 +53,7 @@ bool invoke_recv_msg(int fd, uint32_t *msg) if (numRead == -1) { - debug("%s: Error reading message: %s\n", __FUNCTION__, strerror(errno)); + debug("%s: Error reading message: %m\n", __FUNCTION__); *msg = 0; return false; } diff --git a/src/invoker/invoker.c b/src/invoker/invoker.c index a06910c..90e6152 100644 --- a/src/invoker/invoker.c +++ b/src/invoker/invoker.c @@ -49,11 +49,14 @@ #include "invokelib.h" #include "search.h" +#define BOOSTER_SESSION "silica-session" +#define BOOSTER_GENERIC "generic" + /* Setting VERBOSE_SIGNALS to non-zero value logs receiving of * async-signals - which is useful only when actively debugging * booster / invoker interoperation. */ -#define VERBOSE_SIGNALS 0 +#define VERBOSE_SIGNALS 01 // Utility functions static char *strip(char *str) @@ -223,7 +226,7 @@ static bool shutdown_socket(int socket_fd) * end too. */ - debug("trying to disconnect booster socket...\n"); + info("trying to disconnect booster socket...\n"); if (shutdown(socket_fd, SHUT_WR) == -1) { warning("socket shutdown failed: %m\n"); @@ -243,7 +246,7 @@ static bool shutdown_socket(int socket_fd) .revents = 0, }; - debug("waiting for booster socket input...\n"); + info("waiting for booster socket input...\n"); int rc = poll(&pfd, 1, (int)(timeout - elapsed)); if (rc == 0) @@ -273,7 +276,7 @@ static bool shutdown_socket(int socket_fd) EXIT: if (disconnected) - debug("booster socket was succesfully disconnected\n"); + info("booster socket was succesfully disconnected\n"); else warning("could not disconnect booster socket\n"); @@ -314,7 +317,7 @@ static void kill_application(pid_t pid) FAIL: if (errno == ESRCH) - debug("application (pid=%d) has exited", (int)pid); + info("application (pid=%d) has exited", (int)pid); else warning("application (pid=%d) kill failed: %m", (int)pid); @@ -340,6 +343,8 @@ static bool invoke_recv_ack(int fd) // Inits a socket connection for the given application type static int invoker_init(const char *app_type, const char *app_name) { + info("try type=%s app=%s ...", app_type, app_name); + bool connected = false; int fd = -1; @@ -388,7 +393,7 @@ static int invoker_init(const char *app_type, const char *app_name) goto EXIT; } - debug("connected to: %s\n", sun.sun_path); + info("connected to: %s\n", sun.sun_path); connected = true; EXIT: @@ -469,7 +474,7 @@ static void invoker_send_args(int fd, int argc, char **argv) invoke_send_msg(fd, argc); for (i = 0; i < argc; i++) { - debug("param %d %s \n", i, argv[i]); + info("param %d %s \n", i, argv[i]); invoke_send_str(fd, argv[i]); } } @@ -653,7 +658,7 @@ static int wait_for_launched_process_to_exit(int socket_fd) // coverity[tainted_string_return_content] g_invoked_pid = invoker_recv_pid(socket_fd); - debug("Booster's pid is %d \n ", g_invoked_pid); + info("Booster's pid is %d \n ", g_invoked_pid); // Setup signal handlers sigs_init(); @@ -715,7 +720,7 @@ static int wait_for_launched_process_to_exit(int socket_fd) warning("application (pid=%d) exit(%d) signal(%d)\n", (int)g_invoked_pid, exit_status, exit_signal); else - debug("application (pid=%d) exit(%d) signal(%d)\n", + info("application (pid=%d) exit(%d) signal(%d)\n", (int)g_invoked_pid, exit_status, exit_signal); if (socket_fd != -1) { @@ -853,7 +858,7 @@ static int invoke(InvokeArgs *args) bool tried_session = false; for (size_t i = 0; !tried_session && types[i]; ++i) { - if (strcmp(types[i], "session")) + if (strcmp(types[i], BOOSTER_SESSION)) continue; tried_session = true; fd = invoker_init(types[i], NULL); @@ -866,12 +871,12 @@ static int invoke(InvokeArgs *args) if (fd == -1 && !tried_session) { bool tried_generic = false; for (size_t i = 0; fd == -1 && types[i]; ++i) { - if (!strcmp(types[i], "generic")) + if (!strcmp(types[i], BOOSTER_GENERIC)) tried_generic = true; fd = invoker_init(types[i], args->app_name); } if (fd == -1 && !tried_generic) - fd = invoker_init("generic", args->app_name); + fd = invoker_init(BOOSTER_GENERIC, args->app_name); } if (fd != -1) { @@ -931,6 +936,7 @@ int main(int argc, char *argv[]) {"splash", required_argument, NULL, 'S'}, {"splash-landscape", required_argument, NULL, 'L'}, {"desktop-file", required_argument, NULL, 'F'}, + {"verbose", no_argument, NULL, 'v'}, {0, 0, 0, 0} }; @@ -938,7 +944,7 @@ int main(int argc, char *argv[]) // The use of + for POSIXLY_CORRECT behavior is a GNU extension, but avoids polluting // the environment int opt; - while ((opt = getopt_long(argc, argv, "+hcwnGDsoTd:t:a:r:S:L:F:", longopts, NULL)) != -1) + while ((opt = getopt_long(argc, argv, "+hvcwnGDsoTd:t:a:r:S:L:F:", longopts, NULL)) != -1) { switch(opt) { @@ -946,6 +952,10 @@ int main(int argc, char *argv[]) usage(0); break; + case 'v': + report_set_type(report_get_type() + 1); + break; + case 'w': // nothing to do, it's by default now break; @@ -1077,10 +1087,10 @@ int main(int argc, char *argv[]) // Sleep for delay before exiting if (args.exit_delay) { // DBUS cannot cope some times if the invoker exits too early. - debug("Delaying exit for %d seconds..\n", args.exit_delay); + info("Delaying exit for %d seconds..\n", args.exit_delay); sleep(args.exit_delay); } - debug("invoker exit(%d)\n", ret_val); + info("invoker exit(%d)\n", ret_val); return ret_val; } diff --git a/src/launcherlib/CMakeLists.txt b/src/launcherlib/CMakeLists.txt index 5396614..df440a9 100644 --- a/src/launcherlib/CMakeLists.txt +++ b/src/launcherlib/CMakeLists.txt @@ -5,23 +5,28 @@ set(COMMON ${CMAKE_HOME_DIRECTORY}/src/common) # Find systemd find_package(PkgConfig REQUIRED) pkg_check_modules(SYSTEMD "libsystemd" REQUIRED) +pkg_check_modules(DBUS dbus-1 REQUIRED) +pkg_check_modules(GLIB glib-2.0 REQUIRED) # Set include dirs -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${SYSTEMD_INCLUDE_DIRS} ${COMMON}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${SYSTEMD_INCLUDE_DIRS} ${GLIB_INCLUDE_DIRS} ${DBUS_INCLUDE_DIRS} ${COMMON}) # Hide all symbols except the ones explicitly exported in the code (like main()) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden") # Set sources set(SRC appdata.cpp booster.cpp connection.cpp daemon.cpp logger.cpp - singleinstance.cpp socketmanager.cpp) + singleinstance.cpp socketmanager.cpp + ../common/report.c + ../common/sailjail.c) set(HEADERS appdata.h booster.h connection.h daemon.h logger.h launcherlib.h singleinstance.h socketmanager.h ${COMMON}/protocol.h) # Set libraries to be linked. Shared libraries to be preloaded are not linked in anymore, # but dlopen():ed and listed in src/launcher/preload.h instead. -link_libraries(${LIBDL} "-L/lib -lcap") +link_libraries(${GLIB_LDFLAGS} ${DBUS_LDFLAGS} ${LIBDL} "-L/lib -lsystemd -lcap") # Set executable add_library(applauncherd MODULE ${SRC} ${MOC_SRC}) @@ -29,7 +34,6 @@ set_target_properties(applauncherd PROPERTIES VERSION 0.1 SOVERSION 0) target_link_libraries(applauncherd ${SYSTEMD_LIBRARIES}) - # Add install rule install(TARGETS applauncherd DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}) install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/applauncherd diff --git a/src/launcherlib/appdata.cpp b/src/launcherlib/appdata.cpp index efc5b3c..4e9b8eb 100644 --- a/src/launcherlib/appdata.cpp +++ b/src/launcherlib/appdata.cpp @@ -24,6 +24,7 @@ #include #include #include +#include AppData::AppData() : m_options(0), @@ -71,7 +72,9 @@ bool AppData::disableOutOfMemAdj() const void AppData::setArgc(int newArgc) { - m_argc = newArgc; + (void)newArgc; // unused + // kept for the sake of binary compatibility + // setArgv() sets also m_argc } int AppData::argc() const @@ -81,12 +84,25 @@ int AppData::argc() const void AppData::setArgv(const char ** newArgv) { - m_argv = newArgv; + for (int i = 0; i < m_argc; ++i) + free(m_argv[i]); + free(m_argv); + m_argc = 0; + m_argv = nullptr; + + if (newArgv) { + while (newArgv[m_argc]) + ++m_argc; + m_argv = (char **)calloc(m_argc + 1, sizeof *m_argv); + for (int i = 0; i < m_argc; ++i) + m_argv[i] = strdup(newArgv[i]); + m_argv[m_argc] = nullptr; + } } const char ** AppData::argv() const { - return m_argv; + return (const char **)m_argv; } void AppData::setAppName(const string & newAppName) @@ -251,4 +267,5 @@ string AppData::privileges() const AppData::~AppData() { + setArgv(nullptr); } diff --git a/src/launcherlib/appdata.h b/src/launcherlib/appdata.h index d840674..6a9ab08 100644 --- a/src/launcherlib/appdata.h +++ b/src/launcherlib/appdata.h @@ -135,7 +135,7 @@ private: uint32_t m_options; int m_argc; - const char ** m_argv; + char **m_argv; string m_appName; string m_fileName; int m_prio; diff --git a/src/launcherlib/booster.cpp b/src/launcherlib/booster.cpp index f4008e4..98e7436 100644 --- a/src/launcherlib/booster.cpp +++ b/src/launcherlib/booster.cpp @@ -25,6 +25,7 @@ #include "singleinstance.h" #include "socketmanager.h" #include "logger.h" +#include "report.h" #include #include @@ -50,8 +51,12 @@ #include #include +#include + #include "coverage.h" +#include "sailjail.h" + Booster::Booster() : m_appData(new AppData), m_connection(NULL), @@ -210,11 +215,13 @@ void Booster::sendDataToParent() cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(int)); memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); + debug("send to daemon: pid=%d delay=%d fd=%d", (int)pid, delay, fd); } else { msg.msg_control = NULL; msg.msg_controllen = 0; + debug("send to daemon: pid=%d delay=%d fd=NA", (int)pid, delay); } if (sendmsg(boosterLauncherSocket(), &msg, 0) < 0) @@ -277,6 +284,11 @@ int Booster::run(SocketManager * socketManager) throw std::runtime_error("Booster: Binary doesn't have execute permissions\n"); } + if (boostedApplication() != "default") { + if (!sailjail_verify_launch(boostedApplication().c_str(), m_appData->argv())) + throw std::runtime_error("Booster: Binary doesn't have launch permissions\n"); + } + return launchProcess(); } catch (const std::runtime_error &e) { Logger::logError("Booster: Failed to invoke: %s\n", e.what()); @@ -634,7 +646,6 @@ const string Booster::socketId() const return id; } - pid_t Booster::invokersPid() { if (m_connection->isReportAppExitStatusNeeded()) @@ -718,3 +729,4 @@ std::string Booster::getFinalName(const std::string &name) } return name; } + diff --git a/src/launcherlib/connection.cpp b/src/launcherlib/connection.cpp index 4ca2b5e..509bd4b 100644 --- a/src/launcherlib/connection.cpp +++ b/src/launcherlib/connection.cpp @@ -21,6 +21,7 @@ #include "connection.h" #include "logger.h" +#include "report.h" #include #include /* for getsockopt */ @@ -65,6 +66,12 @@ Connection::~Connection() m_io[i] = -1; } } + + for (int i = 0; i < m_argc; ++i) + delete[] m_argv[i]; + free(m_argv); + m_argc = 0; + m_argv = nullptr; } @@ -148,7 +155,7 @@ bool Connection::recvMsg(uint32_t *msg) } } -const char * Connection::recvStr() +char *Connection::recvStr() { if (!m_testMode) { @@ -238,7 +245,7 @@ string Connection::receiveAppName() return string(); } - const char* name = recvStr(); + char *name = recvStr(); if (!name) { Logger::logError("Connection: receiving application name"); @@ -252,7 +259,7 @@ string Connection::receiveAppName() bool Connection::receiveExec() { - const char* filename = recvStr(); + char *filename = recvStr(); if (!filename) return false; @@ -282,51 +289,36 @@ bool Connection::receiveIDs() bool Connection::receiveArgs() { - // Get argc - recvMsg(&m_argc); const uint32_t argMax = 1024; - if (m_argc > 0 && m_argc < argMax) - { - // Reserve memory for argv - m_argv = new const char * [m_argc]; - if (!m_argv) - { - Logger::logError("Connection: reserving memory for argv"); - return false; - } - // Get argv - for (uint i = 0; i < m_argc; i++) - { - m_argv[i] = recvStr(); - if (!m_argv[i]) - { - Logger::logError("Connection: receiving argv[%i]", i); - return false; - } - } - } - else - { + // Clear current args + for (int i = 0; i < m_argc; ++i) + delete[] m_argv[i]; + free(m_argv); + m_argc = 0; + m_argv = nullptr; + + // Get argc + uint32_t argc = 0; + recvMsg(&argc); + if (argc < 1 || argc > argMax) { Logger::logError("Connection: invalid number of parameters %d", m_argc); return false; } + m_argc = argc; + m_argv = (char **)calloc(m_argc + 1, sizeof *m_argv); + for (int i = 0; i < m_argc; ++i) { + if (!(m_argv[i] = recvStr())) { + m_argc = i; + Logger::logError("Connection: receiving argv[%i]", i); + return false; + } + } + m_argv[m_argc] = nullptr; return true; } -// coverity[ +tainted_string_sanitize_content : arg-0 ] -bool putenv_sanitize(const char * s) -{ - return static_cast(strchr(s, '=')); -} - -// coverity[ +free : arg-0 ] -int putenv_wrapper(char * var) -{ - return putenv(var); -} - bool Connection::receiveEnv() { // Have some "reasonable" limit for environment variables to protect from @@ -341,30 +333,28 @@ bool Connection::receiveEnv() // Get environment variables for (uint32_t i = 0; i < n_vars; i++) { - const char * var = recvStr(); + char *var = recvStr(); if (var == NULL) { Logger::logError("Connection: receiving environ[%i]", i); return false; } - - // In case of error, just warn and try to continue, as the other side is - // going to keep sending the reset of the message. - // String pointed to by var shall become part of the environment, so altering - // the string shall change the environment, don't free it - if (putenv_sanitize(var)) - { - if (putenv_wrapper(const_cast(var)) != 0) - { - Logger::logWarning("Connection: putenv failed"); + char *val = strchr(var, '='); + if (val) { + *val++ = 0; + const char *cur = getenv(var); + /* Note: DBUS_SESSION_BUS_ADDRESS is a special case. If we + * are running in sandbox, we already have non-standard path + * that firejail has placed in env. + */ + if (!cur || strcmp(var, "DBUS_SESSION_BUS_ADDRESS")) { + if (!cur || strcmp(cur, val)) { + info("ENV: $%s: %s -> %s", var, cur ?: "n/a", val); + setenv(var, val, true); + } } } - else - { - delete [] var; - var = NULL; - Logger::logWarning("Connection: invalid environment data"); - } + delete [] var; } } else @@ -430,43 +420,49 @@ bool Connection::receiveActions() { Logger::logDebug("Connection: enter: %s", __FUNCTION__); - while (1) + for (;;) { uint32_t action = 0; // Get the action. - recvMsg(&action); + if (!recvMsg(&action)) + return false; switch (action) { case INVOKER_MSG_EXEC: - receiveExec(); + if (!receiveExec()) + return false; break; case INVOKER_MSG_ARGS: - receiveArgs(); + if (!receiveArgs()) + return false; break; case INVOKER_MSG_ENV: - // Clean-up all the env variables - clearenv(); - receiveEnv(); + if (!receiveEnv()) + return false; break; case INVOKER_MSG_PRIO: - receivePriority(); + if (!receivePriority()) + return false; break; case INVOKER_MSG_DELAY: - receiveDelay(); + if (!receiveDelay()) + return false; break; case INVOKER_MSG_IO: - receiveIO(); + if (!receiveIO()) + return false; break; case INVOKER_MSG_IDS: - receiveIDs(); + if (!receiveIDs()) + return false; break; case INVOKER_MSG_SPLASH: @@ -478,11 +474,10 @@ bool Connection::receiveActions() return false; case INVOKER_MSG_END: - sendMsg(INVOKER_MSG_ACK); - - if (m_sendPid) - sendPid(getpid()); - + if (!sendMsg(INVOKER_MSG_ACK)) + return false; + if (m_sendPid && !sendPid(getpid())) + return false; return true; default: @@ -517,7 +512,7 @@ bool Connection::receiveApplicationData(AppData* appData) appData->setPriority(m_priority); appData->setDelay(m_delay); appData->setArgc(m_argc); - appData->setArgv(m_argv); + appData->setArgv((const char **)m_argv); appData->setIODescriptors(vector(m_io, m_io + IO_DESCRIPTOR_COUNT)); appData->setIDs(m_uid, m_gid); } diff --git a/src/launcherlib/connection.h b/src/launcherlib/connection.h index 6216094..cfdafca 100644 --- a/src/launcherlib/connection.h +++ b/src/launcherlib/connection.h @@ -140,7 +140,7 @@ private: virtual bool recvMsg(uint32_t *msg); //! Receive a string. This is a virtual to help unit testing. - virtual const char * recvStr(); + virtual char *recvStr(); //! Run in test mode, if true bool m_testMode; @@ -152,8 +152,8 @@ private: int m_curSocket; string m_fileName; - uint32_t m_argc; - const char ** m_argv; + int m_argc; + char **m_argv; int m_io[IO_DESCRIPTOR_COUNT]; uint32_t m_priority; uint32_t m_delay; diff --git a/src/launcherlib/daemon.cpp b/src/launcherlib/daemon.cpp index d5c3f16..b70b377 100644 --- a/src/launcherlib/daemon.cpp +++ b/src/launcherlib/daemon.cpp @@ -21,6 +21,7 @@ #include "daemon.h" #include "logger.h" +#include "report.h" #include "connection.h" #include "booster.h" #include "singleinstance.h" @@ -46,6 +47,7 @@ #include #include #include +#include #include "coverage.h" @@ -53,7 +55,7 @@ * async-signals - which is useful only when actively debugging * booster / invoker interoperation. */ -#define VERBOSE_SIGNALS 0 +#define VERBOSE_SIGNALS 01 // Environment @@ -325,13 +327,13 @@ Daemon::Daemon(int & argc, char * argv[]) : throw std::runtime_error("Daemon: Daemon already created!\n"); } - // Parse arguments - parseArgs(ArgVect(argv, argv + argc)); - // Store arguments list m_initialArgv = argv; m_initialArgc = argc; + // Parse arguments + parseArgs(argc, argv); + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, m_boosterLauncherSocket) == -1) { throw std::runtime_error("Daemon: Creating a socket pair for boosters failed!\n"); @@ -550,63 +552,62 @@ void Daemon::run(Booster *booster) void Daemon::readFromBoosterSocket(int fd) { pid_t invokerPid = 0; - int delay = 0; - struct msghdr msg; + int delay = 0; + int socketFd = -1; + + struct iovec iov[2]; + char buf[CMSG_SPACE(sizeof socketFd)]; + struct msghdr msg; struct cmsghdr *cmsg; - struct iovec iov[2]; - char buf[CMSG_SPACE(sizeof(int))]; + + memset(iov, 0, sizeof iov); + memset(buf, 0, sizeof buf); + memset(&msg, 0, sizeof msg); iov[0].iov_base = &invokerPid; - iov[0].iov_len = sizeof(pid_t); + iov[0].iov_len = sizeof invokerPid; iov[1].iov_base = &delay; - iov[1].iov_len = sizeof(int); + iov[1].iov_len = sizeof delay; msg.msg_iov = iov; msg.msg_iovlen = 2; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_control = buf; - msg.msg_controllen = sizeof(buf); + msg.msg_controllen = sizeof buf; - if (recvmsg(fd, &msg, 0) >= 0) - { - Logger::logDebug("Daemon: boosters's pid: %d\n", m_boosterPid); - Logger::logDebug("Daemon: invoker's pid: %d\n", invokerPid); - Logger::logDebug("Daemon: respawn delay: %d \n", delay); - - int newFd = -1; + if (recvmsg(fd, &msg, 0) == -1) { + Logger::logError("Daemon: Critical error communicating with booster. Exiting applauncherd.\n"); + exit(EXIT_FAILURE); + } - if (invokerPid > 0) { - /* invokerPid got filled in at recvmsg() => we have fd too */ - cmsg = CMSG_FIRSTHDR(&msg); - memcpy(&newFd, CMSG_DATA(cmsg), sizeof(int)); - Logger::logDebug("Daemon: socket file descriptor: %d\n", newFd); + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS && + cmsg->cmsg_len >= CMSG_LEN(sizeof(socketFd))) { + memcpy(&socketFd, CMSG_DATA(cmsg), sizeof socketFd); } + } - if (m_boosterPid > 0) { - /* We were expecting booster details => update bookkeeping */ - if (newFd != -1) { - // Store booster pid - invoker socket pair - m_boosterPidToInvokerFd[m_boosterPid] = newFd; - newFd = -1; - } - if (invokerPid > 0) { - // Store booster pid - invoker pid pair - m_boosterPidToInvokerPid[m_boosterPid] = invokerPid; - } - } + Logger::logDebug("Daemon: booster=%d invoker=%d socket=%d delay=%d\n", + m_boosterPid, invokerPid, socketFd, delay); - if (newFd != -1) { - /* If we are not going to use the received fd, it needs to be closed */ - Logger::logWarning("Daemon: close stray socket file descriptor: %d\n", newFd); - close(newFd); + if (m_boosterPid > 0) { + /* We were expecting booster details => update bookkeeping */ + if (socketFd != -1) { + // Store booster pid - invoker socket pair + m_boosterPidToInvokerFd[m_boosterPid] = socketFd, socketFd = -1; + } + if (invokerPid > 0) { + // Store booster pid - invoker pid pair + m_boosterPidToInvokerPid[m_boosterPid] = invokerPid; } } - else - { - Logger::logError("Daemon: Nothing read from the socket\n"); - // Critical error communicating with booster. Exiting applauncherd. - _exit(EXIT_FAILURE); + + if (socketFd != -1) { + /* If we are not going to use the received fd, it needs to be closed */ + Logger::logWarning("Daemon: close stray socket file descriptor: %d\n", socketFd); + close(socketFd); } // 2nd param guarantees some time for the just launched application @@ -918,72 +919,93 @@ void Daemon::daemonize() } } -void Daemon::parseArgs(const ArgVect & args) -{ - std::deque queue; - for (ArgVect::const_iterator i(args.begin() + 1); i != args.end(); i++) - queue.push_back(*i); - - while (!queue.empty()) { - string option(queue.front()); - queue.pop_front(); - if (option == "--boot-mode" || option == "-b") - { +void Daemon::parseArgs(int argc, char **argv) +{ + // Options recognized + static const struct option longopts[] = { + { "help", no_argument, NULL, 'h' }, + { "verbose", no_argument, NULL, 'v' }, + { "debug", no_argument, NULL, 'v' }, + { "boot-mode", no_argument, NULL, 'b' }, + { "daemon", no_argument, NULL, 'd' }, + { "systemd", no_argument, NULL, 'n' }, + { "application", required_argument, NULL, 'a' }, + { 0, 0, 0, 0} + }; + static const char shortopts[] = + "+" // use posix rules + "h" // --help + "v" // --verbose --debug + "b" // --boot-mode + "d" // --daemon + "n" // --systemd + "a:" // --application= + ; + for (;;) { + int opt = getopt_long(argc, argv, shortopts, longopts, NULL); + if (opt == -1) + break; + switch (opt) { + case 'h': + usage(*argv, EXIT_SUCCESS); + break; + case 'v': + Logger::setDebugMode(true); + m_debugMode = true; + break; + case 'b': Logger::logInfo("Daemon: Boot mode set."); m_bootMode = true; - } - else if (option == "--daemon" || option == "-d") - { + break; + case 'd': m_daemon = true; - } - else if (option == "--debug") - { - Logger::setDebugMode(true); - m_debugMode = true; - } - else if (option == "--help" || option == "-h") - { - usage(args[0].c_str(), EXIT_SUCCESS); - } - else if (option == "--systemd") - { + break; + case 'n': m_notifySystemd = true; - } - else if (option == "--application" || option == "-a") - { - if (!queue.empty()) { - m_boostedApplication = queue.front(); - queue.pop_front(); - } - } - else - { - if (option.find_first_not_of(' ') != string::npos) - usage(args[0].c_str(), EXIT_FAILURE); + break; + case 'a': + m_boostedApplication = optarg; + break; + default: + case '?': + usage(*argv, EXIT_FAILURE); } } + if (optind < argc) + usage(*argv, EXIT_FAILURE); } // Prints the usage and exits with given status void Daemon::usage(const char *name, int status) { - printf("\nUsage: %s [options]\n\n" - "Start the application launcher daemon.\n\n" + name = basename(name); + printf("\n" + "Start the application launcher daemon.\n" + "\n" + "Usage:\n" + " %s [options]\n" + "\n" "Options:\n" - " -b, --boot-mode Start %s in the boot mode. This means that\n" + " -b, --boot-mode\n" + " Start %s in the boot mode. This means that\n" " boosters will not initialize caches and booster\n" " respawn delay is set to zero.\n" " Normal mode is restored by sending SIGUSR1\n" " to the launcher.\n" " Boot mode can be activated also by sending SIGUSR2\n" " to the launcher.\n" - " -d, --daemon Run as %s a daemon.\n" - " -a, --application \n" + " -d, --daemon\n" + " Run as %s a daemon.\n" + " -a, --application=\n" " Run as application specific booster.\n" - " --systemd Notify systemd when initialization is done\n" - " --debug Enable debug messages and log everything also to stdout.\n" - " -h, --help Print this help.\n\n", + " -n, --systemd\n" + " Notify systemd when initialization is done\n" + " -h, --help\n" + " Print this help.\n" + " -v, --verbose, --debug\n" + " Make diagnostic logging more verbose.\n" + "\n", name, name, name); exit(status); diff --git a/src/launcherlib/daemon.h b/src/launcherlib/daemon.h index 75c0915..8947431 100644 --- a/src/launcherlib/daemon.h +++ b/src/launcherlib/daemon.h @@ -113,8 +113,7 @@ private: Daemon & operator= (const Daemon & r); //! Parse arguments - typedef vector ArgVect; - void parseArgs(const ArgVect & args); + void parseArgs(int argc, char **argv); //! Fork to a daemon void daemonize(); diff --git a/src/launcherlib/logger.cpp b/src/launcherlib/logger.cpp index b659938..207c504 100644 --- a/src/launcherlib/logger.cpp +++ b/src/launcherlib/logger.cpp @@ -28,40 +28,14 @@ #include #include "coverage.h" +#include "report.h" bool Logger::m_isOpened = false; bool Logger::m_debugMode = false; -static char *strip(char *str) -{ - if (str) { - char *dst = str; - char *src = str; - while (*src && isspace(*src)) - ++src; - for (;;) { - while (*src && !isspace(*src)) - *dst++ = *src++; - while (*src && isspace(*src)) - ++src; - if (!*src) - break; - *dst++ = ' '; - } - *dst = 0; - } - return str; -} - static bool useSyslog() { - static bool checked = false; - static bool value = false; - if (!checked) { - checked = true; - value = !isatty(STDIN_FILENO); - } - return value; + return report_get_output() == report_syslog; } void Logger::openLog(const char * progName) @@ -86,63 +60,48 @@ void Logger::closeLog() } } -void Logger::writeLog(const int priority, const char * format, va_list ap) +void Logger::writeLog(const int priority, const char *format, va_list va) { - if (useSyslog()) { - if (!Logger::m_isOpened) - Logger::openLog(); - vsyslog(priority, format, ap); - } else { - char *msg = 0; - if (vasprintf(&msg, format, ap) < 0) - msg = 0; - else - strip(msg); - fprintf(stderr, "BOOSTER(%d): %s\n", (int)getpid(), msg ?: format); - fflush(stderr); - free(msg); - } + vreport((enum report_type)priority, format, va); } void Logger::logDebug(const char * format, ...) { - if (m_debugMode) - { - va_list ap; - va_start(ap, format); - writeLog(LOG_DEBUG, format, ap); - va_end(ap); - } + va_list va; + va_start(va, format); + Logger::writeLog(report_debug, format, va); + va_end(va); } void Logger::logInfo(const char * format, ...) { - va_list ap; - va_start(ap, format); - writeLog(LOG_INFO, format, ap); - va_end(ap); - // To avoid extra file descriptors in forked boosters closing connection to syslog - Logger::closeLog(); + va_list va; + va_start(va, format); + Logger::writeLog(report_info, format, va); + va_end(va); } void Logger::logWarning(const char * format, ...) { - va_list ap; - va_start(ap, format); - writeLog(LOG_WARNING, format, ap); - va_end(ap); + va_list va; + va_start(va, format); + Logger::writeLog(report_warning, format, va); + va_end(va); } void Logger::logError(const char * format, ...) { - va_list ap; - va_start(ap, format); - writeLog(LOG_ERR, format, ap); - va_end(ap); + va_list va; + va_start(va, format); + Logger::writeLog(report_error, format, va); + va_end(va); } void Logger::setDebugMode(bool enable) { - Logger::m_debugMode = enable; + if ((Logger::m_debugMode = enable)) + report_set_type(report_debug); + else + report_set_type(report_warning); } diff --git a/src/launcherlib/logger.h b/src/launcherlib/logger.h index dd9d9b4..725ac50 100644 --- a/src/launcherlib/logger.h +++ b/src/launcherlib/logger.h @@ -100,11 +100,11 @@ private: #endif }; -/* Allow the same logging API to be used in booster and invoker */ -#define error( FMT, ARGS...) Logger::logError( FMT, ##ARGS) -#define warning(FMT, ARGS...) Logger::logWarning(FMT, ##ARGS) -#define info( FMT, ARGS...) Logger::logInfo( FMT, ##ARGS) -#define debug( FMT, ARGS...) Logger::logDebug( FMT, ##ARGS) +// QUARANTINE /* Allow the same logging API to be used in booster and invoker */ +// QUARANTINE #define error( FMT, ARGS...) Logger::logError( FMT, ##ARGS) +// QUARANTINE #define warning(FMT, ARGS...) Logger::logWarning(FMT, ##ARGS) +// QUARANTINE #define info( FMT, ARGS...) Logger::logInfo( FMT, ##ARGS) +// QUARANTINE #define debug( FMT, ARGS...) Logger::logDebug( FMT, ##ARGS) #endif // LOGGER_H