| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Generates files or directories with hash collisions on a XFS filesystem |
| * Copyright (C) 2014 Hannes Frederic Sowa |
| */ |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <string.h> |
| |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| |
| static enum { |
| ILLEGAL, |
| DIRECTORY, |
| FILENAME, |
| } touch_mode = ILLEGAL; |
| |
| static bool one_hash = false; |
| |
| static uint32_t rol32(uint32_t word, unsigned int shift) |
| { |
| return (word << shift) | (word >> (32 - shift)); |
| } |
| |
| static uint32_t xfs_da_hashname(const uint8_t *name, int namelen) |
| { |
| uint32_t hash; |
| |
| for (hash = 0; namelen >= 4; namelen -= 4, name += 4) |
| hash = (name[0] << 21) ^ (name[1] << 14) ^ (name[2] << 7) ^ |
| (name[3] << 0) ^ rol32(hash, 7 * 4); |
| |
| if (namelen) { |
| fprintf(stderr, |
| "internal error: " |
| "misbalanced input buffer to xfs_da_hashname - " |
| "overlapping %d bytes\n", namelen); |
| exit(1); |
| } |
| |
| return hash; |
| } |
| |
| static uint8_t gen_rand(void) |
| { |
| uint8_t r; |
| while (!(r = rand())); |
| return r; |
| } |
| |
| static uint8_t buffer[252+1] = {0}; |
| |
| static void gen_name(void) |
| { |
| int idx; |
| uint32_t hash, last; |
| |
| again: |
| for (idx = 0; idx < 252-4; idx+=4) { |
| buffer[idx + 0] = gen_rand(); |
| buffer[idx + 1] = gen_rand(); |
| buffer[idx + 2] = gen_rand(); |
| buffer[idx + 3] = gen_rand(); |
| } |
| |
| hash = rol32(xfs_da_hashname(buffer, 248), 7 * 4); |
| last = hash ^ ~0U; |
| |
| if (last == 0) |
| goto again; |
| |
| buffer[idx + 3] = last & 0x7fU; |
| buffer[idx + 2] = (last >> 7) & 0x7fU; |
| buffer[idx + 1] = (last >> 14) & 0x7fU; |
| buffer[idx + 0] = ((last >> 21) & 0xffU); |
| |
| if (memchr(buffer, '.', sizeof(buffer)) || |
| memchr(buffer, '/', sizeof(buffer))) |
| goto again; |
| |
| if (one_hash) { |
| /* very poor - can be improved later */ |
| static bool done = false; |
| static uint32_t filter; |
| |
| if (!done) { |
| filter = xfs_da_hashname(buffer, 252); |
| done = true; |
| return; |
| } |
| |
| if (filter != xfs_da_hashname(buffer, 252)) |
| goto again; |
| } |
| } |
| |
| static int touch(const char *buffer) |
| { |
| if (touch_mode == DIRECTORY) { |
| if (mkdir(buffer, S_IRWXU)) { |
| /* ignore if directory is already present */ |
| if (errno == EEXIST) |
| return 0; |
| perror("mkdir with random directory name"); |
| return 1; |
| } |
| } else if (touch_mode == FILENAME) { |
| int fd = creat(buffer, S_IRWXU); |
| if (fd == -1) { |
| /* ignore duplicate files */ |
| if (errno == EEXIST) |
| return 0; |
| perror("creat with random directory name"); |
| return 1; |
| } |
| if (close(fd)) { |
| perror("close is leaking a file descriptor"); |
| return 1; |
| } |
| return 0; |
| } |
| return 0; |
| } |
| |
| static void do_seed(void) |
| { |
| struct timeval tv; |
| if (gettimeofday(&tv, NULL)) { |
| perror("gettimeofday"); |
| exit(1); |
| } |
| srand(tv.tv_sec ^ tv.tv_usec ^ getpid()); |
| } |
| |
| static void usage_and_exit(const char *pname) |
| { |
| fprintf(stderr, "usage: %s [-d] [-f] [-n num] [-s] directory\n" |
| "\t-f\tcreate files (the default)\n" |
| "\t-d\tcreate directories\n" |
| "\t-n num\tcreate num directories or files (default 200000)\n" |
| "\t-s\tonly generate one hash\n" |
| "\tdirectory\tthe directory to chdir() to\n", |
| pname); |
| exit(1); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| const char allopts[] = "hsdfn:"; |
| int c, orig_cycles, errors = 0, cycles = 200000; |
| |
| while ((c = getopt(argc, argv, allopts)) != -1) { |
| switch (c) { |
| case 'd': |
| if (touch_mode != ILLEGAL) |
| usage_and_exit(argv[0]); |
| touch_mode = DIRECTORY; |
| break; |
| case 'f': |
| if (touch_mode != ILLEGAL) |
| usage_and_exit(argv[0]); |
| touch_mode = FILENAME; |
| break; |
| case 'n': |
| errno = 0; |
| if (sscanf(optarg, "%d", &cycles) != 1 || |
| errno == ERANGE) { |
| fputs("could not parse number of iterations", stderr); |
| exit(1); |
| } |
| break; |
| case 's': |
| one_hash = true; |
| break; |
| default: |
| usage_and_exit(argv[0]); |
| break; |
| } |
| } |
| |
| if (argc <= optind || touch_mode == ILLEGAL) |
| usage_and_exit(argv[0]); |
| |
| if (chdir(argv[optind])) { |
| perror("chdir"); |
| exit(1); |
| } |
| |
| orig_cycles = cycles; |
| |
| do_seed(); |
| |
| while (cycles--) { |
| gen_name(); |
| errors += touch((char *)buffer); |
| } |
| |
| if (errors) |
| fprintf(stderr, "creating %d %s caused %d errors\n", |
| orig_cycles, touch_mode == FILENAME ? "files" : "directories", |
| errors); |
| |
| return 0; |
| } |