Tweak the hostname-worldlist code (#43599)
This commit is contained in:
+7
-5
@@ -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,
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user