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, &params,
                         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;
}