Merge pull request #2 from sailfishos/jb53844_sandboxed_boosters

Sandboxed application boosters
pull/1/head
Simo Piiroinen 4 years ago committed by GitHub
commit 402d9fde2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

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

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

@ -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 <stdlib.h>
#include <stdarg.h>
#include <syslog.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#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, ...)

@ -22,6 +22,13 @@
#ifndef REPORT_H
#define REPORT_H
#include <syslog.h>
#include <stdarg.h>
#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

@ -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)
{
debug("%s: Error reading message: %s\n", __FUNCTION__, strerror(errno));
debug("%s: Error reading message: %m\n", __FUNCTION__);
*msg = 0;
return false;
}

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

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

@ -24,6 +24,7 @@
#include <sys/types.h>
#include <fstream>
#include <dirent.h>
#include <string.h>
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);
}

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

@ -25,6 +25,7 @@
#include "singleinstance.h"
#include "socketmanager.h"
#include "logger.h"
#include "report.h"
#include <cstdlib>
#include <dlfcn.h>
@ -50,8 +51,12 @@
#include <grp.h>
#include <libgen.h>
#include <dbus/dbus.h>
#include "coverage.h"
#include "sailjail.h"
Booster::Booster() :
m_appData(new AppData),
m_connection(NULL),
@ -61,7 +66,6 @@ Booster::Booster() :
m_boostedApplication("default"),
m_bootMode(false)
{
Connection::setMountNamespace(Connection::getMountNamespace(getpid()));
}
Booster::~Booster()
@ -211,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)
@ -240,13 +246,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))
{
@ -285,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());
@ -642,7 +646,6 @@ const string Booster::socketId() const
return id;
}
pid_t Booster::invokersPid()
{
if (m_connection->isReportAppExitStatusNeeded())
@ -726,3 +729,4 @@ std::string Booster::getFinalName(const std::string &name)
}
return name;
}

@ -21,12 +21,11 @@
#include "connection.h"
#include "logger.h"
#include "report.h"
#include <sys/socket.h>
#include <sys/un.h> /* for getsockopt */
#include <sys/stat.h> /* for chmod */
#include <limits.h>
#include <sstream>
#include <cstring>
#include <cstdlib>
#include <cerrno>
@ -34,10 +33,6 @@
#include <stdexcept>
#include <sys/syslog.h>
#define INVOKER_PATH "/usr/bin/invoker"
std::pair<dev_t, ino_t> Connection::s_mntNS = std::pair<dev_t, ino_t>();
Connection::Connection(int socketFd, bool testMode) :
m_testMode(testMode),
m_fd(-1),
@ -71,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;
}
@ -79,8 +80,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 +115,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)
@ -194,7 +155,7 @@ bool Connection::recvMsg(uint32_t *msg)
}
}
const char * Connection::recvStr()
char *Connection::recvStr()
{
if (!m_testMode)
{
@ -284,7 +245,7 @@ string Connection::receiveAppName()
return string();
}
const char* name = recvStr();
char *name = recvStr();
if (!name)
{
Logger::logError("Connection: receiving application name");
@ -298,7 +259,7 @@ string Connection::receiveAppName()
bool Connection::receiveExec()
{
const char* filename = recvStr();
char *filename = recvStr();
if (!filename)
return false;
@ -328,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<bool>(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
@ -387,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<char *>(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
@ -476,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:
@ -524,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:
@ -563,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<int>(m_io, m_io + IO_DESCRIPTOR_COUNT));
appData->setIDs(m_uid, m_gid);
}
@ -594,37 +543,3 @@ pid_t Connection::peerPid()
return cr.pid;
}
void Connection::setMountNamespace(std::pair<dev_t, ino_t> mntNS)
{
s_mntNS = mntNS;
}
std::pair<dev_t, ino_t> 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<dev_t, ino_t>();
}
return std::pair<dev_t, ino_t>(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);
}

@ -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<dev_t, ino_t> getMountNamespace(pid_t pid);
//! \brief Set required mount namespace for invoker processes
static void setMountNamespace(std::pair<dev_t, ino_t> 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);
@ -152,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;
@ -164,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;
@ -173,7 +161,6 @@ private:
gid_t m_gid;
uid_t m_uid;
static std::pair<dev_t, ino_t> s_mntNS;
#ifdef UNIT_TEST
friend class Ut_Connection;

@ -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 <systemd/sd-daemon.h>
#include <unistd.h>
#include <poll.h>
#include <getopt.h>
#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<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.");
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 <application>\n"
" -d, --daemon\n"
" Run as %s a daemon.\n"
" -a, --application=<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);

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

@ -28,40 +28,14 @@
#include <ctype.h>
#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);
}

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

Loading…
Cancel
Save