From 68a0330ce4bd2b0e0af3975187d0912ae0007c54 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Wed, 3 Jan 2024 15:30:07 +0000 Subject: [PATCH] utils: Add a specialized function to print errno for mount(2) mount(2) uses ENOSPC to represent an arbitrary anti-denial-of-service limit being exceeded, which is outside the usual meaning of "No space left on device". We can make this clearer by catching that particular failure mode and giving users a hint. Reference: https://bugzilla.kernel.org/show_bug.cgi?id=218336 Resolves: https://github.com/ValveSoftware/steam-runtime/issues/637 Signed-off-by: Simon McVittie --- bind-mount.c | 19 ++++++++++++++++++- bubblewrap.c | 16 ++++++++-------- utils.c | 36 ++++++++++++++++++++++++++++++++++++ utils.h | 4 ++++ 4 files changed, 66 insertions(+), 9 deletions(-) diff --git a/bind-mount.c b/bind-mount.c index 57b42368..593417e8 100644 --- a/bind-mount.c +++ b/bind-mount.c @@ -571,7 +571,24 @@ die_with_bind_result (bind_mount_result res, /* message is leaked, but we're exiting unsuccessfully anyway, so ignore */ if (want_errno) - fprintf (stderr, ": %s", strerror (saved_errno)); + { + switch (res) + { + case BIND_MOUNT_ERROR_MOUNT: + case BIND_MOUNT_ERROR_REMOUNT_DEST: + case BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT: + fprintf (stderr, ": %s", mount_strerror (saved_errno)); + break; + + case BIND_MOUNT_ERROR_REALPATH_DEST: + case BIND_MOUNT_ERROR_REOPEN_DEST: + case BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD: + case BIND_MOUNT_ERROR_FIND_DEST_MOUNT: + case BIND_MOUNT_SUCCESS: + default: + fprintf (stderr, ": %s", strerror (saved_errno)); + } + } fprintf (stderr, "\n"); exit (1); diff --git a/bubblewrap.c b/bubblewrap.c index e894af76..6d0e73c3 100644 --- a/bubblewrap.c +++ b/bubblewrap.c @@ -1113,7 +1113,7 @@ privileged_op (int privileged_op_socket, case PRIV_SEP_OP_PROC_MOUNT: if (mount ("proc", arg1, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL) != 0) - die_with_error ("Can't mount proc on %s", arg1); + die_with_mount_error ("Can't mount proc on %s", arg1); break; case PRIV_SEP_OP_TMPFS_MOUNT: @@ -1132,19 +1132,19 @@ privileged_op (int privileged_op_socket, cleanup_free char *opt = label_mount (mode, opt_file_label); if (mount ("tmpfs", arg1, "tmpfs", MS_NOSUID | MS_NODEV, opt) != 0) - die_with_error ("Can't mount tmpfs on %s", arg1); + die_with_mount_error ("Can't mount tmpfs on %s", arg1); break; } case PRIV_SEP_OP_DEVPTS_MOUNT: if (mount ("devpts", arg1, "devpts", MS_NOSUID | MS_NOEXEC, "newinstance,ptmxmode=0666,mode=620") != 0) - die_with_error ("Can't mount devpts on %s", arg1); + die_with_mount_error ("Can't mount devpts on %s", arg1); break; case PRIV_SEP_OP_MQUEUE_MOUNT: if (mount ("mqueue", arg1, "mqueue", 0, NULL) != 0) - die_with_error ("Can't mount mqueue on %s", arg1); + die_with_mount_error ("Can't mount mqueue on %s", arg1); break; case PRIV_SEP_OP_SET_HOSTNAME: @@ -3060,11 +3060,11 @@ main (int argc, * receive mounts from the real root, but don't * propagate mounts to the real root. */ if (mount (NULL, "/", NULL, MS_SILENT | MS_SLAVE | MS_REC, NULL) < 0) - die_with_error ("Failed to make / slave"); + die_with_mount_error ("Failed to make / slave"); /* Create a tmpfs which we will use as / in the namespace */ if (mount ("tmpfs", base_path, "tmpfs", MS_NODEV | MS_NOSUID, NULL) != 0) - die_with_error ("Failed to mount tmpfs"); + die_with_mount_error ("Failed to mount tmpfs"); old_cwd = get_current_dir_name (); @@ -3083,7 +3083,7 @@ main (int argc, die_with_error ("Creating newroot failed"); if (mount ("newroot", "newroot", NULL, MS_SILENT | MS_MGC_VAL | MS_BIND | MS_REC, NULL) < 0) - die_with_error ("setting up newroot bind"); + die_with_mount_error ("setting up newroot bind"); if (mkdir ("oldroot", 0755)) die_with_error ("Creating oldroot failed"); @@ -3149,7 +3149,7 @@ main (int argc, /* The old root better be rprivate or we will send unmount events to the parent namespace */ if (mount ("oldroot", "oldroot", NULL, MS_SILENT | MS_REC | MS_PRIVATE, NULL) != 0) - die_with_error ("Failed to make old root rprivate"); + die_with_mount_error ("Failed to make old root rprivate"); if (umount2 ("oldroot", MNT_DETACH)) die_with_error ("unmount old root"); diff --git a/utils.c b/utils.c index a4b2f214..4015b66a 100644 --- a/utils.c +++ b/utils.c @@ -71,6 +71,21 @@ die_with_error (const char *format, ...) exit (1); } +void +die_with_mount_error (const char *format, ...) +{ + va_list args; + int errsv; + + errsv = errno; + + va_start (args, format); + warnv (format, args, mount_strerror (errsv)); + va_end (args); + + exit (1); +} + void die (const char *format, ...) { @@ -893,3 +908,24 @@ label_exec (UNUSED const char *exec_label) #endif return 0; } + +/* + * Like strerror(), but specialized for a failed mount(2) call. + */ +const char * +mount_strerror (int errsv) +{ + switch (errsv) + { + case ENOSPC: + /* "No space left on device" misleads users into thinking there + * is some sort of disk-space problem, but mount(2) uses that + * errno value to mean something more like "limit exceeded". */ + return ("Limit exceeded (ENOSPC). " + "(Hint: Check that /proc/sys/fs/mount-max is sufficient, " + "typically 100000)"); + + default: + return strerror (errsv); + } +} diff --git a/utils.h b/utils.h index d68370be..2b7cc4b2 100644 --- a/utils.h +++ b/utils.h @@ -56,6 +56,8 @@ void warn (const char *format, ...) __attribute__((format (printf, 1, 2))); void die_with_error (const char *format, ...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2))); +void die_with_mount_error (const char *format, + ...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2))); void die (const char *format, ...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2))); void die_oom (void) __attribute__((__noreturn__)); @@ -134,6 +136,8 @@ char *label_mount (const char *opt, int label_exec (const char *exec_label); int label_create_file (const char *file_label); +const char *mount_strerror (int errsv); + static inline void cleanup_freep (void *p) {