* Copyright (C) 2020 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include "cockpitcontrolmessages.h"
#include "common/cockpitfdpassing.h"
#include "common/cockpitjsonprint.h"
#include "cockpitmemfdread.h"
#include "cockpitsocket.h"
#include "testlib/cockpittest.h"
#include <gio/gunixfdmessage.h>
#include <gio/gunixcredentialsmessage.h>
#include <glib-unix.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <stdio.h>
static int
memfd_create_noexec (const char *name,
unsigned int flags)
{
#ifdef MFD_NOEXEC_SEAL
int fd = memfd_create (name, flags | MFD_NOEXEC_SEAL);
if (fd != -1 || errno != EINVAL)
return fd;
#endif
return memfd_create (name, flags);
}
static void
test_memfd_simple (void)
{
FILE *stream;
stream = cockpit_json_print_open_memfd ("test", 1);
gint fd = cockpit_json_print_finish_memfd (&stream);
g_autoptr(GError) error = NULL;
g_autofree gchar *content = cockpit_memfd_read (fd, &error);
g_assert_no_error (error);
close (fd);
g_assert_cmpstr (content, ==, "{\"version\": 1}");
}
static void
test_memfd_error_cases (void)
{
g_autoptr(GError) error = NULL;
g_autofree gchar *content = NULL;
FILE *stream;
gint fd;
gint r;
fd = open ("/dev/null", O_RDONLY);
content = cockpit_memfd_read (fd, &error);
cockpit_assert_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "*not memfd?*");
g_assert (content == NULL);
g_clear_error (&error);
close (fd);
fd = memfd_create_noexec ("xyz", MFD_CLOEXEC);
content = cockpit_memfd_read (fd, &error);
cockpit_assert_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "*incorrect seals set*");
g_assert (content == NULL);
g_clear_error (&error);
close (fd);
fd = memfd_create_noexec ("xyz", MFD_ALLOW_SEALING | MFD_CLOEXEC);
r = fcntl (fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE);
g_assert (r == 0);
content = cockpit_memfd_read (fd, &error);
cockpit_assert_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "*empty*");
g_assert (content == NULL);
g_clear_error (&error);
close (fd);
stream = cockpit_json_print_open_memfd ("xyz", 1);
fprintf (stream, "%20000s", "");
fd = cockpit_json_print_finish_memfd (&stream);
content = cockpit_memfd_read (fd, &error);
cockpit_assert_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "*unreasonably large*");
g_assert (content == NULL);
g_clear_error (&error);
close (fd);
stream = cockpit_json_print_open_memfd ("xyz", 1);
int tmpfd = cockpit_json_print_finish_memfd (&stream);
gchar procfile[80];
snprintf (procfile, sizeof procfile, "/proc/self/fd/%d", tmpfd);
fd = open (procfile, O_WRONLY);
g_assert_cmpint (fd, !=, -1);
r = close (tmpfd);
g_assert_cmpint (r, ==, 0);
content = cockpit_memfd_read (fd, &error);
cockpit_assert_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_BADF, "*failed to read*");
g_assert (content == NULL);
g_clear_error (&error);
close (fd);
stream = cockpit_json_print_open_memfd ("xyz", 1);
fputc (0, stream);
fd = cockpit_json_print_finish_memfd (&stream);
content = cockpit_memfd_read (fd, &error);
cockpit_assert_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "*contains nul*");
g_assert (content == NULL);
g_clear_error (&error);
close (fd);
stream = cockpit_json_print_open_memfd ("xyz", 1);
fputc (0xcc, stream);
fd = cockpit_json_print_finish_memfd (&stream);
content = cockpit_memfd_read (fd, &error);
cockpit_assert_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "*contains non-ASCII*");
g_assert (content == NULL);
g_clear_error (&error);
close (fd);
}
static void
test_memfd_json (void)
{
FILE *stream;
stream = cockpit_json_print_open_memfd ("test", 1);
cockpit_json_print_string_property (stream, "hello", "world", -1);
cockpit_json_print_integer_property (stream, "size", 200);
cockpit_json_print_bool_property (stream, "truth", true);
cockpit_json_print_bool_property (stream, "falsth", false);
gint fd = cockpit_json_print_finish_memfd (&stream);
g_autoptr(GError) error = NULL;
g_autoptr(JsonObject) object = cockpit_memfd_read_json (fd, &error);
g_assert_no_error (error);
close (fd);
g_assert_cmpint (json_object_get_int_member (object, "version"), ==, 1);
g_assert_cmpstr (json_object_get_string_member (object, "hello"), ==, "world");
g_assert_cmpint (json_object_get_int_member (object, "size"), ==, 200);
g_assert_cmpint (json_object_get_boolean_member (object, "truth"), ==, TRUE);
g_assert_cmpint (json_object_get_boolean_member (object, "falsth"), ==, FALSE);
}
static void
test_memfd_json_error_cases (void)
{
g_autoptr(GError) error = NULL;
g_autoptr(JsonObject) object = NULL;
gint fd;
gint r;
fd = memfd_create_noexec ("xyz", MFD_CLOEXEC | MFD_ALLOW_SEALING);
g_assert_cmpint (write (fd, "beh", 3), ==, 3);
r = fcntl (fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE);
g_assert (r == 0);
object = cockpit_memfd_read_json (fd, &error);
g_assert (object == NULL);
cockpit_assert_error_matches (error, JSON_PARSER_ERROR, JSON_PARSER_ERROR_INVALID_BAREWORD, "*unexpected identifier*");
g_clear_error (&error);
close (fd);
fd = memfd_create_noexec ("xyz", MFD_CLOEXEC | MFD_ALLOW_SEALING);
g_assert_cmpint (write (fd, "[]", 2), ==, 2);
r = fcntl (fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE);
g_assert (r == 0);
object = cockpit_memfd_read_json (fd, &error);
g_assert (object == NULL);
cockpit_assert_error_matches (error, JSON_PARSER_ERROR, -1, "*Not a JSON object*");
close (fd);
}
static GSList *live_control_messages;
static void
assert_live_control_messages (gint expected)
{
g_assert_cmpint (g_slist_length (live_control_messages), ==, expected);
}
static void
remove_message_from_list (gpointer data,
GObject *where_the_object_was)
{
for (GSList **n = &live_control_messages; *n; n = &(*n)->next)
if ((*n)->data == where_the_object_was)
{
*n = g_slist_delete_link (*n, *n);
return;
}
g_error ("Couldn't find control message %p in list", where_the_object_was);
}
static void
receive_cmsgs (GSocket *socket,
CockpitControlMessages *ccm)
{
gchar buffer[1];
GInputVector vector = { buffer, sizeof buffer };
GError *error = NULL;
g_socket_receive_message (socket,
NULL,
&vector, 1,
&ccm->messages, &ccm->n_messages,
NULL, NULL,
&error);
for (gint i = 0; i < ccm->n_messages; i++)
{
live_control_messages = g_slist_prepend (live_control_messages, ccm->messages[i]);
g_object_weak_ref (G_OBJECT (ccm->messages[i]), remove_message_from_list, NULL);
}
g_assert_no_error (error);
}
static void
receive_nothing (GSocket *socket)
{
g_auto(CockpitControlMessages) ccm = COCKPIT_CONTROL_MESSAGES_INIT;
receive_cmsgs (socket, &ccm);
g_assert (cockpit_control_messages_empty (&ccm));
}
static gint *
receive_fds (GSocket *socket,
gint *out_nfds,
GError **error)
{
g_auto(CockpitControlMessages) ccm = COCKPIT_CONTROL_MESSAGES_INIT;
receive_cmsgs (socket, &ccm);
int n_fds;
const gint *fds = cockpit_control_messages_peek_fd_list (&ccm, &n_fds, error);
if (fds == NULL)
return NULL;
gint *result = g_new (int, n_fds + 1);
for (gint i = 0; i < n_fds; i++)
result[i] = dup (fds[i]);
result[n_fds] = -1;
*out_nfds = n_fds;
return result;
}
static void
free_fds (gint **inout_fds,
gint *inout_nfds)
{
gint *fds = *inout_fds;
gint nfds = *inout_nfds;
for (gint i = 0; i < nfds; i++)
{
g_assert (fds[i] != -1);
int r = close (fds[i]);
g_assert (r == 0);
}
g_assert (fds[nfds] == -1);
g_free (fds);
*inout_fds = NULL;
*inout_nfds = 0;
}
static gint
receive_fd (GSocket *socket,
GError **error)
{
g_auto(CockpitControlMessages) ccm = COCKPIT_CONTROL_MESSAGES_INIT;
receive_cmsgs (socket, &ccm);
int fd = cockpit_control_messages_peek_single_fd (&ccm, error);
if (fd == -1)
return -1;
return dup (fd);
}
static void
send_cmsgs (GSocket *socket,
GSocketControlMessage **messages,
gint n_messages,
gint n_bytes)
{
const gchar buffer[100] = "";
g_assert_cmpint(n_bytes, <=, sizeof buffer);
GOutputVector vector = { buffer, n_bytes };
GError *error = NULL;
g_socket_send_message (socket,
NULL,
&vector, 1,
messages, n_messages,
0, NULL, &error);
g_assert_no_error (error);
}
static void
send_nothing (GSocket *socket,
gint n_bytes)
{
send_cmsgs (socket, NULL, 0, n_bytes);
}
static GSocketControlMessage *
make_fd_message (const gint *fds,
gint n_fds)
{
g_autoptr(GUnixFDList) fdl = g_unix_fd_list_new ();
for (gint i = 0; i < n_fds; i++)
{
GError *error = NULL;
g_unix_fd_list_append (fdl, fds[i], &error);
g_assert_no_error (error);
}
return g_unix_fd_message_new_with_fd_list (fdl);
}
static void
send_fds (GSocket *socket,
const gint *fds,
gint n_fds)
{
g_autoptr(GSocketControlMessage) fdm = make_fd_message (fds, n_fds);
send_cmsgs (socket, &fdm, 1, 1);
}
static void
send_fd (GSocket *socket,
gint fd)
{
send_fds (socket, &fd, 1);
}
static void
assert_base_state (GSocket *one,
GSocket *two)
{
assert_live_control_messages (0);
g_assert (g_socket_condition_check (one, G_IO_IN | G_IO_OUT) == G_IO_OUT);
g_assert (g_socket_condition_check (two, G_IO_IN | G_IO_OUT) == G_IO_OUT);
}
static void
test_unix_socket_simple (void)
{
g_autoptr(GSocket) one, two;
cockpit_socket_socketpair (&one, &two);
assert_base_state (one, two);
send_nothing (one, 1);
receive_nothing (two);
assert_base_state (one, two);
send_nothing (two, 1);
receive_nothing (one);
assert_base_state (one, two);
send_fd (one, 1);
GError *error = NULL;
gint fd = receive_fd (two, &error);
g_assert_no_error (error);
g_assert (fd != -1);
close (fd);
assert_base_state (one, two);
send_fds (one, (gint []){ 0, 1, 2}, 3);
gint n_fds = 0;
gint *fds = receive_fds (two, &n_fds, &error);
g_assert_no_error (error);
g_assert (fds != NULL);
g_assert_cmpint (n_fds, ==, 3);
free_fds (&fds, &n_fds);
assert_base_state (one, two);
int two_fd = g_socket_get_fd (two);
g_unix_set_fd_nonblocking (two_fd, FALSE, &error);
g_assert_no_error (error);
send_fd (one, 1);
int r = cockpit_socket_receive_fd (two_fd, &fd);
g_assert_cmpint (r, ==, 1);
g_assert (fd != -1);
close (fd);
cockpit_socket_send_fd (two_fd, 1);
fd = receive_fd (one, &error);
g_assert_no_error (error);
g_assert (fd != -1);
close (fd);
assert_base_state (one, two);
}
static void
test_unix_socket_partial_read (void)
{
g_autoptr(GSocket) one, two;
cockpit_socket_socketpair (&one, &two);
assert_base_state (one, two);
* read with the first byte of the message with which it was sent.
*
* we depend on this because we start on the cockpit-ws side by
* reading a single byte, but we will send the json blob as part of
* the first full packet from cockpit-tls.
*/
send_nothing (one, 10);
int fd1 = 1;
g_autoptr(GSocketControlMessage) fdm = make_fd_message (&fd1, 1);
send_cmsgs (one, &fdm, 1, 10);
for (gint i = 0; i < 20; i++)
{
g_autoptr(GError) error = NULL;
gint fd = receive_fd (two, &error);
if (fd != -1)
{
g_assert_cmpint (i, ==, 10);
close (fd);
}
else
{
cockpit_assert_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "*0 control message*");
}
}
}
static void
test_unix_socket_error_cases (void)
{
g_autoptr(GSocket) one, two;
cockpit_socket_socketpair (&one, &two);
assert_base_state (one, two);
send_nothing (one, 1);
GError *error = NULL;
int fd = receive_fd (two, &error);
g_assert (fd == -1);
cockpit_assert_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "*0 control message*");
g_clear_error (&error);
assert_base_state (one, two);
send_fds (one, (const gint []){ 0, 1, 2}, 3);
fd = receive_fd (two, &error);
g_assert (fd == -1);
cockpit_assert_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "*received 3*1 expected*");
g_clear_error (&error);
assert_base_state (one, two);
* enable SO_PASSCRED for that.
*/
int truth = 1;
int r = setsockopt (g_socket_get_fd (two), SOL_SOCKET, SO_PASSCRED, &truth, sizeof truth);
g_assert (r == 0);
g_autoptr(GSocketControlMessage) creds = g_unix_credentials_message_new ();
send_cmsgs (one, &creds, 1, 1);
fd = receive_fd (two, &error);
g_assert (fd == -1);
cockpit_assert_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
"*GUnixCredentialsMessage*GUnixFDMessage expected*");
g_clear_error (&error);
assert_base_state (one, two);
g_autoptr(GUnixFDList) fdl = g_unix_fd_list_new ();
g_unix_fd_list_append (fdl, 1, &error);
g_assert_no_error (error);
g_autoptr(GSocketControlMessage) fdm = g_unix_fd_message_new_with_fd_list (fdl);
GSocketControlMessage *messages[] = { creds, fdm };
send_cmsgs (one, messages, G_N_ELEMENTS (messages), 1);
fd = receive_fd (two, &error);
g_assert (fd == -1);
g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL);
g_assert (strstr (error->message, "2 control messages (one message"));
g_clear_error (&error);
assert_base_state (one, two);
}
static void
test_unix_socket_combined (void)
{
g_autoptr(GSocket) one, two;
cockpit_socket_socketpair (&one, &two);
assert_base_state (one, two);
FILE *stream = cockpit_json_print_open_memfd ("xyz", 1);
cockpit_json_print_string_property (stream, "test", "it worked!", -1);
gint fd = cockpit_json_print_finish_memfd (&stream);
send_fd (one, fd);
close (fd);
g_auto(CockpitControlMessages) ccm = COCKPIT_CONTROL_MESSAGES_INIT;
receive_cmsgs (two, &ccm);
g_autoptr(GError) error = NULL;
g_autoptr(JsonObject) json = cockpit_memfd_read_json_from_control_messages (&ccm, &error);
g_assert_no_error (error);
g_assert_cmpint (json_object_get_int_member (json, "version"), ==, 1);
g_assert_cmpstr (json_object_get_string_member (json, "test"), ==, "it worked!");
}
int
main (int argc,
char **argv)
{
cockpit_test_init (&argc, &argv);
g_test_add_func ("/json/fd/memfd/simple", test_memfd_simple);
g_test_add_func ("/json/fd/memfd/error-cases", test_memfd_error_cases);
g_test_add_func ("/json/fd/memfd/json", test_memfd_json);
g_test_add_func ("/json/fd/memfd/json/error-cases", test_memfd_json_error_cases);
g_test_add_func ("/json/fd/unix-socket/simple", test_unix_socket_simple);
g_test_add_func ("/json/fd/unix-socket/partial-read", test_unix_socket_partial_read);
g_test_add_func ("/json/fd/unix-socket/error-cases", test_unix_socket_error_cases);
g_test_add_func ("/json/fd/unix-socket/combined", test_unix_socket_combined);
return g_test_run ();
}