discover/discover-server: Restrict clients based on uid
authorSamuel Mendoza-Jonas <sam@mendozajonas.com>
Tue, 19 Jun 2018 06:50:28 +0000 (16:50 +1000)
committerSamuel Mendoza-Jonas <sam@mendozajonas.com>
Mon, 3 Dec 2018 03:39:57 +0000 (14:39 +1100)
If crypt support is enabled restrict what actions clients can perform by
default. Initial authorisation is set at connection time; clients
running as root are unrestricted, anything else runs as restricted until
it makes an authentication to pb-discover.

Unprivileged clients may only perform the following actions:
- Boot the default boot option.
- Cancel the autoboot timeout.
- Make an authentication request.

If a group named "petitgroup" exists then the socket permissions are
also modified so that only clients running as root or in that group may
connect to the socket.
The user-event socket is only usable by root since the two main
usecases are by utilities called by pb-discover or by a user in the
shell who will need to su to root anyway.

Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
discover/discover-server.c
discover/discover-server.h
discover/pb-discover.c
discover/platform.c
discover/platform.h
discover/user-event.c

index 34d82be828cc623bb7e5f12b949a72c23953c052..23d6113ec7ce5de5675feff0e761745d854c2442 100644 (file)
@@ -1,3 +1,4 @@
+#define _GNU_SOURCE
 
 #include <unistd.h>
 #include <stdlib.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <asm/byteorder.h>
+#include <grp.h>
+#include <sys/stat.h>
 
 #include <pb-config/pb-config.h>
 #include <talloc/talloc.h>
 #include <waiter/waiter.h>
 #include <log/log.h>
+#include <crypt/crypt.h>
+#include <i18n/i18n.h>
 
 #include "pb-protocol/pb-protocol.h"
 #include "list/list.h"
