[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 <simo.piiroinen@jolla.com>
pull/1/head
Simo Piiroinen 5 years ago
parent f24871adb2
commit 833b551af1

@ -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 from the invoker with the information about which application should be
launched. 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 Contributors
============================== ==============================

@ -8,13 +8,13 @@ Source0: %{name}-%{version}.tar.bz2
Source1: booster-cgroup-mount.service Source1: booster-cgroup-mount.service
Requires: systemd-user-session-targets Requires: systemd-user-session-targets
Requires(post): /sbin/ldconfig Requires(post): /sbin/ldconfig
Requires(post): /usr/sbin/setcap
Requires(postun): /sbin/ldconfig Requires(postun): /sbin/ldconfig
Requires(pre): sailfish-setup Requires(pre): sailfish-setup
BuildRequires: pkgconfig(libshadowutils) BuildRequires: pkgconfig(libshadowutils)
BuildRequires: pkgconfig(systemd) BuildRequires: pkgconfig(systemd)
BuildRequires: pkgconfig(dbus-1) BuildRequires: pkgconfig(dbus-1)
BuildRequires: pkgconfig(libcap) BuildRequires: pkgconfig(libcap)
BuildRequires: pkgconfig(glib-2.0)
BuildRequires: cmake BuildRequires: cmake
Provides: meegotouch-applauncherd > 3.0.3 Provides: meegotouch-applauncherd > 3.0.3
Obsoletes: 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 %post
/sbin/ldconfig /sbin/ldconfig
/usr/sbin/setcap cap_sys_ptrace+pe %{_libexecdir}/mapplauncherd/booster-generic || :
%postun -p /sbin/ldconfig %postun -p /sbin/ldconfig
@ -87,7 +86,8 @@ install -D -m 0755 scripts/booster-cgroup-mount %{buildroot}/usr/lib/startup/boo
%{_bindir}/single-instance %{_bindir}/single-instance
%{_libdir}/libapplauncherd.so* %{_libdir}/libapplauncherd.so*
%attr(2755, root, privileged) %{_libexecdir}/mapplauncherd/booster-generic %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 %{_userunitdir}/user-session.target.wants/booster-generic.service
%files devel %files devel

@ -21,4 +21,5 @@ install(TARGETS booster-generic DESTINATION ${CMAKE_INSTALL_FULL_LIBEXECDIR}/map
if(INSTALL_SYSTEMD_UNITS) 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/)
install(FILES booster-generic@.service DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/systemd/user/)
endif() endif()

@ -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

@ -17,16 +17,39 @@
** **
****************************************************************************/ ****************************************************************************/
#define _GNU_SOURCE
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdarg.h> #include <string.h>
#include <syslog.h>
#include <unistd.h> #include <unistd.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h>
#include <limits.h>
#include "report.h" #include "report.h"
static enum report_output output = report_guess; 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) static char *strip(char *str)
{ {
@ -49,64 +72,94 @@ static char *strip(char *str)
return 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 level;
return; }
if (output == report_syslog) static enum report_type normalize_type(enum report_type type)
closelog(); {
if (type < report_minimum)
return report_minimum;
if (type > report_maximum)
return report_maximum;
return type;
}
if (new_output == report_syslog) extern void report_set_type(enum report_type type)
openlog(PROG_NAME_INVOKER, LOG_PID, LOG_DAEMON); {
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]; if (output != new_output) {
char *str_type = ""; if (output == report_syslog)
int log_type; 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: case report_debug:
log_type = LOG_DEBUG; str_type = "debug: ";
break; break;
default:
case report_info: case report_info:
log_type = LOG_INFO; str_type = "info: ";
break;
case report_notice:
str_type = "notice: ";
break; break;
case report_warning: case report_warning:
str_type = "warning: "; str_type = "warning: ";
log_type = LOG_WARNING;
break; break;
case report_error: case report_error:
str_type = "error: "; str_type = "error: ";
log_type = LOG_ERR;
break; break;
case report_fatal: case report_fatal:
str_type = "died: "; str_type = "died: ";
log_type = LOG_ERR; break;
default:
break; break;
} }
char str[400];
vsnprintf(str, sizeof(str), msg, arg); vsnprintf(str, sizeof(str), msg, arg);
if (output == report_guess) { switch (report_get_output()) {
if (isatty(STDIN_FILENO)) case report_console:
output = report_console; fprintf(stderr, "%s: %s%s\n", progname(), str_type, strip(str));
else
output = report_syslog;
}
if (output == report_console) {
fprintf(stderr, "%s: %s%s\n", PROG_NAME_INVOKER, str_type, strip(str));
fflush(stderr); fflush(stderr);
} else if (output == report_syslog) { break;
syslog(log_type, "%s%s", str_type, str); 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, ...) void report(enum report_type type, const char *msg, ...)

@ -22,6 +22,13 @@
#ifndef REPORT_H #ifndef REPORT_H
#define REPORT_H #define REPORT_H
#include <syslog.h>
#include <stdarg.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __GNUC__ #ifdef __GNUC__
#define ATTR_NORET __attribute__((noreturn)) #define ATTR_NORET __attribute__((noreturn))
#else #else
@ -32,31 +39,39 @@ enum report_output {
report_guess, report_guess,
report_console, report_console,
report_syslog, report_syslog,
report_none report_none,
}; };
enum report_type { enum report_type {
report_debug, report_fatal = LOG_CRIT,
report_info, report_error = LOG_ERR,
report_warning, report_warning = LOG_WARNING,
report_error, report_notice = LOG_NOTICE,
report_fatal 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 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 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 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
#endif // REPORT_H

@ -0,0 +1,559 @@
#include "sailjail.h"
#include "report.h"
#include <stdio.h>
#include <limits.h>
#include <dbus/dbus.h>
/* 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;
}

@ -0,0 +1,13 @@
#ifndef SAILJAIL_H_
# define SAILJAIL_H_
# include <stdbool.h>
# include <glib.h>
G_BEGIN_DECLS
bool sailjail_verify_launch(const char *desktop, const char **argv);
G_END_DECLS
#endif // SAILJAIL_H_

@ -53,7 +53,7 @@ bool invoke_recv_msg(int fd, uint32_t *msg)
if (numRead == -1) if (numRead == -1)
{ {
debug("%s: Error reading message: %s\n", __FUNCTION__, strerror(errno)); debug("%s: Error reading message: %m\n", __FUNCTION__);
*msg = 0; *msg = 0;
return false; return false;
} }

@ -49,11 +49,14 @@
#include "invokelib.h" #include "invokelib.h"
#include "search.h" #include "search.h"
#define BOOSTER_SESSION "silica-session"
#define BOOSTER_GENERIC "generic"
/* Setting VERBOSE_SIGNALS to non-zero value logs receiving of /* Setting VERBOSE_SIGNALS to non-zero value logs receiving of
* async-signals - which is useful only when actively debugging * async-signals - which is useful only when actively debugging
* booster / invoker interoperation. * booster / invoker interoperation.
*/ */
#define VERBOSE_SIGNALS 0 #define VERBOSE_SIGNALS 01
// Utility functions // Utility functions
static char *strip(char *str) static char *strip(char *str)
@ -223,7 +226,7 @@ static bool shutdown_socket(int socket_fd)
* end too. * end too.
*/ */
debug("trying to disconnect booster socket...\n"); info("trying to disconnect booster socket...\n");
if (shutdown(socket_fd, SHUT_WR) == -1) { if (shutdown(socket_fd, SHUT_WR) == -1) {
warning("socket shutdown failed: %m\n"); warning("socket shutdown failed: %m\n");
@ -243,7 +246,7 @@ static bool shutdown_socket(int socket_fd)
.revents = 0, .revents = 0,
}; };
debug("waiting for booster socket input...\n"); info("waiting for booster socket input...\n");
int rc = poll(&pfd, 1, (int)(timeout - elapsed)); int rc = poll(&pfd, 1, (int)(timeout - elapsed));
if (rc == 0) if (rc == 0)
@ -273,7 +276,7 @@ static bool shutdown_socket(int socket_fd)
EXIT: EXIT:
if (disconnected) if (disconnected)
debug("booster socket was succesfully disconnected\n"); info("booster socket was succesfully disconnected\n");
else else
warning("could not disconnect booster socket\n"); warning("could not disconnect booster socket\n");
@ -314,7 +317,7 @@ static void kill_application(pid_t pid)
FAIL: FAIL:
if (errno == ESRCH) if (errno == ESRCH)
debug("application (pid=%d) has exited", (int)pid); info("application (pid=%d) has exited", (int)pid);
else else
warning("application (pid=%d) kill failed: %m", (int)pid); 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 // Inits a socket connection for the given application type
static int invoker_init(const char *app_type, const char *app_name) 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; bool connected = false;
int fd = -1; int fd = -1;
@ -388,7 +393,7 @@ static int invoker_init(const char *app_type, const char *app_name)
goto EXIT; goto EXIT;
} }
debug("connected to: %s\n", sun.sun_path); info("connected to: %s\n", sun.sun_path);
connected = true; connected = true;
EXIT: EXIT:
@ -469,7 +474,7 @@ static void invoker_send_args(int fd, int argc, char **argv)
invoke_send_msg(fd, argc); invoke_send_msg(fd, argc);
for (i = 0; i < argc; i++) 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]); 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] // coverity[tainted_string_return_content]
g_invoked_pid = invoker_recv_pid(socket_fd); 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 // Setup signal handlers
sigs_init(); 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", warning("application (pid=%d) exit(%d) signal(%d)\n",
(int)g_invoked_pid, exit_status, exit_signal); (int)g_invoked_pid, exit_status, exit_signal);
else 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); (int)g_invoked_pid, exit_status, exit_signal);
if (socket_fd != -1) { if (socket_fd != -1) {
@ -853,7 +858,7 @@ static int invoke(InvokeArgs *args)
bool tried_session = false; bool tried_session = false;
for (size_t i = 0; !tried_session && types[i]; ++i) { for (size_t i = 0; !tried_session && types[i]; ++i) {
if (strcmp(types[i], "session")) if (strcmp(types[i], BOOSTER_SESSION))
continue; continue;
tried_session = true; tried_session = true;
fd = invoker_init(types[i], NULL); fd = invoker_init(types[i], NULL);
@ -866,12 +871,12 @@ static int invoke(InvokeArgs *args)
if (fd == -1 && !tried_session) { if (fd == -1 && !tried_session) {
bool tried_generic = false; bool tried_generic = false;
for (size_t i = 0; fd == -1 && types[i]; ++i) { for (size_t i = 0; fd == -1 && types[i]; ++i) {
if (!strcmp(types[i], "generic")) if (!strcmp(types[i], BOOSTER_GENERIC))
tried_generic = true; tried_generic = true;
fd = invoker_init(types[i], args->app_name); fd = invoker_init(types[i], args->app_name);
} }
if (fd == -1 && !tried_generic) if (fd == -1 && !tried_generic)
fd = invoker_init("generic", args->app_name); fd = invoker_init(BOOSTER_GENERIC, args->app_name);
} }
if (fd != -1) { if (fd != -1) {
@ -931,6 +936,7 @@ int main(int argc, char *argv[])
{"splash", required_argument, NULL, 'S'}, {"splash", required_argument, NULL, 'S'},
{"splash-landscape", required_argument, NULL, 'L'}, {"splash-landscape", required_argument, NULL, 'L'},
{"desktop-file", required_argument, NULL, 'F'}, {"desktop-file", required_argument, NULL, 'F'},
{"verbose", no_argument, NULL, 'v'},
{0, 0, 0, 0} {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 use of + for POSIXLY_CORRECT behavior is a GNU extension, but avoids polluting
// the environment // the environment
int opt; 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) switch(opt)
{ {
@ -946,6 +952,10 @@ int main(int argc, char *argv[])
usage(0); usage(0);
break; break;
case 'v':
report_set_type(report_get_type() + 1);
break;
case 'w': case 'w':
// nothing to do, it's by default now // nothing to do, it's by default now
break; break;
@ -1077,10 +1087,10 @@ int main(int argc, char *argv[])
// Sleep for delay before exiting // Sleep for delay before exiting
if (args.exit_delay) { if (args.exit_delay) {
// DBUS cannot cope some times if the invoker exits too early. // 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); sleep(args.exit_delay);
} }
debug("invoker exit(%d)\n", ret_val); info("invoker exit(%d)\n", ret_val);
return ret_val; return ret_val;
} }

@ -5,23 +5,28 @@ set(COMMON ${CMAKE_HOME_DIRECTORY}/src/common)
# Find systemd # Find systemd
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(SYSTEMD "libsystemd" 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 # 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()) # Hide all symbols except the ones explicitly exported in the code (like main())
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
# Set sources # Set sources
set(SRC appdata.cpp booster.cpp connection.cpp daemon.cpp logger.cpp 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 set(HEADERS appdata.h booster.h connection.h daemon.h logger.h launcherlib.h
singleinstance.h socketmanager.h ${COMMON}/protocol.h) singleinstance.h socketmanager.h ${COMMON}/protocol.h)
# Set libraries to be linked. Shared libraries to be preloaded are not linked in anymore, # 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. # 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 # Set executable
add_library(applauncherd MODULE ${SRC} ${MOC_SRC}) 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}) target_link_libraries(applauncherd ${SYSTEMD_LIBRARIES})
# Add install rule # Add install rule
install(TARGETS applauncherd DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}) install(TARGETS applauncherd DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})
install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/applauncherd install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/applauncherd

@ -24,6 +24,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <fstream> #include <fstream>
#include <dirent.h> #include <dirent.h>
#include <string.h>
AppData::AppData() : AppData::AppData() :
m_options(0), m_options(0),
@ -71,7 +72,9 @@ bool AppData::disableOutOfMemAdj() const
void AppData::setArgc(int newArgc) 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 int AppData::argc() const
@ -81,12 +84,25 @@ int AppData::argc() const
void AppData::setArgv(const char ** newArgv) 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 const char ** AppData::argv() const
{ {
return m_argv; return (const char **)m_argv;
} }
void AppData::setAppName(const string & newAppName) void AppData::setAppName(const string & newAppName)
@ -251,4 +267,5 @@ string AppData::privileges() const
AppData::~AppData() AppData::~AppData()
{ {
setArgv(nullptr);
} }

@ -135,7 +135,7 @@ private:
uint32_t m_options; uint32_t m_options;
int m_argc; int m_argc;
const char ** m_argv; char **m_argv;
string m_appName; string m_appName;
string m_fileName; string m_fileName;
int m_prio; int m_prio;

@ -25,6 +25,7 @@
#include "singleinstance.h" #include "singleinstance.h"
#include "socketmanager.h" #include "socketmanager.h"
#include "logger.h" #include "logger.h"
#include "report.h"
#include <cstdlib> #include <cstdlib>
#include <dlfcn.h> #include <dlfcn.h>
@ -50,8 +51,12 @@
#include <grp.h> #include <grp.h>
#include <libgen.h> #include <libgen.h>
#include <dbus/dbus.h>
#include "coverage.h" #include "coverage.h"
#include "sailjail.h"
Booster::Booster() : Booster::Booster() :
m_appData(new AppData), m_appData(new AppData),
m_connection(NULL), m_connection(NULL),
@ -210,11 +215,13 @@ void Booster::sendDataToParent()
cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int)); cmsg->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
debug("send to daemon: pid=%d delay=%d fd=%d", (int)pid, delay, fd);
} }
else else
{ {
msg.msg_control = NULL; msg.msg_control = NULL;
msg.msg_controllen = 0; msg.msg_controllen = 0;
debug("send to daemon: pid=%d delay=%d fd=NA", (int)pid, delay);
} }
if (sendmsg(boosterLauncherSocket(), &msg, 0) < 0) 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"); 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(); return launchProcess();
} catch (const std::runtime_error &e) { } catch (const std::runtime_error &e) {
Logger::logError("Booster: Failed to invoke: %s\n", e.what()); Logger::logError("Booster: Failed to invoke: %s\n", e.what());
@ -634,7 +646,6 @@ const string Booster::socketId() const
return id; return id;
} }
pid_t Booster::invokersPid() pid_t Booster::invokersPid()
{ {
if (m_connection->isReportAppExitStatusNeeded()) if (m_connection->isReportAppExitStatusNeeded())
@ -718,3 +729,4 @@ std::string Booster::getFinalName(const std::string &name)
} }
return name; return name;
} }

