test: test-policy-priv do broadcast tests before a policy holder is uploaded
authorDjalal Harouni <tixxdz@opendz.org>
Wed, 8 Oct 2014 09:59:17 +0000 (10:59 +0100)
committerDjalal Harouni <tixxdz@opendz.org>
Thu, 9 Oct 2014 19:32:34 +0000 (20:32 +0100)
Add broadcast tests, and modify RUN_UNPRIVILEGED() so we can specify
the uid/gid of the user to drop in and run tests.

Move all the tests that check the default behaviour of the bus and
before a policy holder is uploaded to their function:
test_priv_before_policy_upload().

Each test is documented.

Signed-off-by: Djalal Harouni <tixxdz@opendz.org>
test/test-policy-priv.c

index a35710cd5ab7d500189156f8e37c64617e1bad57..5c7db29bea72a47ceaae36b1b6326bce39caa24b 100644 (file)
@@ -6,7 +6,9 @@
 #include <stdint.h>
 #include <stdbool.h>
 #include <unistd.h>
+#include <time.h>
 #include <sys/capability.h>
+#include <sys/eventfd.h>
 #include <sys/ioctl.h>
 #include <sys/wait.h>
 
 #define UNPRIV_UID 65534
 #define UNPRIV_GID 65534
 
-#define RUN_UNPRIVILEGED(_child_, _parent_) ({                         \
+enum kdbus_drop_user {
+       DO_NOT_DROP,
+       DROP_SAME_UNPRIV_USER,
+       DROP_OTHER_UNPRIV_USER,
+};
+
+#define RUN_UNPRIVILEGED(child_uid, child_gid, _child_, _parent_) ({   \
                pid_t pid, rpid;                                        \
                int ret;                                                \
                                                                        \
                pid = fork();                                           \
                if (pid == 0) {                                         \
-                       ret = drop_privileges(UNPRIV_UID, UNPRIV_GID);  \
+                       ret = drop_privileges(child_uid, child_gid);    \
                        if (ret < 0)                                    \
                                _exit(ret);                             \
                                                                        \
@@ -44,7 +52,7 @@
        })
 
 #define RUN_UNPRIVILEGED_CONN(_var_, _bus_, _code_)                    \
