diff --git a/Makefile.am b/Makefile.am index d787fff8be..c3446446f7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -130,7 +130,7 @@ dist-luarock: $(LUACRUN_ROCK) endif crun_CFLAGS = -I $(abs_top_builddir)/libocispec/src -I $(abs_top_srcdir)/libocispec/src -D CRUN_LIBDIR="\"$(CRUN_LIBDIR)\"" -crun_SOURCES = src/crun.c src/run.c src/delete.c src/kill.c src/pause.c src/unpause.c src/spec.c \ +crun_SOURCES = src/crun.c src/run.c src/delete.c src/kill.c src/pause.c src/unpause.c src/features.c src/spec.c \ src/exec.c src/list.c src/create.c src/start.c src/state.c src/update.c src/ps.c \ src/checkpoint.c src/restore.c src/libcrun/cloned_binary.c @@ -143,7 +143,7 @@ endif EXTRA_DIST = COPYING COPYING.libcrun README.md NEWS SECURITY.md rpm/crun.spec.in autogen.sh \ src/crun.h src/list.h src/run.h src/delete.h src/kill.h src/pause.h src/unpause.h \ - src/create.h src/start.h src/state.h src/exec.h src/spec.h src/update.h src/ps.h \ + src/create.h src/start.h src/state.h src/exec.h src/features.h src/spec.h src/update.h src/ps.h \ src/checkpoint.h src/restore.h src/libcrun/seccomp_notify.h src/libcrun/seccomp_notify_plugin.h \ src/libcrun/container.h src/libcrun/seccomp.h src/libcrun/ebpf.h \ src/libcrun/cgroup.h src/libcrun/cgroup-cgroupfs.h \ diff --git a/src/crun.c b/src/crun.c index d96b6a151c..ba9a7062d0 100644 --- a/src/crun.c +++ b/src/crun.c @@ -45,6 +45,7 @@ #include "spec.h" #include "pause.h" #include "unpause.h" +#include "features.h" #include "ps.h" #include "checkpoint.h" #include "restore.h" @@ -137,6 +138,7 @@ enum COMMAND_UPDATE, COMMAND_PAUSE, COMMAND_UNPAUSE, + COMMAND_FEATURES, COMMAND_PS, COMMAND_CHECKPOINT, COMMAND_RESTORE, @@ -155,6 +157,7 @@ struct commands_s commands[] = { { COMMAND_CREATE, "create", crun_command_create { COMMAND_UPDATE, "update", crun_command_update }, { COMMAND_PAUSE, "pause", crun_command_pause }, { COMMAND_UNPAUSE, "resume", crun_command_unpause }, + { COMMAND_FEATURES, "features", crun_command_features }, #if HAVE_CRIU && HAVE_DLOPEN { COMMAND_CHECKPOINT, "checkpoint", crun_command_checkpoint }, { COMMAND_RESTORE, "restore", crun_command_restore }, @@ -170,6 +173,7 @@ static char doc[] = "\nCOMMANDS:\n" "\tcreate - create a container\n" "\tdelete - remove definition for a container\n" "\texec - exec a command in a running container\n" + "\tfeatures - show the enabled features\n" "\tlist - list known containers\n" "\tkill - send a signal to the container init process\n" "\tps - show the processes in the container\n" diff --git a/src/features.c b/src/features.c new file mode 100644 index 0000000000..ade03805b8 --- /dev/null +++ b/src/features.c @@ -0,0 +1,314 @@ +/* + * crun - OCI runtime written in C + * + * crun is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * crun is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with crun. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "crun.h" +#include "libcrun/container.h" +#include "libcrun/utils.h" + +#define cleanup_struct_features __attribute__ ((cleanup (cleanup_struct_features_free))) + +static char doc[] = "OCI runtime"; + +static struct argp_option options[] = { { 0 } }; + +static char args_doc[] = "features"; + +const unsigned char *json_string; + +size_t json_length; + +static error_t +parse_opt (int key, char *arg arg_unused, struct argp_state *state arg_unused) +{ + if (key != ARGP_KEY_NO_ARGS) + { + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +static struct argp run_argp = { options, parse_opt, args_doc, doc, NULL, NULL, NULL }; + +void +add_string_to_json (yajl_gen json_gen, const char *key, const char *value) +{ + yajl_gen_string (json_gen, (const unsigned char *) key, strlen (key)); + yajl_gen_string (json_gen, (const unsigned char *) value, strlen (value)); +} + +void +add_bool_to_json (yajl_gen json_gen, const char *key, int value) +{ + yajl_gen_string (json_gen, (const unsigned char *) key, strlen (key)); + yajl_gen_bool (json_gen, value); +} + +void +add_array_to_json (yajl_gen json_gen, const char *key, const char **array) +{ + yajl_gen_string (json_gen, (const unsigned char *) key, strlen (key)); + yajl_gen_array_open (json_gen); + + for (size_t i = 0; array[i] != NULL; i++) + { + yajl_gen_string (json_gen, (const unsigned char *) array[i], strlen (array[i])); + } + + yajl_gen_array_close (json_gen); +} + +void +crun_features_add_hooks (yajl_gen json_gen, const char **hooks) +{ + add_array_to_json (json_gen, "hooks", hooks); +} + +void +crun_features_add_mount_options (yajl_gen json_gen, const char **mount_options) +{ + add_array_to_json (json_gen, "mountOptions", mount_options); +} + +void +crun_features_add_namespaces (yajl_gen json_gen, const struct linux_info_s *linux) +{ + add_array_to_json (json_gen, "namespaces", linux->namespaces); +} + +void +crun_features_add_capabilities (yajl_gen json_gen, const struct linux_info_s *linux) +{ + add_array_to_json (json_gen, "capabilities", linux->capabilities); +} + +void +crun_features_add_cgroup_info (yajl_gen json_gen, const struct linux_info_s *linux) +{ + yajl_gen_string (json_gen, (const unsigned char *) "cgroup", strlen ("cgroup")); + yajl_gen_map_open (json_gen); + + add_bool_to_json (json_gen, "v1", linux->cgroup.v1); + add_bool_to_json (json_gen, "v2", linux->cgroup.v2); + add_bool_to_json (json_gen, "systemd", linux->cgroup.systemd); + add_bool_to_json (json_gen, "systemdUser", linux->cgroup.systemdUser); + + yajl_gen_map_close (json_gen); +} + +void +crun_features_add_seccomp_info (yajl_gen json_gen, const struct linux_info_s *linux) +{ + yajl_gen_string (json_gen, (const unsigned char *) "seccomp", strlen ("seccomp")); + yajl_gen_map_open (json_gen); + + add_bool_to_json (json_gen, "enabled", linux->seccomp.enabled); + add_array_to_json (json_gen, "actions", linux->seccomp.actions); + add_array_to_json (json_gen, "operators", linux->seccomp.operators); + add_array_to_json (json_gen, "archs", linux->seccomp.archs); + + yajl_gen_map_close (json_gen); +} + +void +crun_features_add_apparmor_info (yajl_gen json_gen, const struct linux_info_s *linux) +{ + yajl_gen_string (json_gen, (const unsigned char *) "apparmor", strlen ("apparmor")); + yajl_gen_map_open (json_gen); + + add_bool_to_json (json_gen, "enabled", linux->apparmor.enabled); + + // Close the map for apparmor + yajl_gen_map_close (json_gen); +} + +void +crun_features_add_selinux_info (yajl_gen json_gen, const struct linux_info_s *linux) +{ + yajl_gen_string (json_gen, (const unsigned char *) "selinux", strlen ("selinux")); + yajl_gen_map_open (json_gen); + + add_bool_to_json (json_gen, "enabled", linux->selinux.enabled); + + // Close the map for selinux + yajl_gen_map_close (json_gen); +} + +void +crun_features_add_linux_info (yajl_gen json_gen, const struct linux_info_s *linux) +{ + yajl_gen_string (json_gen, (const unsigned char *) "linux", strlen ("linux")); + yajl_gen_map_open (json_gen); + + crun_features_add_namespaces (json_gen, linux); + crun_features_add_capabilities (json_gen, linux); + crun_features_add_cgroup_info (json_gen, linux); + crun_features_add_seccomp_info (json_gen, linux); + crun_features_add_apparmor_info (json_gen, linux); + crun_features_add_selinux_info (json_gen, linux); + + yajl_gen_map_close (json_gen); +} + +static inline void +cleanup_struct_features_free (struct features_info_s **info) +{ + if (info == NULL || *info == NULL) + return; + + struct features_info_s *ptr = *info; + + // Free oci_version_min if it is not NULL + if (ptr->oci_version_min != NULL) + { + free ((void *) ptr->oci_version_min); + ptr->oci_version_min = NULL; // Set to NULL after freeing + } + + // Free oci_version_max if it is not NULL + if (ptr->oci_version_max != NULL) + { + free ((void *) ptr->oci_version_max); + ptr->oci_version_max = NULL; // Set to NULL after freeing + } + if (ptr->hooks != NULL) + { + for (size_t i = 0; ptr->hooks[i] != NULL; i++) + { + free ((void *) ptr->hooks[i]); + } + free (ptr->hooks); + } + if (ptr->mount_options != NULL) + { + for (size_t i = 0; ptr->mount_options[i] != NULL; i++) + { + free ((void *) ptr->mount_options[i]); + } + free (ptr->mount_options); + } + if (ptr->linux.namespaces != NULL) + { + for (size_t i = 0; ptr->linux.namespaces[i] != NULL; i++) + { + free ((void *) ptr->linux.namespaces[i]); + } + free (ptr->linux.namespaces); + } + + if (ptr->linux.capabilities != NULL) + { + for (size_t i = 0; ptr->linux.capabilities[i] != NULL; i++) + { + free ((void *) ptr->linux.capabilities[i]); + } + free (ptr->linux.capabilities); + } + if (ptr->linux.seccomp.actions != NULL) + { + for (size_t i = 0; ptr->linux.seccomp.actions[i] != NULL; i++) + { + free ((void *) ptr->linux.seccomp.actions[i]); + } + free (ptr->linux.seccomp.actions); + } + if (ptr->linux.seccomp.operators != NULL) + { + for (size_t i = 0; ptr->linux.seccomp.operators[i] != NULL; i++) + { + free ((void *) ptr->linux.seccomp.operators[i]); + } + free (ptr->linux.seccomp.operators); + } + if (ptr->linux.seccomp.archs != NULL) + { + for (size_t i = 0; ptr->linux.seccomp.archs[i] != NULL; i++) + { + free ((void *) ptr->linux.seccomp.archs[i]); + } + free (ptr->linux.seccomp.archs); + } + + free (ptr); + *info = NULL; +} + +int +crun_command_features (struct crun_global_arguments *global_args, int argc, char **argv, libcrun_error_t *err) +{ + argp_parse (&run_argp, argc, argv, 0, 0, &options); + + // Call the function in features.c to gather the feature information + cleanup_struct_features struct features_info_s *info = NULL; + int result = libcrun_container_get_features (&info); + if (result != 0) + { + libcrun_make_error (err, 0, "Failed to gather features information."); + return result; + } + + // Prepare the JSON output + yajl_gen json_gen = yajl_gen_alloc (NULL); + if (json_gen == NULL) + { + libcrun_make_error (err, errno, "Failed to initialize json structure."); + return 0; + } + yajl_gen_status status; + + yajl_gen_config (json_gen, yajl_gen_beautify, 1); // Optional: Enable pretty formatting + + // Start building the JSON + yajl_gen_map_open (json_gen); + + // Add ociVersionMin field + add_string_to_json (json_gen, "ociVersionMin", info->oci_version_min); + + // Add ociVersionMax field + add_string_to_json (json_gen, "ociVersionMax", info->oci_version_max); + + // Add hooks array + crun_features_add_hooks (json_gen, info->hooks); + + // Add mountOptions array + crun_features_add_mount_options (json_gen, info->mount_options); + + // Add linux struct info + crun_features_add_linux_info (json_gen, &info->linux); + + // End building the JSON + yajl_gen_map_close (json_gen); + + yajl_gen_get_buf (json_gen, &json_string, &json_length); + + printf ("%s", (const char *) json_string); + + yajl_gen_free (json_gen); + + return 0; +} diff --git a/src/features.h b/src/features.h new file mode 100644 index 0000000000..1c91ce9fcd --- /dev/null +++ b/src/features.h @@ -0,0 +1,25 @@ +/* + * crun - OCI runtime written in C + * + * Copyright (C) 2017, 2018, 2019 Giuseppe Scrivano + * crun is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * crun is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with crun. If not, see . + */ +#ifndef FEATURES_H +#define FEATURES_H + +#include "crun.h" + +int crun_command_features (struct crun_global_arguments *global_args, int argc, char **argv, libcrun_error_t *error); + +#endif diff --git a/src/libcrun/container.c b/src/libcrun/container.c index ff621bbc56..160004b650 100644 --- a/src/libcrun/container.c +++ b/src/libcrun/container.c @@ -36,6 +36,7 @@ #include #include #include "status.h" +#include "mount_flags.h" #include "linux.h" #include "terminal.h" #include "io_priority.h" @@ -45,6 +46,7 @@ #include #include #include +#include #include #ifdef HAVE_SYSTEMD @@ -92,6 +94,69 @@ struct sync_socket_message_s typedef runtime_spec_schema_defs_hook hook; +// linux hooks +const char *hooks[] = { + "prestart", + "createRuntime", + "createContainer", + "startContainer", + "poststart", + "poststop" +}; + +// linux namespaces +static const char *namespaces[] = { + "cgroup", + "ipc", + "mount", + "network", + "pid", + "user", + "uts" +}; + +static const char *actions[] = { + "SCMP_ACT_ALLOW", + "SCMP_ACT_ERRNO", + "SCMP_ACT_KILL", + "SCMP_ACT_KILL_PROCESS", + "SCMP_ACT_KILL_THREAD", + "SCMP_ACT_LOG", + "SCMP_ACT_NOTIFY", + "SCMP_ACT_TRACE", + "SCMP_ACT_TRAP" +}; + +const char *operators[] = { + "SCMP_CMP_NE", + "SCMP_CMP_LT", + "SCMP_CMP_LE", + "SCMP_CMP_EQ", + "SCMP_CMP_GE", + "SCMP_CMP_GT", + "SCMP_CMP_MASKED_EQ", +}; + +static const char *archs[] = { + "SCMP_ARCH_AARCH64", + "SCMP_ARCH_ARM", + "SCMP_ARCH_MIPS", + "SCMP_ARCH_MIPS64", + "SCMP_ARCH_MIPS64N32", + "SCMP_ARCH_MIPSEL", + "SCMP_ARCH_MIPSEL64", + "SCMP_ARCH_MIPSEL64N32", + "SCMP_ARCH_PPC", + "SCMP_ARCH_PPC64", + "SCMP_ARCH_PPC64LE", + "SCMP_ARCH_RISCV64", + "SCMP_ARCH_S390", + "SCMP_ARCH_S390X", + "SCMP_ARCH_X32", + "SCMP_ARCH_X86", + "SCMP_ARCH_X86_64" +}; + static const char spec_file[] = "\ {\n\ \"ociVersion\": \"1.0.0\",\n\ @@ -3706,6 +3771,143 @@ libcrun_container_update_from_values (libcrun_context_t *context, const char *id return ret; } +void +populate_array_field (const char ***field, const char *array[], size_t num_elements) +{ + *field = xmalloc0 ((num_elements + 1) * sizeof (char *)); + size_t i; + for (i = 0; i < num_elements; i++) + { + (*field)[i] = xstrdup (array[i]); + } + + (*field)[i] = NULL; // Terminate the array with NULL +} + +void +populate_capabilities (struct features_info_s *info, const char ***capabilities, size_t *num_capabilities) +{ + cap_value_t i; + *num_capabilities = 0; + for (i = 0;; i++) + { + char *v = cap_to_name (i); + if (v == NULL) + break; + char *endptr; + strtol (v, &endptr, 10); + if (endptr != v) + { + // Non-numeric or non-zero value encountered, break the loop + break; + } + (*num_capabilities)++; + } + + *capabilities = xmalloc0 ((*num_capabilities + 1) * sizeof (const char *)); + size_t index = 0; + for (i = 0;; i++) + { + char *v = cap_to_name (i); + if (v == NULL) + break; + char *endptr; + strtol (v, &endptr, 10); + if (endptr != v) + { + // Non-numeric or non-zero value encountered, break the loop + break; + } + + // Convert capability name to uppercase + for (int j = 0; v[j] != '\0'; j++) + { + v[j] = toupper (v[j]); + } + + (*capabilities)[index] = v; + index++; + } + + (*capabilities)[index] = NULL; // Terminate the array with NULL + populate_array_field (&(info->linux.capabilities), *capabilities, *num_capabilities); +} + +void +retrieve_mount_options (struct features_info_s **info) +{ + // Retrieve mount options from wordlist + const struct propagation_flags_s *mount_options_list = get_mount_flags_from_wordlist (); + + // Calculate the number of mount options + size_t num_mount_options = 0; + while (mount_options_list[num_mount_options].name != NULL) + { + num_mount_options++; + } + + // Allocate memory for mount options in info struct + (*info)->mount_options = xmalloc0 (num_mount_options * sizeof (char *)); + + // Copy mount options to info struct + for (size_t i = 0; i < num_mount_options; i++) + { + (*info)->mount_options[i] = xstrdup (mount_options_list[i].name); + } +} + +int +libcrun_container_get_features (struct features_info_s **info) +{ + // Allocate memory for the features_info_s structure + *info = xmalloc0 (sizeof (struct features_info_s)); + // Hardcoded feature information + (*info)->oci_version_min = xstrdup ("1.0.0"); + (*info)->oci_version_max = xstrdup ("1.1.0-rc.3"); + + // Populate hooks + size_t num_hooks = sizeof (hooks) / sizeof (hooks[0]); + populate_array_field (&((*info)->hooks), hooks, num_hooks); + + // Populate mount_options + retrieve_mount_options (info); + + // Populate namespaces + size_t num_namspaces = sizeof (namespaces) / sizeof (namespaces[0]); + populate_array_field (&((*info)->linux.namespaces), namespaces, num_namspaces); + + // Populate capabilities + size_t num_capabilities = 0; + const char **capabilities = NULL; + populate_capabilities (*info, &capabilities, &num_capabilities); + + // Hardcode the values for cgroup + (*info)->linux.cgroup.v1 = true; + (*info)->linux.cgroup.v2 = true; +#ifdef HAVE_SYSTEMD + (*info)->linux.cgroup.systemd = true; + (*info)->linux.cgroup.systemdUser = true; +#endif + + // Put seccomp values + (*info)->linux.seccomp.enabled = true; + // Populate actions + size_t num_actions = sizeof (actions) / sizeof (actions[0]); + populate_array_field (&((*info)->linux.seccomp.actions), actions, num_actions); + // Populate operators + size_t num_operators = sizeof (operators) / sizeof (operators[0]); + populate_array_field (&((*info)->linux.seccomp.operators), operators, num_operators); + // Populate archs + size_t num_archs = sizeof (archs) / sizeof (archs[0]); + populate_array_field (&((*info)->linux.seccomp.archs), archs, num_archs); + + // Put values for apparmor and selinux + (*info)->linux.apparmor.enabled = true; + (*info)->linux.selinux.enabled = true; + + return 0; +} + int libcrun_container_spec (bool root, FILE *out, libcrun_error_t *err arg_unused) { diff --git a/src/libcrun/container.h b/src/libcrun/container.h index 9e4243e676..0c2349c115 100644 --- a/src/libcrun/container.h +++ b/src/libcrun/container.h @@ -95,6 +95,45 @@ typedef struct libcrun_container_s libcrun_container_t; typedef struct libcrun_context_s libcrun_context_t; struct container_entrypoint_s; +struct cgroup_info_s +{ + bool v1; + bool v2; + bool systemd; + bool systemdUser; +}; +struct seccomp_info_s +{ + bool enabled; + const char **actions; + const char **operators; + const char **archs; +}; +struct apparmor_info_s +{ + bool enabled; +}; +struct selinux_info_s +{ + bool enabled; +}; +struct linux_info_s +{ + const char **namespaces; + const char **capabilities; + struct cgroup_info_s cgroup; + struct seccomp_info_s seccomp; + struct apparmor_info_s apparmor; + struct selinux_info_s selinux; +}; +struct features_info_s +{ + const char *oci_version_min; + const char *oci_version_max; + const char **hooks; + const char **mount_options; + struct linux_info_s linux; +}; struct libcrun_checkpoint_restore_s { @@ -184,6 +223,8 @@ LIBCRUN_PUBLIC int libcrun_container_update_from_values (libcrun_context_t *cont struct libcrun_update_value_s *values, size_t len, libcrun_error_t *err); +LIBCRUN_PUBLIC int libcrun_container_get_features (struct features_info_s **info); + LIBCRUN_PUBLIC int libcrun_container_spec (bool root, FILE *out, libcrun_error_t *err); LIBCRUN_PUBLIC int libcrun_container_pause (libcrun_context_t *context, const char *id, libcrun_error_t *err); diff --git a/src/libcrun/mount_flags.c b/src/libcrun/mount_flags.c index 41296a3d90..fd4dd9ff93 100644 --- a/src/libcrun/mount_flags.c +++ b/src/libcrun/mount_flags.c @@ -431,3 +431,17 @@ libcrun_str2mount_flags (const char *name) { return libcrun_mount_flag_in_word_set (name, strlen (name)); } + +const struct propagation_flags_s * +get_mount_flags_from_wordlist(void) { + struct propagation_flags_s *flags; + size_t num_wordlist_flags = sizeof(wordlist) / sizeof(wordlist[0]); + + flags = xmalloc0 (sizeof(struct propagation_flags_s) * num_wordlist_flags); + + for (size_t i = 0; i < num_wordlist_flags; i++) { + flags[i].name = wordlist[i].name; + } + + return flags; +} diff --git a/src/libcrun/mount_flags.h b/src/libcrun/mount_flags.h index 726960901b..a93fce6cd0 100644 --- a/src/libcrun/mount_flags.h +++ b/src/libcrun/mount_flags.h @@ -35,5 +35,6 @@ struct propagation_flags_s }; const struct propagation_flags_s *libcrun_str2mount_flags (const char *name); +const struct propagation_flags_s *get_mount_flags_from_wordlist (); #endif