@@ -31,6 +36,7 @@ struct discover_server {
        struct list clients;
        struct list status;
        struct device_handler *device_handler;
+       bool restrict_clients;
 };
 
 struct client {
@@ -39,6 +45,8 @@ struct client {
        struct waiter *waiter;
        int fd;
        bool remote_closed;
+       bool can_modify;
+       struct waiter *auth_waiter;
 };
 
 
@@ -65,6 +73,9 @@ static int client_destructor(void *arg)
        if (client->waiter)
                waiter_remove(client->waiter);
 
+       if (client->auth_waiter)
+               waiter_remove(client->auth_waiter);
+
        list_remove(&client->list);
 
        return 0;
@@ -245,11 +256,134 @@ static int write_config_message(struct discover_server *server,
        return client_write_message(server, client, message);
 }
 
+static int write_authenticate_message(struct discover_server *server,
+               struct client *client)
+{
+       struct pb_protocol_message *message;
+       struct auth_message auth_msg;
+       int len;
+
+       auth_msg.op = AUTH_MSG_RESPONSE;
+       auth_msg.authenticated = client->can_modify;
+
+       len = pb_protocol_authenticate_len(&auth_msg);
+
+       message = pb_protocol_create_message(client,
+                       PB_PROTOCOL_ACTION_AUTHENTICATE, len);
+       if (!message)
+               return -1;
+
+       pb_protocol_serialise_authenticate(&auth_msg, message->payload, len);
+
+       return client_write_message(server, client, message);
+}
+
+static int client_auth_timeout(void *arg)
+{
+       struct client *client = arg;
+       int rc;
+
+       client->auth_waiter = NULL;
+       client->can_modify = false;
+
+       rc = write_authenticate_message(client->server, client);
+       if (rc)
+               pb_log("failed to send client auth timeout\n");
+
+       return 0;
+}
+
+static int discover_server_handle_auth_message(struct client *client,
+               struct auth_message *auth_msg)
+{
+       struct status *status;
+       char *hash;
+       int rc;
+
+       status = talloc_zero(client, struct status);
+
+       switch (auth_msg->op) {
+       case AUTH_MSG_REQUEST:
+               if (!crypt_check_password(auth_msg->password)) {
+                       rc = -1;
+                       pb_log("Client failed to authenticate\n");
+                       status->type = STATUS_ERROR;
+                       status->message = talloc_asprintf(status,
+                                       _("Password incorrect"));
+               } else {
+                       client->can_modify = true;
+                       rc = write_authenticate_message(client->server,
+                                       client);
+                       if (client->auth_waiter)
+                               waiter_remove(client->auth_waiter);
+                       client->auth_waiter = waiter_register_timeout(
+                                       client->server->waitset,
+                                       300000, /* 5 min */
+                                       client_auth_timeout, client);
+                       pb_log("Client authenticated\n");
+                       status->type = STATUS_INFO;
+                       status->message = talloc_asprintf(status,
+                                       _("Authenticated successfully"));
+               }
+               break;
+       case AUTH_MSG_SET:
+               if (client->server->restrict_clients) {
+                       if (!crypt_check_password(auth_msg->set_password.password)) {
+                               rc = -1;
+                               pb_log("Wrong password for set request\n");
+                               status->type = STATUS_ERROR;
+                               status->message = talloc_asprintf(status,
+                                               _("Password incorrect"));
+                               break;
+                       }
+               }
+
+               rc = crypt_set_password(auth_msg,
+                               auth_msg->set_password.new_password);
+               if (rc) {
+                       pb_log("Failed to set password\n");
+                       status->type = STATUS_ERROR;
+                       status->message = talloc_asprintf(status,
+                                       _("Error setting password"));
+               } else {
+                       if (!auth_msg->set_password.new_password ||
+                               !strlen(auth_msg->set_password.new_password)) {
+                               platform_set_password("");
+                               discover_server_set_auth_mode(client->server,
+                                               false);
+                               pb_log("Password cleared\n");
+                       } else {
+                               hash = crypt_get_hash(auth_msg);
+                               platform_set_password(hash);
+                               talloc_free(hash);
+                               discover_server_set_auth_mode(client->server,
+                                               true);
+                       }
+                       pb_log("System password changed\n");
+                       status->type = STATUS_ERROR;
+                       status->message = talloc_asprintf(status,
+                                       _("Password updated successfully"));
+               }
+               break;
+       default:
+               pb_log("%s: unknown op\n", __func__);
+               rc = -1;
+               break;
+       }
+
+       write_boot_status_message(client->server, client, status);
+       talloc_free(status);
+
+       return rc;
+}
+
 static int discover_server_process_message(void *arg)
 {
        struct autoboot_option *autoboot_opt;
        struct pb_protocol_message *message;
        struct boot_command *boot_command;
+       struct auth_message *auth_msg;
+       struct status *status;
        struct client *client = arg;
        struct config *config;
        char *url;
@@ -262,6 +396,56 @@ static int discover_server_process_message(void *arg)
                return 0;
        }
 
+       /*
+        * If crypt support is enabled, non-authorised clients can only delay
+        * boot, not configure options or change the default boot option.
+        */
+       if (!client->can_modify) {
+               switch (message->action) {
+               case PB_PROTOCOL_ACTION_BOOT:
+                       boot_command = talloc(client, struct boot_command);
+
+                       rc = pb_protocol_deserialise_boot_command(boot_command,
+                                       message);
+                       if (rc) {
+                               pb_log("%s: no boot command?", __func__);
+                               return 0;
+                       }
+
+                       device_handler_boot(client->server->device_handler,
+                                       client->can_modify, boot_command);
+                       break;
+               case PB_PROTOCOL_ACTION_CANCEL_DEFAULT:
+                       device_handler_cancel_default(client->server->device_handler);
+                       break;
+               case PB_PROTOCOL_ACTION_AUTHENTICATE:
+                       auth_msg = talloc(client, struct auth_message);
+                       rc = pb_protocol_deserialise_authenticate(
+                                       auth_msg, message);
+                       if (rc) {
+                               pb_log("Couldn't parse client's auth request\n");
+                               break;
+                       }
+
+                       rc = discover_server_handle_auth_message(client,
+                                       auth_msg);
+                       talloc_free(auth_msg);
+                       break;
+               default:
+                       pb_log("non-root client tried to perform action %d\n",
+                                       message->action);
+                       status = talloc_zero(client, struct status);
+                       if (status) {
+                               status->type = STATUS_ERROR;
+                               status->message = talloc_asprintf(status,
+                                               "Client must run as root to make changes");
+                               write_boot_status_message(client->server, client,
+                                               status);
+                               talloc_free(status);
+                       }
+               }
+               return 0;
+       }
 
        switch (message->action) {
        case PB_PROTOCOL_ACTION_BOOT:
@@ -275,7 +459,7 @@ static int discover_server_process_message(void *arg)
                }
 
                device_handler_boot(client->server->device_handler,
-                               boot_command);
+                               client->can_modify, boot_command);
                break;
 
        case PB_PROTOCOL_ACTION_CANCEL_DEFAULT:
@@ -327,6 +511,19 @@ static int discover_server_process_message(void *arg)
                                autoboot_opt);
                break;
 
+       /* For AUTH_MSG_SET */
+       case PB_PROTOCOL_ACTION_AUTHENTICATE:
+               auth_msg = talloc(client, struct auth_message);
+               rc = pb_protocol_deserialise_authenticate(
+                               auth_msg, message);
+               if (rc) {
+                       pb_log("Couldn't parse client's auth request\n");
+                       break;
+               }
+
+               rc = discover_server_handle_auth_message(client, auth_msg);
+               talloc_free(auth_msg);
+               break;
        default:
                pb_log_fn("invalid action %d\n", message->action);
                return 0;
@@ -336,12 +533,27 @@ static int discover_server_process_message(void *arg)
        return 0;
 }
 
+void discover_server_set_auth_mode(struct discover_server *server,
+               bool restrict_clients)
+{
+       struct client *client;
+
+       server->restrict_clients = restrict_clients;
+
+       list_for_each_entry(&server->clients, client, list) {
+               client->can_modify = !restrict_clients;
+               write_authenticate_message(server, client);
+       }
+}
+
 static int discover_server_process_connection(void *arg)
 {
        struct discover_server *server = arg;
        struct statuslog_entry *entry;
        int fd, rc, i, n_devices, n_plugins;
        struct client *client;
+       struct ucred ucred;
+       socklen_t len;
 
        /* accept the incoming connection */
        fd = accept(server->socket, NULL, NULL);
@@ -362,6 +574,30 @@ static int discover_server_process_connection(void *arg)
                                WAIT_IN, discover_server_process_message,
                                client);
 
+       /*
+        * get some info on the connecting process - if the client is being
+        * run as root allow them to make changes
+        */
+       if (server->restrict_clients) {
+               len = sizeof(struct ucred);
+               rc = getsockopt(client->fd, SOL_SOCKET, SO_PEERCRED, &ucred,
+                               &len);
+               if (rc) {
+                       pb_log("Failed to get socket info - restricting client\n");
+                       client->can_modify = false;
+               } else {
+                       pb_log("Client details: pid: %d, uid: %d, egid: %d\n",
+                                       ucred.pid, ucred.uid, ucred.gid);
+                       client->can_modify = ucred.uid == 0;
+               }
+       } else
+               client->can_modify = true;
+
+       /* send auth status to client */
+       rc = write_authenticate_message(server, client);
+       if (rc)
+               return 0;
+
        /* send sysinfo to client */
        rc = write_system_info_message(server, client, system_info_get());
        if (rc)
@@ -508,6 +744,7 @@ struct discover_server *discover_server_init(struct waitset *waitset)
 {
        struct discover_server *server;
        struct sockaddr_un addr;
+       struct group *group;
 
        server = talloc(NULL, struct discover_server);
        if (!server)
@@ -527,7 +764,6 @@ struct discover_server *discover_server_init(struct waitset *waitset)
        }
 
        talloc_set_destructor(server, server_destructor);
-
        addr.sun_family = AF_UNIX;
        strcpy(addr.sun_path, PB_SOCKET_PATH);
 
@@ -536,6 +772,13 @@ struct discover_server *discover_server_init(struct waitset *waitset)
                goto out_err;
        }
 