-       RUN_UNPRIVILEGED(({                                             \
+       RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({                     \
                struct kdbus_conn *_var_;                               \
                _var_ = kdbus_hello(_bus_, 0, NULL, 0);                 \
                ASSERT_EXIT(_var_);                                     \
                kdbus_conn_free(_var_);                                 \
        }), ({ 0; }))
 
+static int test_policy_priv_by_id(const char *bus,
+                                 struct kdbus_conn *conn_dst,
+                                 bool drop_second_user,
+                                 int parent_status,
+                                 int child_status)
+{
+       int ret;
+       uint64_t expected_cookie = time(NULL) ^ 0xdeadbeef;
+
+       ASSERT_RETURN(conn_dst);
+
+       ret = RUN_UNPRIVILEGED_CONN(unpriv, bus, ({
+               ret = kdbus_msg_send(unpriv, NULL,
+                                    expected_cookie, 0, 0, 0,
+                                    conn_dst->id);
+               ASSERT_EXIT(ret == child_status);
+       }));
+       ASSERT_RETURN(ret >= 0);
+
+       ret = kdbus_msg_recv_poll(conn_dst, 100, NULL, NULL);
+       ASSERT_RETURN(ret == parent_status);
+
+       return 0;
+}
+
+static int test_policy_priv_by_broadcast(const char *bus,
+                                        struct kdbus_conn *conn_dst,
+                                        int drop_second_user,
+                                        int parent_status,
+                                        int child_status)
+{
+       int ret;
+       int efd;
+       eventfd_t event_status = 0;
+       struct kdbus_msg *msg = NULL;
+       uid_t second_uid = UNPRIV_UID;
+       gid_t second_gid = UNPRIV_GID;
+       struct kdbus_conn *child_2 = conn_dst;
+       uint64_t expected_cookie = time(NULL) ^ 0xdeadbeef;
+
+       /* Drop to another unprivileged user other than UNPRIV_UID */
+       if (drop_second_user == DROP_OTHER_UNPRIV_USER) {
+               second_uid = UNPRIV_UID - 1;
+               second_gid = UNPRIV_GID - 1;
+       }
+
+       /* child will signal parent to send broadcast */
+       efd = eventfd(0, EFD_CLOEXEC);
+       ASSERT_RETURN_VAL(efd >= 0, efd);
+
+       ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({
+               struct kdbus_conn *child;
+
+               child = kdbus_hello(bus, 0, NULL, 0);
+               ASSERT_EXIT(child);
+
+               ret = kdbus_add_match_empty(child);
+               ASSERT_EXIT(ret == 0);
+
+               /* signal parent */
+               ret = eventfd_write(efd, 1);
+               ASSERT_EXIT(ret == 0);
+
+               ret = kdbus_msg_recv_poll(child, 300, &msg, NULL);
+               ASSERT_EXIT(ret == child_status);
+
+               /*
+                * If we expect the child to get the broadcast
+                * message, then check the received cookie.
+                */
+               if (ret == 0) {
+                       ASSERT_EXIT(expected_cookie == msg->cookie);
+               }
+
+               /* Use expected_cookie since 'msg' might be NULL */
+               ret = kdbus_msg_send(child, NULL, expected_cookie + 1,
+                                    0, 0, 0, KDBUS_DST_ID_BROADCAST);
+               ASSERT_EXIT(ret == 0);
+
+               kdbus_msg_free(msg);
+               kdbus_conn_free(child);
+       }),
+       ({
+               if (drop_second_user == DO_NOT_DROP) {
+                       ASSERT_RETURN(child_2);
+
+                       ret = eventfd_read(efd, &event_status);
+                       ASSERT_RETURN(ret >= 0 && event_status == 1);
+
+                       ret = kdbus_msg_send(child_2, NULL,
+                                            expected_cookie, 0, 0, 0,
+                                            KDBUS_DST_ID_BROADCAST);
+                       ASSERT_RETURN(ret == 0);
+
+                       ret = kdbus_msg_recv_poll(child_2, 300,
+                                                 &msg, NULL);
+                       ASSERT_RETURN(ret == parent_status);
+
+                       /*
+                        * Check returned cookie in case we expect
+                        * success.
+                        */
+                       if (ret == 0) {
+                               ASSERT_RETURN(msg->cookie ==
+                                             expected_cookie + 1);
+                       }
+
+                       kdbus_msg_free(msg);
+               } else {
+                       /*
+                        * Two unprivileged users will try to
+                        * communicate using broadcast.
+                        */
+                       ret = RUN_UNPRIVILEGED(second_uid, second_gid, ({
+                               child_2 = kdbus_hello(bus, 0, NULL, 0);
+                               ASSERT_EXIT(child_2);
+
+                               ret = kdbus_add_match_empty(child_2);
+                               ASSERT_EXIT(ret == 0);
+
+                               ret = eventfd_read(efd, &event_status);
+                               ASSERT_RETURN(ret >= 0 && event_status == 1);
+
+                               ret = kdbus_msg_send(child_2, NULL,
+                                               expected_cookie, 0, 0, 0,
+                                               KDBUS_DST_ID_BROADCAST);
+                               ASSERT_EXIT(ret == 0);
+
+                               ret = kdbus_msg_recv_poll(child_2, 100,
+                                                         &msg, NULL);
+                               ASSERT_EXIT(ret == parent_status);
+
+                               /*
+                                * Check returned cookie in case we expect
+                                * success.
+                                */
+                               if (ret == 0) {
+                                       ASSERT_RETURN(msg->cookie ==
+                                                     expected_cookie + 1);
+                               }
+
+                               kdbus_msg_free(msg);
+                               kdbus_conn_free(child_2);
+                       }),
+                       ({ 0; }));
+               }
+       }));
+
+       close(efd);
+
+       return ret;
+}
+
 static void nosig(int sig)
 {
 }
 
+static int test_priv_before_policy_upload(struct kdbus_test_env *env)
+{
+       int ret;
+       struct kdbus_conn *conn;
+
+       conn = kdbus_hello(env->buspath, 0, NULL, 0);
+       ASSERT_RETURN(conn);
+
+       /*
+        * Make sure unprivileged bus user cannot acquire names
+        * before registring any policy holder.
+        */
+
+       ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ret = kdbus_name_acquire(unpriv, "com.example.a", NULL);
+               ASSERT_EXIT(ret < 0);
+       }));
+       ASSERT_RETURN(ret == 0);
+
+       /*
+        * Make sure unprivileged bus users cannot talk by default
+        * to privileged ones, unless a policy holder that allows
+        * this was uploaded.
+        */
+
+       ret = test_policy_priv_by_id(env->buspath, conn, false,
+                                    -ETIMEDOUT, -EPERM);
+       ASSERT_RETURN(ret == 0);
+
+       /* Activate matching for a privileged connection */
+       ret = kdbus_add_match_empty(conn);
+       ASSERT_RETURN(ret == 0);
+
+       /*
+        * First make sure that BROADCAST with msg flag
+        * KDBUS_MSG_FLAGS_EXPECT_REPLY will fail with -ENOTUNIQ
+        */
+       ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
+               ret = kdbus_msg_send(unpriv, NULL, 0xdeadbeef,
+                                    KDBUS_MSG_FLAGS_EXPECT_REPLY,
+                                    5000000000ULL, 0,
+                                    KDBUS_DST_ID_BROADCAST);
+               ASSERT_EXIT(ret == -ENOTUNIQ);
+       }));
+       ASSERT_RETURN(ret == 0);
+
+       /*
+        * Test broadcast with a privileged connection.
+        *
+        * The first receiver should get the broadcast message since
+        * the sender is a privileged connection.
+        *
+        * The privileged connection should not get the broadcast
+        * message since the sender is an unprivileged connection.
+        * It will fail with -ETIMEDOUT.
+        *
+        */
+
+       ret = test_policy_priv_by_broadcast(env->buspath, conn,
+                                           DO_NOT_DROP,
+                                           -ETIMEDOUT, EXIT_SUCCESS);
+       ASSERT_RETURN(ret == 0);
+
+
+       /*
+        * Test broadcast with two unprivileged connections running
+        * under the same user.
+        *
+        * Both connections should succeed.
+        */
+
+       ret = test_policy_priv_by_broadcast(env->buspath, NULL,
+                                           DROP_SAME_UNPRIV_USER,
+                                           EXIT_SUCCESS, EXIT_SUCCESS);
+       ASSERT_RETURN(ret == 0);
+
+       /*
+        * Test broadcast with two unprivileged connections running
+        * under different users.
+        *
+        * Both connections will fail with -ETIMEDOUT.
+        */
+
+       ret = test_policy_priv_by_broadcast(env->buspath, NULL,
+                                           DROP_OTHER_UNPRIV_USER,
+                                           -ETIMEDOUT, -ETIMEDOUT);
+       ASSERT_RETURN(ret == 0);
+
+       kdbus_conn_free(conn);
+
+       return ret;
+}
+
 static int test_policy_priv(struct kdbus_test_env *env)
 {
        struct kdbus_conn *conn_a, *conn_b, *conn, *owner;
@@ -95,31 +349,14 @@ static int test_policy_priv(struct kdbus_test_env *env)
        conn = kdbus_hello(env->buspath, 0, NULL, 0);
        ASSERT_RETURN(conn);
 
-       /* Before registering any policy holder */
-
-       /*
-        * Make sure unprivileged bus user cannot acquire names
-        * before registring any policy holder.
-        */
-
-       ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
-               ret = kdbus_name_acquire(unpriv, "com.example.a", NULL);
-               ASSERT_EXIT(ret < 0);
-       }));
-       ASSERT_RETURN(ret >= 0);
-
        /*
-        * Make sure unprivileged bus users cannot talk by default
-        * to privileged ones, unless a policy holder that allows
-        * this was uploaded.
+        * Before registering any policy holder, make sure that the
+        * bus is secure by default. This test is necessary, it catches
+        * several cases where old D-Bus was vulnerable.
         */
 
-       ret = RUN_UNPRIVILEGED_CONN(unpriv, env->buspath, ({
-               ret = kdbus_msg_send(unpriv, NULL, 0xdeadbeef,
-                                    0, 0, 0, conn->id);
-               ASSERT_EXIT(ret == -EPERM);
-       }));
-       ASSERT_RETURN(ret >= 0);
+       ret = test_priv_before_policy_upload(env);
+       ASSERT_RETURN(ret == 0);
 
        /* Register policy holder */
 
@@ -582,9 +819,9 @@ static int test_policy_priv(struct kdbus_test_env *env)
        ASSERT_RETURN(owner);
 
        ret = kdbus_name_acquire(owner, "com.example.c", NULL);
-       ASSERT_EXIT(ret >= 0);
+       ASSERT_RETURN(ret >= 0);
 
-       ret = RUN_UNPRIVILEGED(({
+       ret = RUN_UNPRIVILEGED(UNPRIV_UID, UNPRIV_GID, ({
                struct kdbus_conn *unpriv;
 
                /* wait for parent to be finished */