summaryrefslogtreecommitdiffstats
path: root/src/libsystemd/sd-journal/test-journal-verify.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libsystemd/sd-journal/test-journal-verify.c')
-rw-r--r--src/libsystemd/sd-journal/test-journal-verify.c210
1 files changed, 210 insertions, 0 deletions
diff --git a/src/libsystemd/sd-journal/test-journal-verify.c b/src/libsystemd/sd-journal/test-journal-verify.c
new file mode 100644
index 0000000..edce440
--- /dev/null
+++ b/src/libsystemd/sd-journal/test-journal-verify.c
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "chattr-util.h"
+#include "fd-util.h"
+#include "iovec-util.h"
+#include "journal-file-util.h"
+#include "journal-verify.h"
+#include "log.h"
+#include "mmap-cache.h"
+#include "rm-rf.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "tests.h"
+
+#define N_ENTRIES 6000
+#define RANDOM_RANGE 77
+
+static void bit_toggle(const char *fn, uint64_t p) {
+ uint8_t b;
+ ssize_t r;
+ int fd;
+
+ fd = open(fn, O_RDWR|O_CLOEXEC);
+ assert_se(fd >= 0);
+
+ r = pread(fd, &b, 1, p/8);
+ assert_se(r == 1);
+
+ b ^= 1 << (p % 8);
+
+ r = pwrite(fd, &b, 1, p/8);
+ assert_se(r == 1);
+
+ safe_close(fd);
+}
+
+static int raw_verify(const char *fn, const char *verification_key) {
+ _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL;
+ JournalFile *f;
+ int r;
+
+ m = mmap_cache_new();
+ assert_se(m != NULL);
+
+ r = journal_file_open(
+ /* fd= */ -1,
+ fn,
+ O_RDONLY,
+ JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0),
+ 0666,
+ /* compress_threshold_bytes= */ UINT64_MAX,
+ /* metrics= */ NULL,
+ m,
+ /* template= */ NULL,
+ &f);
+ if (r < 0)
+ return r;
+
+ r = journal_file_verify(f, verification_key, NULL, NULL, NULL, false);
+ (void) journal_file_close(f);
+
+ return r;
+}
+
+static int run_test(const char *verification_key, ssize_t max_iterations) {
+ _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL;
+ char t[] = "/var/tmp/journal-XXXXXX";
+ struct stat st;
+ JournalFile *f;
+ JournalFile *df;
+ usec_t from = 0, to = 0, total = 0;
+ uint64_t start, end;
+ int r;
+
+ m = mmap_cache_new();
+ assert_se(m != NULL);
+
+ /* journal_file_open() requires a valid machine id */
+ if (sd_id128_get_machine(NULL) < 0)
+ return log_tests_skipped("No valid machine ID found");
+
+ test_setup_logging(LOG_DEBUG);
+
+ assert_se(mkdtemp(t));
+ assert_se(chdir(t) >= 0);
+ (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
+
+ log_info("Generating a test journal");
+
+ assert_se(journal_file_open(
+ /* fd= */ -1,
+ "test.journal",
+ O_RDWR|O_CREAT,
+ JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0),
+ 0666,
+ /* compress_threshold_bytes= */ UINT64_MAX,
+ /* metrics= */ NULL,
+ m,
+ /* template= */ NULL,
+ &df) == 0);
+
+ for (size_t n = 0; n < N_ENTRIES; n++) {
+ _cleanup_free_ char *test = NULL;
+ struct iovec iovec;
+ struct dual_timestamp ts;
+
+ dual_timestamp_now(&ts);
+ assert_se(asprintf(&test, "RANDOM=%li", random() % RANDOM_RANGE));
+ iovec = IOVEC_MAKE_STRING(test);
+ assert_se(journal_file_append_entry(
+ df,
+ &ts,
+ /* boot_id= */ NULL,
+ &iovec,
+ /* n_iovec= */ 1,
+ /* seqnum= */ NULL,
+ /* seqnum_id= */ NULL,
+ /* ret_object= */ NULL,
+ /* ret_offset= */ NULL) == 0);
+ }
+
+ (void) journal_file_offline_close(df);
+
+ log_info("Verifying with key: %s", strna(verification_key));
+
+ assert_se(journal_file_open(
+ /* fd= */ -1,
+ "test.journal",
+ O_RDONLY,
+ JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0),
+ 0666,
+ /* compress_threshold_bytes= */ UINT64_MAX,
+ /* metrics= */ NULL,
+ m,
+ /* template= */ NULL,
+ &f) == 0);
+ journal_file_print_header(f);
+ journal_file_dump(f);
+
+ assert_se(journal_file_verify(f, verification_key, &from, &to, &total, true) >= 0);
+
+ if (verification_key && JOURNAL_HEADER_SEALED(f->header))
+ log_info("=> Validated from %s to %s, %s missing",
+ FORMAT_TIMESTAMP(from),
+ FORMAT_TIMESTAMP(to),
+ FORMAT_TIMESPAN(total > to ? total - to : 0, 0));
+
+ (void) journal_file_close(f);
+ assert_se(stat("test.journal", &st) >= 0);
+
+ start = 38448 * 8 + 0;
+ end = max_iterations < 0 ? (uint64_t)st.st_size * 8 : start + max_iterations;
+ log_info("Toggling bits %"PRIu64 " to %"PRIu64, start, end);
+
+ for (uint64_t p = start; p < end; p++) {
+ bit_toggle("test.journal", p);
+
+ if (max_iterations < 0)
+ log_info("[ %"PRIu64"+%"PRIu64"]", p / 8, p % 8);
+
+ r = raw_verify("test.journal", verification_key);
+ /* Suppress the notice when running in the limited (CI) mode */
+ if (verification_key && max_iterations < 0 && r >= 0)
+ log_notice(ANSI_HIGHLIGHT_RED ">>>> %"PRIu64" (bit %"PRIu64") can be toggled without detection." ANSI_NORMAL, p / 8, p % 8);
+
+ bit_toggle("test.journal", p);
+ }
+
+ assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
+
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ const char *verification_key = NULL;
+ int max_iterations = 512;
+
+ if (argc > 1) {
+ /* Don't limit the number of iterations when the verification key
+ * is provided on the command line, we want to do that only in CIs */
+ verification_key = argv[1];
+ max_iterations = -1;
+ }
+
+ assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0);
+ run_test(verification_key, max_iterations);
+
+ assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0);
+ run_test(verification_key, max_iterations);
+
+#if HAVE_GCRYPT
+ /* If we're running without any arguments and we're compiled with gcrypt
+ * check the journal verification stuff with a valid key as well */
+ if (argc <= 1) {
+ verification_key = "c262bd-85187f-0b1b04-877cc5/1c7af8-35a4e900";
+
+ assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0);
+ run_test(verification_key, max_iterations);
+
+ assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0);
+ run_test(verification_key, max_iterations);
+ }
+#endif
+
+ return 0;
+}