Tweak the hostname-worldlist code (#43599)

This commit is contained in:
Yu Watanabe
2026-09-03 06:29:25 +09:00
committed by GitHub
6 changed files with 145 additions and 71 deletions
+7 -5
View File
@@ -790,7 +790,9 @@ int read_full_stream_full(
if (feof(f))
break;
if (size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER)) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */
if (size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER)) {
/* If we got asked to read some specific size, we already sized the buffer right,
* hence leave. */
assert(l == size);
break;
}
@@ -834,9 +836,9 @@ int read_full_stream_full(
}
if (!ret_size) {
/* Safety check: if the caller doesn't want to know the size of what we just read it will rely on the
* trailing NUL byte. But if there's an embedded NUL byte, then we should refuse operation as otherwise
* there'd be ambiguity about what we just read. */
/* Safety check: if the caller doesn't want to know the size of what we just read it will
* rely on the trailing NUL byte. But if there's an embedded NUL byte, then we should refuse
* operation as otherwise there'd be ambiguity about what we just read. */
if (memchr(buf, 0, l)) {
r = -EBADMSG;
@@ -1295,7 +1297,7 @@ static int search_and_fopen_internal(
assert(path);
assert(mode || !ret_file);
r = search_and_open(
r = search_and_open_internal(
path,
mode ? fopen_mode_to_flags(mode) : 0,
root,
+8 -8
View File
@@ -264,20 +264,20 @@ char** path_strv_resolve(char **l, const char *root) {
if (strv_isempty(l))
return l;
/* Goes through every item in the string list and canonicalize
* the path. This works in place and won't rollback any
* changes on failure. */
/* Go through every item in the string list and canonicalize the path.
* This works in place and doesn't roll back any changes on failure. */
STRV_FOREACH(s, l) {
_cleanup_free_ char *orig = NULL;
char *t, *u;
if (!path_is_absolute(*s)) {
free(*s);
continue;
}
if (root) {
if (!path_is_absolute(*s)) {
log_debug("Ignoring non-absolute search path '%s'.", *s);
free(*s);
continue;
}
orig = *s;
t = path_join(root, orig);
if (!t) {
+78 -58
View File
@@ -331,45 +331,72 @@ static bool normalize_and_validate_word(char *word) {
return hostname_is_valid(word, /* flags= */ 0);
}
static int pick_word_linear_scan(FILE *f, off_t offset, char **ret) {
int r;
assert(f);
static int pick_word_at(const char *words, size_t size, size_t offset, char **ret) {
assert(words);
assert(offset <= size);
assert(ret);
if (fseeko(f, offset, SEEK_SET) < 0)
return -errno;
/* Extracts the line starting at 'offset', strips and validates it. Returns 1 and the word on
* success, 0 if the line is a comment, empty, or not a valid hostname word. */
bool wrapped = false;
r = read_line(f, LONG_LINE_MAX, NULL); /* discard the partial line we landed in */
if (r < 0)
return r;
if (r == 0) {
wrapped = true;
rewind(f);
const char *p = words + offset;
size_t len = MIN(size - offset, LONG_LINE_MAX + 1);
const char *e = memchr(p, '\n', len);
if (e)
len = e - p;
if (len > LONG_LINE_MAX)
return -EUCLEAN;
/* Strip leading and trailing whitespace */
while (len > 0 && strchr(WHITESPACE, p[0])) {
p++;
len--;
}
while (len > 0 && strchr(WHITESPACE, p[len - 1]))
len--;
_cleanup_free_ char *word = strndup(p, len);
if (!word)
return -ENOMEM;
bool good = normalize_and_validate_word(word);
*ret = good ? TAKE_PTR(word) : NULL;
return good;
}
static int pick_word_linear_scan(const char *words, size_t size, size_t offset, char **ret) {
int r;
assert(words);
assert(size > 0);
assert(offset <= size);
assert(ret);
/* Skip the partial line we landed in */
bool wrapped = false;
const char *e = memchr(words + offset, '\n', size - offset);
offset = e ? (size_t) (e - words) + 1 : size;
for (;;) {
_cleanup_free_ char *line = NULL;
r = read_stripped_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0) { /* hit EOF: we started at a random offset, wrap around to the beginning */
if (offset >= size) { /* hit EOF: we started at a random offset, wrap around to the beginning */
if (wrapped) /* already wrapped once, the file contains no usable word at all */
return -ENOENT;
wrapped = true;
rewind(f);
continue;
}
if (normalize_and_validate_word(line)) {
*ret = TAKE_PTR(line);
return 0;
offset = 0;
}
r = pick_word_at(words, size, offset, ret);
if (r != 0)
return r < 0 ? r : 0;
/* Advance to the next line */
e = memchr(words + offset, '\n', size - offset);
offset = e ? (size_t) (e - words) + 1 : size;
}
}
static int hostname_pick_word(sd_id128_t mid, size_t pos, char **ret) {
int hostname_pick_word(sd_id128_t mid, size_t pos, char **ret) {
static const sd_id128_t word_key = SD_ID128_MAKE(2d,9f,1c,7a,4b,8e,43,11,9a,6d,5f,02,c8,77,e3,14);
_cleanup_fclose_ FILE *f = NULL;
struct stat st;
@@ -391,55 +418,48 @@ static int hostname_pick_word(sd_id128_t mid, size_t pos, char **ret) {
r = stat_verify_regular(&st);
if (r < 0)
return r;
if (st.st_size == 0)
_cleanup_free_ char *words = NULL;
size_t size;
r = read_full_stream(f, &words, &size);
if (r < 0)
return r;
if (size == 0)
return -ENOENT;
/* Pick a word without reading the whole list into memory:
* 1. pick a random offset in the file [0 … st.st_size-1]
* 2. if offset is zero, read a full line from the beginning of the file, use that.
* 3. otherwise, seek to offset minus 1 and read one character.
* 4. if that character is newline, then read a full line after it, and use that as result
* 5. otherwise, goto 1
/* Pick a word:
* 1. pick a random offset in the buffer [0 … size-1]
* 2. if the offset is zero or the preceding byte is a newline, it starts a line: use that line
* 3. otherwise, goto 1
*
* As a safety net terminate after a fixed number iterations (for pathological wordlists)
* This stream is independent of the '?' nibble stream so pure-'?' * templates keep producing
* This stream is independent of the '?' nibble stream so pure-'?' templates keep producing
* byte-identical output. Stable as long as the wordlist is stable. */
off_t offset = 0;
size_t offset = 0;
const unsigned int MAX_ITERATIONS = 64;
for (unsigned i = 0; i < MAX_ITERATIONS; i++) {
_cleanup_free_ char *line = NULL;
struct siphash state;
siphash24_init(&state, word_key.bytes);
siphash24_compress_typesafe(mid, &state);
siphash24_compress_typesafe(pos, &state);
siphash24_compress_typesafe(i, &state); /* counter mode */
offset = (off_t) (siphash24_finalize(&state) % (uint64_t) st.st_size);
offset = (size_t) (siphash24_finalize(&state) % (uint64_t) size);
if (offset > 0) {
if (fseeko(f, offset - 1, SEEK_SET) < 0)
return -errno;
if (fgetc(f) != '\n')
continue; /* not a line start */
} else if (fseeko(f, 0, SEEK_SET) < 0) /* offset 0 always begins the first line */
return -errno;
if (offset > 0 && words[offset - 1] != '\n')
continue; /* not a line start */
r = pick_word_at(words, size, offset, ret);
if (r != 0)
return r < 0 ? r : 0;
r = read_stripped_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return r;
if (r == 0) /* raced with truncation */
continue;
if (normalize_and_validate_word(line)) {
*ret = TAKE_PTR(line);
return 0;
}
/* Comment/empty/invalid line: resample rather than advancing, to keep the pick uniform. */
}
/* We exhausted the uniform attempts, this should never happen but if it does fallback to picking the
* next word after our last attempt. */
log_warning("hostname_pick_word did not find a usable word after %u in wordlist %zu", MAX_ITERATIONS, pos);
return pick_word_linear_scan(f, offset, ret);
/* We exhausted the uniform attempts, this occasionally happens, so fall back to picking the next
* word after our last offset. */
log_debug("Falling back to linear scan for a word in wordlist %zu after trying %u random offsets.",
pos, MAX_ITERATIONS);
return pick_word_linear_scan(words, size, offset, ret);
}
int hostname_substitute_wildcards(const char *name, char **ret) {
+1
View File
@@ -22,6 +22,7 @@ int read_etc_hostname(const char *path, bool substitute_wildcards, char **ret);
void hostname_update_source_hint(const char *hostname, HostnameSource source);
int hostname_setup(bool really);
int hostname_pick_word(sd_id128_t mid, size_t pos, char **ret);
int hostname_substitute_wildcards(const char *name, char **ret);
char* get_default_hostname(void);
+4
View File
@@ -399,6 +399,10 @@ executables += [
],
'timeout' : 180,
},
test_template + {
'sources': files('test-hostname-pick-word.c'),
'type' : 'manual',
},
test_template + {
'sources' : files('test-ipcrm.c'),
'type' : 'unsafe',
+47
View File
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-id128.h"
#include "alloc-util.h"
#include "hostname-setup.h"
#include "log.h"
#include "main-func.h"
#include "parse-util.h"
#include "tests.h"
static int run(int argc, char **argv) {
int r;
log_parse_environment();
unsigned c = 100, pos = 1;
if (argc > 1) {
r = safe_atou(argv[1], &pos);
if (r < 0)
return log_error_errno(r, "Cannot parse uint %s: %m", argv[1]);
}
if (argc > 2) {
r = safe_atou(argv[2], &c);
if (r < 0)
return log_error_errno(r, "Cannot parse uint %s: %m", argv[2]);
}
sd_id128_t mid = SD_ID128_ARRAY(aa,bb,cc,ee,33,21,42,31,89,ba,a4,91,33,22,11,00);
while (c-- > 0) {
_cleanup_free_ char *w = NULL;
ASSERT_OK(hostname_pick_word(mid, pos, &w));
ASSERT_PTR(w);
printf("%s\n", w);
mid.qwords[0] += 3;
}
return 0;
}
DEFINE_MAIN_FUNCTION(run);