Linux
DBus Policy 적용 규칙/범위/단위 분석
yunilogs
2025. 5. 21. 00:00
- dbus-daemon은 policy 관련한 룰을 파싱해 들고 있으면서 해당 policy들을 request들에 대해서 적용한다.
- sender <-> receiver 간에 어떻게 rule을 적용하게 되는지 알아보자.
- busname 단위로 conf 파일 rule과 매칭이 되는 것으로 보임(좀 더 분석 예정)
- dispatch란? dbus-daemon으로 들어온 DBusMessage하나를 적절한 수신자에게 routing 하는 절자.
1. bus_client_policy_check_can_send()는 sender가 보낼 수 있는지에 대한 rule 검사.
BusResult
bus_client_policy_check_can_send (DBusConnection *sender,
BusClientPolicy *policy,
BusRegistry *registry,
dbus_bool_t requested_reply,
DBusConnection *addressed_recipient,
DBusConnection *receiver,
DBusMessage *message,
dbus_int32_t *toggles,
dbus_bool_t *log,
const char **privilege_param,
BusDeferredMessage **deferred_message,
char **out_rule)
{
BusResult result;
const char *privilege;
BusPolicyRule *matched_rule = NULL;
struct RuleParams params;
params.type = PARAM_SR;
params.u.sr.registry = registry;
params.u.sr.requested_reply = requested_reply;
params.u.sr.peer = receiver;
params.u.sr.message = message;
params.u.sr.name = dbus_message_get_destination (message);
_dbus_verbose (" (policy) checking send rules\n");
result = check_policy (policy, check_send_rule, ¶ms,
toggles, log, &privilege, &matched_rule);
if (result == BUS_RESULT_LATER)
{
BusContext *context = bus_connection_get_context(sender);
BusCheck *check = bus_context_get_check(context);
result = bus_check_privilege(check, message, sender, addressed_recipient, receiver,
privilege, BUS_DEFERRED_MESSAGE_CHECK_SEND, deferred_message);
if (result == BUS_RESULT_LATER && deferred_message != NULL)
bus_deferred_message_set_policy_check_info(*deferred_message, requested_reply,
*toggles, privilege);
}
else
privilege = NULL;
if (privilege_param != NULL)
*privilege_param = privilege;
if (result == BUS_RESULT_FALSE)
{
if (matched_rule && out_rule)
bus_policy_rule_to_string (matched_rule, out_rule);
}
return result;
}
2. 위의 check_policy()를 따라 들어가다보면 find_and_check_rules()가 나옴.
static void
find_and_check_rules (DBusHashTable *rules,
DBusList *prefix_rules,
CheckRuleFunc check_func,
const void *params,
dbus_int32_t *toggles,
dbus_bool_t *log,
BusResult *result,
const char **privilege,
BusPolicyRule **matched_rule)
{
const RuleParams *p = params;
const DBusList *services = NULL;
int score = 0;
if (p->type == PARAM_SR)
{
if (p->u.sr.peer != NULL)
{
DBusList *link;
여기서 peer 즉 receiver에 해당 하는 connection이 가진 service list를 가져온다.
예를들어 내가 method call 하려는 서비스가 a라면, a connection이 own하고 있는 well-know name 리스트를 가져오는 것.
services = bus_connection_get_owned_services_list (p->u.sr.peer);
link = _dbus_list_get_first_link ((DBusList **)&services);
while (link != NULL)
{
receiver connection이 own하고 있는 서비스이름 줄줄이 나옴.
const char *name = bus_service_get_name (link->data);
link = _dbus_list_get_next_link ((DBusList **)&services, link);
/* skip unique id names */
if (name[0] == ':')
continue;
해당 이름이 들고 있는(busname) rule 검사하는 것으로 보임. 좀 더 분석 예정.
score = find_and_check_rules_for_name (rules, prefix_rules, name, score,
check_func, params,
toggles, log, result,
privilege, matched_rule);
}
}
else if (p->u.sr.name != NULL)
{
score = find_and_check_rules_for_name (rules, prefix_rules, p->u.sr.name, score,
check_func, params,
toggles, log, result,
privilege, matched_rule);
}
}
else
score = find_and_check_rules_for_name (rules, prefix_rules, _dbus_string_get_const_data(p->u.name),
score, check_func, params,
toggles, log, result,
privilege, matched_rule);
/* check also wildcard rules */
score = check_rules_for_name (rules, "", score, check_func, params,
toggles, log, result, privilege, matched_rule);
}
즉, 내가 메시지를 보내려는 곳의 connection을 얻어오고, 해당 connection이 own 하고 있는 well-know name 리스트를 받아 온 뒤, 해당 이름에 대한 rule 검사를 다 하는 것으로 보인다.
그럼 내가 메시지를 보내려는 곳의 connection은 어떻게 알아내는 것인가?
1. bus_dispatch 참고
static DBusHandlerResult
bus_dispatch (DBusConnection *connection,
DBusMessage *message)
{
const char *sender, *service_name;
DBusError error;
BusTransaction *transaction;
BusContext *context;
DBusHandlerResult result;
DBusConnection *addressed_recipient;
result = DBUS_HANDLER_RESULT_HANDLED;
transaction = NULL;
addressed_recipient = NULL;
dbus_error_init (&error);
context = bus_connection_get_context (connection);
_dbus_assert (context != NULL);
/* If we can't even allocate an OOM error, we just go to sleep
* until we can.
*/
while (!bus_connection_preallocate_oom_error (connection))
_dbus_wait_for_memory ();
/* Ref connection in case we disconnect it at some point in here */
dbus_connection_ref (connection);
/* Monitors aren't meant to send messages to us. */
if (bus_connection_is_monitor (connection))
{
sender = bus_connection_get_name (connection);
/* should never happen */
if (sender == NULL)
sender = "(unknown)";
if (dbus_message_is_signal (message,
DBUS_INTERFACE_LOCAL,
"Disconnected"))
{
bus_context_log (context, DBUS_SYSTEM_LOG_INFO,
"Monitoring connection %s closed.", sender);
bus_connection_disconnected (connection);
goto out;
}
else
{
/* Monitors are not allowed to send messages, because that
* probably indicates that the monitor is incorrectly replying
* to its eavesdropped messages, and we want the authors of
* such monitors to fix them.
*/
bus_context_log (context, DBUS_SYSTEM_LOG_WARNING,
"Monitoring connection %s (%s) is not allowed "
"to send messages; closing it. Please fix the "
"monitor to not do that. "
"(message type=\"%s\" interface=\"%s\" "
"member=\"%s\" error name=\"%s\" "
"destination=\"%s\")",
sender, bus_connection_get_loginfo (connection),
dbus_message_type_to_string (
dbus_message_get_type (message)),
nonnull (dbus_message_get_interface (message),
"(unset)"),
nonnull (dbus_message_get_member (message),
"(unset)"),
nonnull (dbus_message_get_error_name (message),
"(unset)"),
nonnull (dbus_message_get_destination (message),
DBUS_SERVICE_DBUS));
dbus_connection_close (connection);
goto out;
}
}
message에서 도착지 찾는다. 근데 아래에도 동일한 코드가 한줄 더 들어가있는데 확인 해볼 것.
service_name = dbus_message_get_destination (message);
/* If service_name is NULL, if it's a signal we send it to all
* connections with a match rule. If it's not a signal, there
* are some special cases here but mostly we just bail out.
*/
if (service_name == NULL)
{
if (dbus_message_is_signal (message,
DBUS_INTERFACE_LOCAL,
"Disconnected"))
{
bus_connection_disconnected (connection);
goto out;
}
if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_SIGNAL)
{
/* DBusConnection also handles some of these automatically, we leave
* it to do so.
*
* FIXME: this means monitors won't get the opportunity to see
* non-signals with NULL destination, or their replies (which in
* practice are UnknownMethod errors)
*/
result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
goto out;
}
}
/* Create our transaction */
transaction = bus_transaction_new (context);
if (transaction == NULL)
{
BUS_SET_OOM (&error);
goto out;
}
/* Assign a sender to the message */
if (bus_connection_is_active (connection))
{
sender = bus_connection_get_name (connection);
_dbus_assert (sender != NULL);
if (!dbus_message_set_sender (message, sender))
{
BUS_SET_OOM (&error);
goto out;
}
}
else
{
/* For monitors' benefit: we don't want the sender to be able to
* trick the monitor by supplying a forged sender, and we also
* don't want the message to have no sender at all. */
if (!dbus_message_set_sender (message, ":not.active.yet"))
{
BUS_SET_OOM (&error);
goto out;
}
}
/* We need to refetch the service name here, because
* dbus_message_set_sender can cause the header to be
* reallocated, and thus the service_name pointer will become
* invalid.
*/
service_name = dbus_message_get_destination (message);
if (service_name &&
strcmp (service_name, DBUS_SERVICE_DBUS) == 0) /* to bus driver */
{
BusDeferredMessage *deferred_message = NULL;
if (!bus_transaction_capture (transaction, connection, NULL, message))
{
BUS_SET_OOM (&error);
goto out;
}
switch (bus_context_check_security_policy (context, transaction,
connection, NULL, NULL, message,
NULL, &error,
&deferred_message))
{
case BUS_RESULT_TRUE:
break;
case BUS_RESULT_FALSE:
_dbus_verbose ("Security policy rejected message\n");
goto out;
case BUS_RESULT_LATER:
/* Disable dispatching messages from the sender,
* roll back and dispatch the message once the policy result is available */
bus_deferred_message_disable_sender(deferred_message);
bus_transaction_cancel_and_free (transaction);
transaction = NULL;
result = DBUS_HANDLER_RESULT_LATER;
goto out;
}
_dbus_verbose ("Giving message to %s\n", DBUS_SERVICE_DBUS);
switch (bus_driver_handle_message (connection, transaction, message, &error))
{
case BUS_RESULT_TRUE:
break;
case BUS_RESULT_FALSE:
goto out;
case BUS_RESULT_LATER:
bus_transaction_cancel_and_free (transaction);
transaction = NULL;
result = DBUS_HANDLER_RESULT_LATER;
goto out;
}
}
else if (!bus_connection_is_active (connection)) /* clients must talk to bus driver first */
{
if (!bus_transaction_capture (transaction, connection, NULL, message))
{
BUS_SET_OOM (&error);
goto out;
}
_dbus_verbose ("Received message from non-registered client. Disconnecting.\n");
dbus_connection_close (connection);
goto out;
}
내가 보낼 곳, 즉 message를 보낼 destination이 있으면, 여기부터 아래코드들 더 분석 예정
else if (service_name != NULL) /* route to named service */
{
DBusString service_string;
BusService *service;
BusRegistry *registry;
_dbus_assert (service_name != NULL);
registry = bus_connection_get_registry (connection);
_dbus_string_init_const (&service_string, service_name);
service = bus_registry_lookup (registry, &service_string);
if (service == NULL && dbus_message_get_auto_start (message))
{
BusActivation *activation;
BusDeferredMessage *deferred_message = NULL;
if (!bus_transaction_capture (transaction, connection, NULL,
message))
{
BUS_SET_OOM (&error);
goto out;
}
activation = bus_connection_get_activation (connection);
/* This will do as much of a security policy check as it can.
* We can't do the full security policy check here, since the
* addressed recipient service doesn't exist yet. We do it before
* sending the message after the service has been created.
*/
switch (bus_activation_activate_service (activation, connection, transaction, TRUE,
message, service_name, &error,
&deferred_message))
{
case BUS_RESULT_FALSE:
_DBUS_ASSERT_ERROR_IS_SET (&error);
_dbus_verbose ("bus_activation_activate_service() failed: %s\n", error.name);
break;
case BUS_RESULT_LATER:
bus_deferred_message_disable_sender(deferred_message);
bus_transaction_cancel_and_free (transaction);
transaction = NULL;
result = DBUS_HANDLER_RESULT_LATER;
break;
case BUS_RESULT_TRUE:
break;
}
goto out;
}
else if (service == NULL)
{
if (!bus_transaction_capture (transaction, connection,
NULL, message))
{
BUS_SET_OOM (&error);
goto out;
}
dbus_set_error (&error,
DBUS_ERROR_NAME_HAS_NO_OWNER,
"Name \"%s\" does not exist",
service_name);
goto out;
}
else
{
addressed_recipient = bus_service_get_primary_owners_connection (service);
_dbus_assert (addressed_recipient != NULL);
if (!bus_transaction_capture (transaction, connection,
addressed_recipient, message))
{
BUS_SET_OOM (&error);
goto out;
}
}
}
else /* service_name == NULL */
{
if (!bus_transaction_capture (transaction, connection, NULL, message))
{
BUS_SET_OOM (&error);
goto out;
}
}
/* Now send the message to its destination (or not, if
* addressed_recipient == NULL), and match it against other connections'
* match rules.
*/
switch (bus_dispatch_matches (transaction, connection, addressed_recipient, message, NULL, &error))
{
case BUS_RESULT_TRUE:
case BUS_RESULT_FALSE:
break;
case BUS_RESULT_LATER:
/* Roll back and dispatch the message once the policy result is available */
bus_transaction_cancel_and_free (transaction);
transaction = NULL;
result = DBUS_HANDLER_RESULT_LATER;
break;
}
out:
if (dbus_error_is_set (&error))
{
/* Even if we disconnected it, pretend to send it any pending error
* messages so that monitors can observe them.
*/
if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
{
bus_connection_send_oom_error (connection, message);
/* cancel transaction due to OOM */
if (transaction != NULL)
{
bus_transaction_cancel_and_free (transaction);
transaction = NULL;
}
}
else
{
/* Try to send the real error, if no mem to do that, send
* the OOM error
*/
_dbus_assert (transaction != NULL);
if (!bus_transaction_send_error_reply (transaction, connection,
&error, message))
{
bus_connection_send_oom_error (connection, message);
/* cancel transaction due to OOM */
if (transaction != NULL)
{
bus_transaction_cancel_and_free (transaction);
transaction = NULL;
}
}
}
dbus_error_free (&error);
}
if (transaction != NULL)
{
DBusList *connections_copy = bus_transaction_copy_connections (transaction);
bus_transaction_execute_and_free (transaction);
bus_check_connection_overflow (context, connections_copy);
}
dbus_connection_unref (connection);
return result;
}