Merge branch 'selinux_replycheck' into 'main'

WIP: SELinux: add option to control checking of reply messages

See merge request dbus/dbus!199
This commit is contained in:
Christian Göttsche 2026-01-09 12:07:31 +00:00
commit a446c4df14
12 changed files with 177 additions and 5 deletions

View file

@ -60,6 +60,7 @@ struct BusContext
char *config_file;
char *type;
char *servicehelper;
char *replycheck_verb;
char *address;
char *pidfile;
char *user;
@ -571,6 +572,7 @@ process_config_every_time (BusContext *context,
DBusList **dirs;
char *addr;
const char *servicehelper;
const char *replycheck_verb;
char *s;
dbus_bool_t retval;
@ -667,6 +669,21 @@ process_config_every_time (BusContext *context,
context->servicehelper = s;
}
/* and the replycheck */
replycheck_verb = bus_selinux_convert_replycheck_option (bus_config_parser_get_replycheck (parser));
s = _dbus_strdup(replycheck_verb);
if (s == NULL && replycheck_verb != NULL)
{
BUS_SET_OOM (error);
goto failed;
}
else
{
dbus_free(context->replycheck_verb);
context->replycheck_verb = s;
}
/* Create activation subsystem */
if (context->activation)
{
@ -1307,6 +1324,7 @@ bus_context_unref (BusContext *context)
dbus_free (context->address);
dbus_free (context->user);
dbus_free (context->servicehelper);
dbus_free (context->replycheck_verb);
if (context->pidfile)
{
@ -1349,6 +1367,12 @@ bus_context_get_servicehelper (BusContext *context)
return context->servicehelper;
}
const char*
bus_context_get_replycheck_verb (BusContext *context)
{
return context->replycheck_verb;
}
dbus_bool_t
bus_context_get_systemd_activation (BusContext *context)
{
@ -1793,6 +1817,8 @@ bus_context_check_security_policy (BusContext *context,
* go on with the standard checks.
*/
if (!bus_selinux_allows_send (sender, proposed_recipient,
requested_reply,
bus_context_get_replycheck_verb (context),
dbus_message_type_to_string (dbus_message_get_type (message)),
dbus_message_get_interface (message),
dbus_message_get_member (message),

View file

@ -103,6 +103,7 @@ dbus_bool_t bus_context_get_id (BusContext
const char* bus_context_get_type (BusContext *context);
const char* bus_context_get_address (BusContext *context);
const char* bus_context_get_servicehelper (BusContext *context);
const char* bus_context_get_replycheck_verb (BusContext *context);
dbus_bool_t bus_context_get_systemd_activation (BusContext *context);
BusRegistry* bus_context_get_registry (BusContext *context);
BusConnections* bus_context_get_connections (BusContext *context);

View file

@ -113,6 +113,10 @@ bus_config_parser_element_name_to_type (const char *name)
{
return ELEMENT_ASSOCIATE;
}
else if (strcmp (name, "replycheck") == 0)
{
return ELEMENT_REPLYCHECK;
}
else if (strcmp (name, "syslog") == 0)
{
return ELEMENT_SYSLOG;
@ -177,6 +181,8 @@ bus_config_parser_element_type_to_name (ElementType type)
return "selinux";
case ELEMENT_ASSOCIATE:
return "associate";
case ELEMENT_REPLYCHECK:
return "replycheck";
case ELEMENT_SYSLOG:
return "syslog";
case ELEMENT_KEEP_UMASK:

View file

@ -47,6 +47,7 @@ typedef enum
ELEMENT_CONFIGTYPE,
ELEMENT_SELINUX,
ELEMENT_ASSOCIATE,
ELEMENT_REPLYCHECK,
ELEMENT_STANDARD_SESSION_SERVICEDIRS,
ELEMENT_STANDARD_SYSTEM_SERVICEDIRS,
ELEMENT_KEEP_UMASK,

View file

@ -42,6 +42,7 @@ struct BusConfigParser
DBusString user; /**< User the dbus-daemon runs as */
DBusString bus_type; /**< Message bus type */
DBusString service_helper; /**< Location of the setuid helper */
DBusString replycheck; /**< SELinux checking of reply messages */
DBusList *service_dirs; /**< Directories to look for services in */
};
@ -103,11 +104,15 @@ bus_config_parser_new (const DBusString *basedir,
goto failed_type;
if (!_dbus_string_init (&parser->service_helper))
goto failed_helper;
if (!_dbus_string_init (&parser->replycheck))
goto failed_reply;
/* woot! */
return parser;
/* argh. we have do do this carefully because of OOM */
failed_reply:
_dbus_string_free (&parser->service_helper);
failed_helper:
_dbus_string_free (&parser->bus_type);
failed_type:
@ -123,6 +128,7 @@ bus_config_parser_unref (BusConfigParser *parser)
{
_dbus_string_free (&parser->user);
_dbus_string_free (&parser->service_helper);
_dbus_string_free (&parser->replycheck);
_dbus_string_free (&parser->bus_type);
_dbus_list_clear_full (&parser->service_dirs, dbus_free);
dbus_free (parser);
@ -144,6 +150,7 @@ bus_config_parser_start_element (BusConfigParser *parser,
case ELEMENT_SERVICEHELPER:
case ELEMENT_USER:
case ELEMENT_CONFIGTYPE:
case ELEMENT_REPLYCHECK:
/* content about to be handled */
break;
@ -286,6 +293,28 @@ bus_config_parser_content (BusConfigParser *parser,
}
break;
case ELEMENT_REPLYCHECK:
{
const char* content_string;
if (!_dbus_string_copy (&content_sane, 0, &parser->replycheck, 0))
{
BUS_SET_OOM (error);
goto out_content;
}
content_string = _dbus_string_get_const_data (&content_sane);
if (strcmp(content_string, "none") != 0 &&
strcmp(content_string, "send") != 0 &&
strcmp(content_string, "reply_with_fallback") != 0 &&
strcmp(content_string, "reply") != 0)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Element <replycheck> has invalid content %s", content_string);
goto out_content;
}
}
break;
case ELEMENT_NONE:
case ELEMENT_BUSCONFIG:
case ELEMENT_INCLUDE:

View file

@ -117,6 +117,8 @@ struct BusConfigParser
DBusHashTable *service_context_table; /**< Map service names to SELinux contexts */
char *replycheck; /**< What permission verb to use on message replies */
unsigned int fork : 1; /**< TRUE to fork into daemon mode */
unsigned int syslog : 1; /**< TRUE to enable syslog */
@ -404,6 +406,13 @@ merge_included (BusConfigParser *parser,
included->servicehelper = NULL;
}
if (included->replycheck != NULL)
{
dbus_free (parser->replycheck);
parser->replycheck = included->replycheck;
included->replycheck = NULL;
}
while ((link = _dbus_list_pop_first_link (&included->listen_on)))
_dbus_list_append_link (&parser->listen_on, link);
@ -587,6 +596,7 @@ bus_config_parser_unref (BusConfigParser *parser)
dbus_free (parser->servicehelper);
dbus_free (parser->bus_type);
dbus_free (parser->pidfile);
dbus_free (parser->replycheck);
_dbus_list_clear_full (&parser->listen_on, dbus_free);
_dbus_list_clear_full (&parser->service_dirs,
@ -1997,6 +2007,19 @@ start_selinux_child (BusConfigParser *parser,
own_copy, context_copy))
goto oom;
return TRUE;
}
else if (strcmp (element_name, "replycheck") == 0)
{
if (!check_no_attributes (parser, "replycheck", attribute_names, attribute_values, error))
return FALSE;
if (push_element (parser, ELEMENT_REPLYCHECK) == NULL)
{
BUS_SET_OOM (error);
return FALSE;
}
return TRUE;
}
else
@ -2297,6 +2320,7 @@ bus_config_parser_end_element (BusConfigParser *parser,
case ELEMENT_SERVICEHELPER:
case ELEMENT_INCLUDEDIR:
case ELEMENT_LIMIT:
case ELEMENT_REPLYCHECK:
if (!e->had_content)
{
dbus_set_error (error, DBUS_ERROR_FAILED,
@ -2890,6 +2914,20 @@ bus_config_parser_content (BusConfigParser *parser,
e->d.limit.name);
}
break;
case ELEMENT_REPLYCHECK:
{
char *s;
e->had_content = TRUE;
if (!_dbus_string_copy_data (content, &s))
goto nomem;
dbus_free (parser->replycheck);
parser->replycheck = s;
}
break;
}
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
@ -2997,6 +3035,12 @@ bus_config_parser_get_servicehelper (BusConfigParser *parser)
return parser->servicehelper;
}
const char *
bus_config_parser_get_replycheck (BusConfigParser *parser)
{
return parser->replycheck;
}
BusPolicy*
bus_config_parser_steal_policy (BusConfigParser *parser)
{
@ -3390,6 +3434,7 @@ elements_equal (const Element *a,
case ELEMENT_CONFIGTYPE:
case ELEMENT_SELINUX:
case ELEMENT_ASSOCIATE:
case ELEMENT_REPLYCHECK:
case ELEMENT_STANDARD_SESSION_SERVICEDIRS:
case ELEMENT_STANDARD_SYSTEM_SERVICEDIRS:
case ELEMENT_KEEP_UMASK:
@ -3520,7 +3565,7 @@ config_parsers_equal (const BusConfigParser *a,
if (!lists_of_service_dirs_equal (a->service_dirs, b->service_dirs))
return FALSE;
/* FIXME: compare policy */
/* FIXME: compare service selinux ID table */
@ -3531,6 +3576,9 @@ config_parsers_equal (const BusConfigParser *a,
if (!strings_equal_or_both_null (a->pidfile, b->pidfile))
return FALSE;
if (!strings_equal_or_both_null (a->replycheck, b->replycheck))
return FALSE;
if (! bools_equal (a->fork, b->fork))
return FALSE;

View file

@ -67,6 +67,7 @@ dbus_bool_t bus_config_parser_get_syslog (BusConfigParser *parser);
dbus_bool_t bus_config_parser_get_keep_umask (BusConfigParser *parser);
const char* bus_config_parser_get_pidfile (BusConfigParser *parser);
const char* bus_config_parser_get_servicehelper (BusConfigParser *parser);
const char* bus_config_parser_get_replycheck (BusConfigParser *parser);
DBusList** bus_config_parser_get_service_dirs (BusConfigParser *parser);
DBusList** bus_config_parser_get_conf_dirs (BusConfigParser *parser);
BusPolicy* bus_config_parser_steal_policy (BusConfigParser *parser);

View file

@ -379,6 +379,7 @@ error:
* granted from the connection to the message bus or to another
* optionally supplied security identifier (e.g. for a service
* context). Currently these permissions are either send_msg or
* reply_msg (depending in the replycheck configuration) or
* acquire_svc in the dbus class.
*
* @param sender_sid source security context
@ -532,6 +533,8 @@ bus_selinux_allows_acquire_service (DBusConnection *connection,
dbus_bool_t
bus_selinux_allows_send (DBusConnection *sender,
DBusConnection *proposed_recipient,
dbus_bool_t requested_reply,
const char *replycheck_verb,
const char *msgtype,
const char *interface,
const char *member,
@ -555,6 +558,10 @@ bus_selinux_allows_send (DBusConnection *sender,
if (activation_entry)
return TRUE;
/* Skip check on reply messages. */
if (requested_reply && !replycheck_verb)
return TRUE;
if (!sender || !dbus_connection_get_unix_process_id (sender, &spid))
spid = 0;
if (!proposed_recipient || !dbus_connection_get_unix_process_id (proposed_recipient, &tpid))
@ -623,10 +630,10 @@ bus_selinux_allows_send (DBusConnection *sender,
else
recipient_sid = BUS_SID_FROM_SELINUX (bus_sid);
ret = bus_selinux_check (sender_sid,
ret = bus_selinux_check (sender_sid,
recipient_sid,
"dbus",
"send_msg",
requested_reply ? replycheck_verb : "send_msg",
&auxdata);
_dbus_string_free (&auxdata);
@ -995,3 +1002,31 @@ bus_selinux_shutdown (void)
}
#endif /* HAVE_SELINUX */
}
/**
* Convert the replycheck configuraion string into the SELinux permission verb.
*/
const char*
bus_selinux_convert_replycheck_option(const char *replycheck_option)
{
#ifdef HAVE_SELINUX
security_class_t security_class;
if (replycheck_option && strcmp (replycheck_option, "none") == 0)
return NULL;
if (replycheck_option && strcmp (replycheck_option, "send") == 0)
return "send_msg";
if (replycheck_option && strcmp (replycheck_option, "reply") == 0)
return "reply_msg";
security_class = string_to_security_class ("dbus");
if (security_class != 0 && string_to_av_perm (security_class, "reply_msg") != 0)
return "reply_msg";
return "send_msg";
#else
return NULL;
#endif /* HAVE_SELINUX */
}

View file

@ -57,6 +57,8 @@ dbus_bool_t bus_selinux_allows_acquire_service (DBusConnection *connection,
dbus_bool_t bus_selinux_allows_send (DBusConnection *sender,
DBusConnection *proposed_recipient,
dbus_bool_t requested_reply,
const char *replycheck_verb,
const char *msgtype, /* Supplementary audit data */
const char *interface,
const char *member,
@ -68,4 +70,5 @@ dbus_bool_t bus_selinux_allows_send (DBusConnection *sender,
BusSELinuxID* bus_selinux_init_connection_id (DBusConnection *connection,
DBusError *error);
const char* bus_selinux_convert_replycheck_option(const char *replycheck_option);
#endif /* BUS_SELINUX_H */

View file

@ -59,11 +59,13 @@
<!ELEMENT limit (#PCDATA)>
<!ATTLIST limit name CDATA #REQUIRED>
<!ELEMENT selinux (associate)*>
<!ELEMENT selinux (associate|
replycheck)*>
<!ELEMENT associate EMPTY>
<!ATTLIST associate
own CDATA #REQUIRED
context CDATA #REQUIRED>
<!ELEMENT replycheck (#PCDATA)>
<!ELEMENT apparmor EMPTY>
<!ATTLIST apparmor

View file

@ -1186,6 +1186,7 @@ More details below.</para>
<itemizedlist remap='TP'>
<listitem><para><emphasis remap='I'>&lt;associate&gt;</emphasis></para></listitem>
<listitem><para><emphasis remap='I'>&lt;replycheck&gt;</emphasis></para></listitem>
</itemizedlist>
@ -1219,6 +1220,23 @@ Right now the default will be the security context of the bus itself.</para>
<para>If two &lt;associate&gt; elements specify the same name, the element
appearing later in the configuration file will be used.</para>
<para>The &lt;replycheck&gt; element controls how reply messages are checked.
There are four options:</para>
<literallayout remap='.nf'>
"send" : the same SELinux permission as for request
messages is used (the previous default)
"none" : reply messages are not checked
"reply" : reply messages are checked with a distinct
SELinux permission
"reply_with_fallback" : reply messages are checked with a distinct
SELinux permission, if this permission is
defined in the loaded SELinux policy.
Otherwise the same permission as for request
messages is used
</literallayout> <!-- .fi -->
<itemizedlist remap='TP'>
<listitem><para><emphasis remap='I'>&lt;apparmor&gt;</emphasis></para></listitem>
@ -1457,7 +1475,8 @@ that class.</para>
<para>First, any time a message is routed from one connection to another
connection, the bus daemon will check permissions with the security context of
the first connection as source, security context of the second connection
as target, object class "dbus" and requested permission "send_msg".</para>
as target, object class "dbus" and requested permission "send_msg" or "reply_msg",
depending on the message type and the &lt;replycheck&gt; setting.</para>
<para>If a security context is not available for a connection

View file

@ -27,6 +27,7 @@
context="my_selinux_context_t"/>
<associate own="org.freedesktop.BlahBlahBlah"
context="foo_t"/>
<replycheck>reply_with_fallback</replycheck>
</selinux>
</busconfig>