/* * Copyright (C) 2020 Jonas Dreßler. * * This program 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. * * This program 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 this program; if not, see . */ #include "config.h" #include #include #include #include #include #include #if defined(HAVE_MEMFD_CREATE) #define READONLY_SEALS (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE) #endif static const char *teststring = "test string 1234567890"; static gboolean test_read_fd_mmap (int fd, const char *expected_string) { void *mem; int string_size; string_size = strlen (expected_string) + 1; mem = mmap (NULL, string_size, PROT_READ, MAP_PRIVATE, fd, 0); g_assert (mem != MAP_FAILED); if (strcmp (expected_string, mem) != 0) { munmap (mem, string_size); return FALSE; } munmap (mem, string_size); return TRUE; } static gboolean test_write_fd (int fd, const char *string) { int written_size, string_size; string_size = strlen (string) + 1; written_size = write (fd, string, string_size); if (written_size != string_size) return FALSE; return TRUE; } #if defined(HAVE_MEMFD_CREATE) static gboolean test_readonly_seals (int fd) { unsigned int seals; seals = fcntl (fd, F_GET_SEALS); if (seals == -1) return FALSE; if (seals != READONLY_SEALS) return FALSE; return TRUE; } #endif static gboolean test_write_read (int fd) { g_autofree char *new_string = g_uuid_string_random (); if (!test_write_fd (fd, new_string)) return FALSE; if (!test_read_fd_mmap (fd, new_string)) return FALSE; return TRUE; } #if defined(HAVE_MEMFD_CREATE) static gboolean test_open_write_read (const char *path) { int fd; fd = open (path, O_RDWR); g_assert (fd != -1); if (!test_write_read (fd)) { close (fd); return FALSE; } close (fd); return TRUE; } #endif int main (int argc, char **argv) { MetaAnonymousFile *file; int fd = -1, other_fd = -1; g_autofree char *fd_path = NULL; file = meta_anonymous_file_new (strlen (teststring) + 1, (const uint8_t *) teststring); if (!file) { g_critical ("%s: Creating file failed", __func__); return EXIT_FAILURE; } #if defined(HAVE_MEMFD_CREATE) fd = meta_anonymous_file_open_fd (file, META_ANONYMOUS_FILE_MAPMODE_PRIVATE); g_assert (fd != -1); other_fd = meta_anonymous_file_open_fd (file, META_ANONYMOUS_FILE_MAPMODE_PRIVATE); g_assert (other_fd != -1); /* When MAPMODE_PRIVATE was used, meta_anonymous_file_open_fd() should always * return the same fd. */ if (other_fd != fd) goto fail; /* If memfd_create was used and we request a MAPMODE_PRIVATE file, all the * readonly seals should be set. */ if (!test_readonly_seals (fd)) goto fail; if (!test_read_fd_mmap (fd, teststring)) goto fail; /* Writing and reading the written data should fail */ if (test_write_read (fd)) goto fail; /* Instead we should still be reading the teststring */ if (!test_read_fd_mmap (fd, teststring)) goto fail; /* Opening the fd manually in RW mode and writing to it should fail */ fd_path = g_strdup_printf ("/proc/%d/fd/%d", getpid (), fd); if (test_open_write_read (fd_path)) goto fail; /* Instead we should still be reading the teststring */ if (!test_read_fd_mmap (fd, teststring)) goto fail; /* Just to be sure test the other fd, too */ if (!test_read_fd_mmap (other_fd, teststring)) goto fail; meta_anonymous_file_close_fd (fd); meta_anonymous_file_close_fd (fd); fd = meta_anonymous_file_open_fd (file, META_ANONYMOUS_FILE_MAPMODE_SHARED); g_assert (fd != -1); other_fd = meta_anonymous_file_open_fd (file, META_ANONYMOUS_FILE_MAPMODE_SHARED); g_assert (other_fd != -1); /* The MAPMODE_SHARED fd should not have readonly seals applied */ if (test_readonly_seals (fd)) goto fail; if (!test_read_fd_mmap (fd, teststring)) goto fail; if (!test_read_fd_mmap (other_fd, teststring)) goto fail; /* Writing and reading the written data should succeed */ if (!test_write_read (fd)) goto fail; /* The other fd should still read the teststring though */ if (!test_read_fd_mmap (other_fd, teststring)) goto fail; meta_anonymous_file_close_fd (fd); meta_anonymous_file_close_fd (other_fd); /* Test an artificial out-of-space situation by setting the maximum file * size this process may create to 2 bytes, if memfd_create with * MAPMODE_PRIVATE is used, everything should still work (the existing FD * should be used). */ if (!getenv ("CI_JOB_ID")) { struct rlimit rlimit = { .rlim_cur = 2, .rlim_max = 2, }; if (setrlimit (RLIMIT_FSIZE, &rlimit) == -1) goto fail; fd = meta_anonymous_file_open_fd (file, META_ANONYMOUS_FILE_MAPMODE_PRIVATE); g_assert (fd != -1); if (!test_read_fd_mmap (fd, teststring)) goto fail; meta_anonymous_file_close_fd (fd); } #else fd = meta_anonymous_file_open_fd (file, META_ANONYMOUS_FILE_MAPMODE_PRIVATE); g_assert (fd != -1); other_fd = meta_anonymous_file_open_fd (file, META_ANONYMOUS_FILE_MAPMODE_PRIVATE); g_assert (other_fd != -1); /* Writing and reading the written data should succeed */ if (!test_write_read (fd)) goto fail; /* The other fd should still read the teststring though */ if (!test_read_fd_mmap (other_fd, teststring)) goto fail; meta_anonymous_file_close_fd (fd); meta_anonymous_file_close_fd (other_fd); fd = meta_anonymous_file_open_fd (file, META_ANONYMOUS_FILE_MAPMODE_SHARED); g_assert (fd != -1); other_fd = meta_anonymous_file_open_fd (file, META_ANONYMOUS_FILE_MAPMODE_SHARED); g_assert (other_fd != -1); if (!test_read_fd_mmap (fd, teststring)) goto fail; if (!test_read_fd_mmap (other_fd, teststring)) goto fail; /* Writing and reading the written data should succeed */ if (!test_write_read (fd)) goto fail; /* The other fd should still read the teststring though */ if (!test_read_fd_mmap (other_fd, teststring)) goto fail; meta_anonymous_file_close_fd (fd); meta_anonymous_file_close_fd (other_fd); #endif meta_anonymous_file_free (file); return EXIT_SUCCESS; fail: if (fd > 0) meta_anonymous_file_close_fd (fd); if (other_fd > 0) meta_anonymous_file_close_fd (other_fd); meta_anonymous_file_free (file); return EXIT_FAILURE; }