+       /* Allow all clients to communicate on this socket */
+       group = getgrnam("petitgroup");
+       if (group) {
+               chown(PB_SOCKET_PATH, 0, group->gr_gid);
+               chmod(PB_SOCKET_PATH, 0660);
+       }
+
        if (listen(server->socket, 8)) {
                pb_log("server socket listen: %s\n", strerror(errno));
                goto out_err;
index 9f3aa6274a699ae91ad53f8f3e26885f844f031e..9722e173399a393f793f149f7dfebeaf61395280 100644 (file)
@@ -20,6 +20,9 @@ void discover_server_destroy(struct discover_server *server);
 void discover_server_set_device_source(struct discover_server *server,
                struct device_handler *handler);
 
+void discover_server_set_auth_mode(struct discover_server *server,
+               bool restrict_clients);
+
 void discover_server_notify_device_add(struct discover_server *server,
                struct device *device);
 void discover_server_notify_boot_option_add(struct discover_server *server,
index c494eeb31ba5e595a15b7df387270c6e1d7b1781..e2b36dd499e623ec63caf752c0cc0cc844c393f3 100644 (file)
@@ -189,6 +189,9 @@ int main(int argc, char *argv[])
        if (config_get()->debug)
                pb_log_set_debug(true);
 
+       if (platform_restrict_clients())
+               discover_server_set_auth_mode(server, true);
+
        system_info_init(server);
 
        handler = device_handler_init(server, waitset, opts.dry_run == opt_yes);
index 237da3a9f0ece3427071844d9e611226da364c9b..7712ef14028772c07f39eb4d2d2a320eef1eba45 100644 (file)
@@ -213,6 +213,19 @@ int platform_get_sysinfo(struct system_info *info)
        return -1;
 }
 
+bool platform_restrict_clients(){
+       if (platform && platform->restrict_clients)
+               return platform->restrict_clients(platform);
+       return false;
+}
+
+int platform_set_password(const char *hash)
+{
+       if (platform && platform->set_password)
+               return platform->set_password(platform, hash);
+       return -1;
+}
+
 int config_set(struct config *newconfig)
 {
        int rc;
index 294056263782e7f72020a52b4d4aeaa4cfc9da41..f7d3d1c48324161f5ebf62fe6f3cce38c1b0a906 100644 (file)
@@ -12,6 +12,8 @@ struct platform {
        void            (*pre_boot)(struct platform *,
                                const struct config *);
        int             (*get_sysinfo)(struct platform *, struct system_info *);
+       bool            (*restrict_clients)(struct platform *);
+       int             (*set_password)(struct platform *, const char *hash);
        uint16_t        dhcp_arch_id;
        void            *platform_data;
 };
@@ -20,6 +22,8 @@ int platform_init(void *ctx);
 int platform_fini(void);
 const struct platform *platform_get(void);
 int platform_get_sysinfo(struct system_info *info);
+bool platform_restrict_clients(void);
+int platform_set_password(const char *hash);
 void platform_pre_boot(void);
 
 /* configuration interface */
index 734f77b3014a6905635b73e110dd2e38706e3a28..d3d4a5e8ad8962e35f0c29ebc75877e79e173146 100644 (file)
@@ -24,6 +24,7 @@
 #include <errno.h>
 #include <string.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/un.h>
 
@@ -507,7 +508,7 @@ static int user_event_boot(struct user_event *uev, struct event *event)
                cmd->boot_args = talloc_strdup(cmd, event_get_param(event, "args"));
        }
 
-       device_handler_boot(handler, cmd);
+       device_handler_boot(handler, false, cmd);
 
        talloc_free(cmd);
 
@@ -749,6 +750,10 @@ struct user_event *user_event_init(struct device_handler *handler,
                        strerror(errno));
        }
 
+       /* Don't allow events from non-priviledged users */
+       chown(PBOOT_USER_EVENT_SOCKET, 0, 0);
+       chmod(PBOOT_USER_EVENT_SOCKET, 0660);
+
        waiter_register_io(waitset, uev->socket, WAIT_IN,
                        user_event_process, uev);