@ -21,6 +21,7 @@
#include "connection.h" #include "connection.h"
#include "logger.h" #include "logger.h"
#include "report.h"
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/un.h> /* for getsockopt */ #include <sys/un.h> /* for getsockopt */
@ -65,6 +66,12 @@ Connection::~Connection()
m_io[i] = -1; 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) if (!m_testMode)
{ {
@ -238,7 +245,7 @@ string Connection::receiveAppName()
return string(); return string();
} }
const char* name = recvStr(); char *name = recvStr();
if (!name) if (!name)
{ {
Logger::logError("Connection: receiving application name"); Logger::logError("Connection: receiving application name");
@ -252,7 +259,7 @@ string Connection::receiveAppName()
bool Connection::receiveExec() bool Connection::receiveExec()
{ {
const char* filename = recvStr(); char *filename = recvStr();
if (!filename) if (!filename)
return false; return false;
@ -282,51 +289,36 @@ bool Connection::receiveIDs()
bool Connection::receiveArgs() bool Connection::receiveArgs()
{ {
// Get argc
recvMsg(&m_argc);
const uint32_t argMax = 1024; 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 // Clear current args
for (uint i = 0; i < m_argc; i++) for (int i = 0; i < m_argc; ++i)
{ delete[] m_argv[i];
m_argv[i] = recvStr(); free(m_argv);
if (!m_argv[i]) m_argc = 0;
{ m_argv = nullptr;
Logger::logError("Connection: receiving argv[%i]", i);
return false; // Get argc
} uint32_t argc = 0;
} recvMsg(&argc);
} if (argc < 1 || argc > argMax) {
else
{
Logger::logError("Connection: invalid number of parameters %d", m_argc); Logger::logError("Connection: invalid number of parameters %d", m_argc);
return false; 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; return true;
} }
// coverity[ +tainted_string_sanitize_content : arg-0 ]
bool putenv_sanitize(const char * s)
{
return static_cast<bool>(strchr(s, '='));
}
// coverity[ +free : arg-0 ]
int putenv_wrapper(char * var)
{
return putenv(var);
}
bool Connection::receiveEnv() bool Connection::receiveEnv()
{ {
// Have some "reasonable" limit for environment variables to protect from // Have some "reasonable" limit for environment variables to protect from
@ -341,30 +333,28 @@ bool Connection::receiveEnv()
// Get environment variables // Get environment variables
for (uint32_t i = 0; i < n_vars; i++) for (uint32_t i = 0; i < n_vars; i++)
{ {
const char * var = recvStr(); char *var = recvStr();
if (var == NULL) if (var == NULL)
{ {
Logger::logError("Connection: receiving environ[%i]", i); Logger::logError("Connection: receiving environ[%i]", i);
return false; return false;
} }
char *val = strchr(var, '=');
// In case of error, just warn and try to continue, as the other side is if (val) {
// going to keep sending the reset of the message. *val++ = 0;
// String pointed to by var shall become part of the environment, so altering const char *cur = getenv(var);
// the string shall change the environment, don't free it /* Note: DBUS_SESSION_BUS_ADDRESS is a special case. If we
if (putenv_sanitize(var)) * are running in sandbox, we already have non-standard path
{ * that firejail has placed in env.
if (putenv_wrapper(const_cast<char *>(var)) != 0) */
{ if (!cur || strcmp(var, "DBUS_SESSION_BUS_ADDRESS")) {
Logger::logWarning("Connection: putenv failed"); if (!cur || strcmp(cur, val)) {
info("ENV: $%s: %s -> %s", var, cur ?: "n/a", val);
setenv(var, val, true);
}
} }
} }
else delete [] var;
{
delete [] var;
var = NULL;
Logger::logWarning("Connection: invalid environment data");
}
} }
} }
else else
@ -430,43 +420,49 @@ bool Connection::receiveActions()
{ {
Logger::logDebug("Connection: enter: %s", __FUNCTION__); Logger::logDebug("Connection: enter: %s", __FUNCTION__);
while (1) for (;;)
{ {
uint32_t action = 0; uint32_t action = 0;
// Get the action. // Get the action.
recvMsg(&action); if (!recvMsg(&action))
return false;
switch (action) switch (action)
{ {
case INVOKER_MSG_EXEC: case INVOKER_MSG_EXEC:
receiveExec(); if (!receiveExec())
return false;
break; break;
case INVOKER_MSG_ARGS: case INVOKER_MSG_ARGS:
receiveArgs(); if (!receiveArgs())
return false;
break; break;
case INVOKER_MSG_ENV: case INVOKER_MSG_ENV:
// Clean-up all the env variables if (!receiveEnv())
clearenv(); return false;
receiveEnv();
break; break;
case INVOKER_MSG_PRIO: case INVOKER_MSG_PRIO:
receivePriority(); if (!receivePriority())
return false;
break; break;
case INVOKER_MSG_DELAY: case INVOKER_MSG_DELAY:
receiveDelay(); if (!receiveDelay())
return false;
break; break;
case INVOKER_MSG_IO: case INVOKER_MSG_IO:
receiveIO(); if (!receiveIO())
return false;
break; break;
case INVOKER_MSG_IDS: case INVOKER_MSG_IDS:
receiveIDs(); if (!receiveIDs())
return false;
break; break;
case INVOKER_MSG_SPLASH: case INVOKER_MSG_SPLASH:
@ -478,11 +474,10 @@ bool Connection::receiveActions()
return false; return false;
case INVOKER_MSG_END: case INVOKER_MSG_END:
sendMsg(INVOKER_MSG_ACK); if (!sendMsg(INVOKER_MSG_ACK))
return false;
if (m_sendPid) if (m_sendPid && !sendPid(getpid()))
sendPid(getpid()); return false;
return true; return true;
default: default:
@ -517,7 +512,7 @@ bool Connection::receiveApplicationData(AppData* appData)
appData->setPriority(m_priority); appData->setPriority(m_priority);
appData->setDelay(m_delay); appData->setDelay(m_delay);
appData->setArgc(m_argc); appData->setArgc(m_argc);
appData->setArgv(m_argv); appData->setArgv((const char **)m_argv);
appData->setIODescriptors(vector<int>(m_io, m_io + IO_DESCRIPTOR_COUNT)); appData->setIODescriptors(vector<int>(m_io, m_io + IO_DESCRIPTOR_COUNT));
appData->setIDs(m_uid, m_gid); appData->setIDs(m_uid, m_gid);
} }

@ -140,7 +140,7 @@ private:
virtual bool recvMsg(uint32_t *msg); virtual bool recvMsg(uint32_t *msg);
//! Receive a string. This is a virtual to help unit testing. //! Receive a string. This is a virtual to help unit testing.
virtual const char * recvStr(); virtual char *recvStr();
//! Run in test mode, if true //! Run in test mode, if true
bool m_testMode; bool m_testMode;
@ -152,8 +152,8 @@ private:
int m_curSocket; int m_curSocket;
string m_fileName; string m_fileName;
uint32_t m_argc; int m_argc;
const char ** m_argv; char **m_argv;
int m_io[IO_DESCRIPTOR_COUNT]; int m_io[IO_DESCRIPTOR_COUNT];
uint32_t m_priority; uint32_t m_priority;
uint32_t m_delay; uint32_t m_delay;

@ -21,6 +21,7 @@
#include "daemon.h" #include "daemon.h"
#include "logger.h" #include "logger.h"
#include "report.h"
#include "connection.h" #include "connection.h"
#include "booster.h" #include "booster.h"
#include "singleinstance.h" #include "singleinstance.h"
@ -46,6 +47,7 @@
#include <systemd/sd-daemon.h> #include <systemd/sd-daemon.h>
#include <unistd.h> #include <unistd.h>
#include <poll.h> #include <poll.h>
#include <getopt.h>
#include "coverage.h" #include "coverage.h"
@ -53,7 +55,7 @@
* async-signals - which is useful only when actively debugging * async-signals - which is useful only when actively debugging
* booster / invoker interoperation. * booster / invoker interoperation.
*/ */
#define VERBOSE_SIGNALS 0 #define VERBOSE_SIGNALS 01
// Environment // Environment
@ -325,13 +327,13 @@ Daemon::Daemon(int & argc, char * argv[]) :
throw std::runtime_error("Daemon: Daemon already created!\n"); throw std::runtime_error("Daemon: Daemon already created!\n");
} }
// Parse arguments
parseArgs(ArgVect(argv, argv + argc));
// Store arguments list // Store arguments list
m_initialArgv = argv; m_initialArgv = argv;
m_initialArgc = argc; m_initialArgc = argc;
// Parse arguments
parseArgs(argc, argv);
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, m_boosterLauncherSocket) == -1) if (socketpair(AF_UNIX, SOCK_DGRAM, 0, m_boosterLauncherSocket) == -1)
{ {
throw std::runtime_error("Daemon: Creating a socket pair for boosters failed!\n"); 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) void Daemon::readFromBoosterSocket(int fd)
{ {
pid_t invokerPid = 0; pid_t invokerPid = 0;
int delay = 0; int delay = 0;
struct msghdr msg; int socketFd = -1;
struct iovec iov[2];
char buf[CMSG_SPACE(sizeof socketFd)];
struct msghdr msg;
struct cmsghdr *cmsg; 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_base = &invokerPid;
iov[0].iov_len = sizeof(pid_t); iov[0].iov_len = sizeof invokerPid;
iov[1].iov_base = &delay; iov[1].iov_base = &delay;
iov[1].iov_len = sizeof(int); iov[1].iov_len = sizeof delay;
msg.msg_iov = iov; msg.msg_iov = iov;
msg.msg_iovlen = 2; msg.msg_iovlen = 2;
msg.msg_name = NULL; msg.msg_name = NULL;
msg.msg_namelen = 0; msg.msg_namelen = 0;
msg.msg_control = buf; msg.msg_control = buf;
msg.msg_controllen = sizeof(buf); msg.msg_controllen = sizeof buf;
if (recvmsg(fd, &msg, 0) >= 0) if (recvmsg(fd, &msg, 0) == -1) {
{ Logger::logError("Daemon: Critical error communicating with booster. Exiting applauncherd.\n");
Logger::logDebug("Daemon: boosters's pid: %d\n", m_boosterPid); exit(EXIT_FAILURE);
Logger::logDebug("Daemon: invoker's pid: %d\n", invokerPid); }
Logger::logDebug("Daemon: respawn delay: %d \n", delay);
int newFd = -1;
if (invokerPid > 0) { for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
/* invokerPid got filled in at recvmsg() => we have fd too */ if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_type == SCM_RIGHTS &&
memcpy(&newFd, CMSG_DATA(cmsg), sizeof(int)); cmsg->cmsg_len >= CMSG_LEN(sizeof(socketFd))) {
Logger::logDebug("Daemon: socket file descriptor: %d\n", newFd); memcpy(&socketFd, CMSG_DATA(cmsg), sizeof socketFd);
} }
}
if (m_boosterPid > 0) { Logger::logDebug("Daemon: booster=%d invoker=%d socket=%d delay=%d\n",
/* We were expecting booster details => update bookkeeping */ m_boosterPid, invokerPid, socketFd, delay);
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;
}
}
if (newFd != -1) { if (m_boosterPid > 0) {
/* If we are not going to use the received fd, it needs to be closed */ /* We were expecting booster details => update bookkeeping */
Logger::logWarning("Daemon: close stray socket file descriptor: %d\n", newFd); if (socketFd != -1) {
close(newFd); // 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
{ if (socketFd != -1) {
Logger::logError("Daemon: Nothing read from the socket\n"); /* If we are not going to use the received fd, it needs to be closed */
// Critical error communicating with booster. Exiting applauncherd. Logger::logWarning("Daemon: close stray socket file descriptor: %d\n", socketFd);
_exit(EXIT_FAILURE); close(socketFd);
} }
// 2nd param guarantees some time for the just launched application // 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<string> 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=<APP>
;
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."); Logger::logInfo("Daemon: Boot mode set.");
m_bootMode = true; m_bootMode = true;
} break;
else if (option == "--daemon" || option == "-d") case 'd':
{
m_daemon = true; m_daemon = true;
} break;
else if (option == "--debug") case 'n':
{
Logger::setDebugMode(true);
m_debugMode = true;
}
else if (option == "--help" || option == "-h")
{
usage(args[0].c_str(), EXIT_SUCCESS);
}
else if (option == "--systemd")
{
m_notifySystemd = true; m_notifySystemd = true;
} break;
else if (option == "--application" || option == "-a") case 'a':
{ m_boostedApplication = optarg;
if (!queue.empty()) { break;
m_boostedApplication = queue.front(); default:
queue.pop_front(); case '?':
} usage(*argv, EXIT_FAILURE);
}
else
{
if (option.find_first_not_of(' ') != string::npos)
usage(args[0].c_str(), EXIT_FAILURE);
} }
} }
if (optind < argc)
usage(*argv, EXIT_FAILURE);
} }
// Prints the usage and exits with given status // Prints the usage and exits with given status
void Daemon::usage(const char *name, int status) void Daemon::usage(const char *name, int status)
{ {
printf("\nUsage: %s [options]\n\n" name = basename(name);
"Start the application launcher daemon.\n\n" printf("\n"
"Start the application launcher daemon.\n"
"\n"
"Usage:\n"
" %s [options]\n"
"\n"
"Options:\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" " boosters will not initialize caches and booster\n"
" respawn delay is set to zero.\n" " respawn delay is set to zero.\n"
" Normal mode is restored by sending SIGUSR1\n" " Normal mode is restored by sending SIGUSR1\n"
" to the launcher.\n" " to the launcher.\n"
" Boot mode can be activated also by sending SIGUSR2\n" " Boot mode can be activated also by sending SIGUSR2\n"
" to the launcher.\n" " to the launcher.\n"
" -d, --daemon Run as %s a daemon.\n" " -d, --daemon\n"
" -a, --application <application>\n" " Run as %s a daemon.\n"
" -a, --application=<application>\n"
" Run as application specific booster.\n" " Run as application specific booster.\n"
" --systemd Notify systemd when initialization is done\n" " -n, --systemd\n"
" --debug Enable debug messages and log everything also to stdout.\n" " Notify systemd when initialization is done\n"
" -h, --help Print this help.\n\n", " -h, --help\n"
" Print this help.\n"
" -v, --verbose, --debug\n"
" Make diagnostic logging more verbose.\n"
"\n",
name, name, name); name, name, name);
exit(status); exit(status);

@ -113,8 +113,7 @@ private:
Daemon & operator= (const Daemon & r); Daemon & operator= (const Daemon & r);
//! Parse arguments //! Parse arguments
typedef vector<string> ArgVect; void parseArgs(int argc, char **argv);
void parseArgs(const ArgVect & args);
//! Fork to a daemon //! Fork to a daemon
void daemonize(); void daemonize();

@ -28,40 +28,14 @@
#include <ctype.h> #include <ctype.h>
#include "coverage.h" #include "coverage.h"
#include "report.h"
bool Logger::m_isOpened = false; bool Logger::m_isOpened = false;
bool Logger::m_debugMode = 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 useSyslog()
{ {
static bool checked = false; return report_get_output() == report_syslog;
static bool value = false;
if (!checked) {
checked = true;
value = !isatty(STDIN_FILENO);
}
return value;
} }
void Logger::openLog(const char * progName) 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()) { vreport((enum report_type)priority, format, va);
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);
}
} }
void Logger::logDebug(const char * format, ...) void Logger::logDebug(const char * format, ...)
{ {
if (m_debugMode) va_list va;
{ va_start(va, format);
va_list ap; Logger::writeLog(report_debug, format, va);
va_start(ap, format); va_end(va);
writeLog(LOG_DEBUG, format, ap);
va_end(ap);
}
} }
void Logger::logInfo(const char * format, ...) void Logger::logInfo(const char * format, ...)
{ {
va_list ap; va_list va;
va_start(ap, format); va_start(va, format);
writeLog(LOG_INFO, format, ap); Logger::writeLog(report_info, format, va);
va_end(ap); va_end(va);
// To avoid extra file descriptors in forked boosters closing connection to syslog
Logger::closeLog();
} }
void Logger::logWarning(const char * format, ...) void Logger::logWarning(const char * format, ...)
{ {
va_list ap; va_list va;
va_start(ap, format); va_start(va, format);
writeLog(LOG_WARNING, format, ap); Logger::writeLog(report_warning, format, va);
va_end(ap); va_end(va);
} }
void Logger::logError(const char * format, ...) void Logger::logError(const char * format, ...)
{ {
va_list ap; va_list va;
va_start(ap, format); va_start(va, format);
writeLog(LOG_ERR, format, ap); Logger::writeLog(report_error, format, va);
va_end(ap); va_end(va);
} }
void Logger::setDebugMode(bool enable) 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);
} }

@ -100,11 +100,11 @@ private:
#endif #endif
}; };
/* Allow the same logging API to be used in booster and invoker */ // QUARANTINE /* Allow the same logging API to be used in booster and invoker */
#define error( FMT, ARGS...) Logger::logError( FMT, ##ARGS) // QUARANTINE #define error( FMT, ARGS...) Logger::logError( FMT, ##ARGS)
#define warning(FMT, ARGS...) Logger::logWarning(FMT, ##ARGS) // QUARANTINE #define warning(FMT, ARGS...) Logger::logWarning(FMT, ##ARGS)
#define info( FMT, ARGS...) Logger::logInfo( FMT, ##ARGS) // QUARANTINE #define info( FMT, ARGS...) Logger::logInfo( FMT, ##ARGS)
#define debug( FMT, ARGS...) Logger::logDebug( FMT, ##ARGS) // QUARANTINE #define debug( FMT, ARGS...) Logger::logDebug( FMT, ##ARGS)
#endif // LOGGER_H #endif // LOGGER_H

Loading…
Cancel
Save