Merge pull request #2073 from poettering/dns-label-fixes

Dns label fixes + unrelated selinux clean-up
This commit is contained in:
Lennart Poettering
2015-12-02 20:16:23 +01:00
4 changed files with 227 additions and 113 deletions

View File

@@ -134,52 +134,45 @@ _printf_(2, 3) static int log_callback(int type, const char *fmt, ...) {
#endif
va_start(ap, fmt);
log_internalv(LOG_AUTH | callback_type_to_priority(type),
0, __FILE__, __LINE__, __FUNCTION__, fmt, ap);
log_internalv(LOG_AUTH | callback_type_to_priority(type), 0, __FILE__, __LINE__, __FUNCTION__, fmt, ap);
va_end(ap);
return 0;
}
/*
Function must be called once to initialize the SELinux AVC environment.
Sets up callbacks.
If you want to cleanup memory you should need to call selinux_access_finish.
*/
static int access_init(void) {
int r = 0;
if (avc_open(NULL, 0))
return log_error_errno(errno, "avc_open() failed: %m");
selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback);
selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback);
if (security_getenforce() < 0){
r = -errno;
avc_destroy();
}
return r;
}
static int mac_selinux_access_init(sd_bus_error *error) {
int r;
if (initialized)
return 0;
static int access_init(sd_bus_error *error) {
if (!mac_selinux_use())
return 0;
r = access_init();
if (r < 0)
return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to initialize SELinux.");
if (initialized)
return 1;
if (avc_open(NULL, 0) != 0) {
int enforce, saved_errno = errno;
enforce = security_getenforce();
log_full_errno(enforce != 0 ? LOG_ERR : LOG_WARNING, saved_errno, "Failed to open the SELinux AVC: %m");
/* If enforcement isn't on, then let's suppress this
* error, and just don't do any AVC checks. The
* warning we printed is hence all the admin will
* see. */
if (enforce == 0)
return 0;
/* Return an access denied error, if we couldn't load
* the AVC but enforcing mode was on, or we couldn't
* determine whether it is one. */
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to open the SELinux AVC: %s", strerror(saved_errno));
}
selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback);
selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback);
initialized = true;
return 0;
return 1;
}
#endif
/*
This function communicates with the kernel to check whether or not it should
@@ -193,7 +186,6 @@ int mac_selinux_generic_access_check(
const char *permission,
sd_bus_error *error) {
#ifdef HAVE_SELINUX
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
const char *tclass = NULL, *scon = NULL;
struct audit_info audit_info = {};
@@ -206,11 +198,8 @@ int mac_selinux_generic_access_check(
assert(permission);
assert(error);
if (!mac_selinux_use())
return 0;
r = mac_selinux_access_init(error);
if (r < 0)
r = access_init(error);
if (r <= 0)
return r;
r = sd_bus_query_sender_creds(
@@ -277,7 +266,17 @@ finish:
}
return r;
#else
return 0;
#endif
}
#else
int mac_selinux_generic_access_check(
sd_bus_message *message,
const char *path,
const char *permission,
sd_bus_error *error) {
return 0;
}
#endif

View File

@@ -53,12 +53,12 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
if (*n == 0)
break;
if (sz <= 0)
return -ENOSPC;
if (r >= DNS_LABEL_MAX)
return -EINVAL;
if (sz <= 0)
return -ENOBUFS;
if (*n == '\\') {
/* Escaped character */
@@ -185,10 +185,14 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
char *q;
if (l > DNS_LABEL_MAX)
/* DNS labels must be between 1 and 63 characters long. A
* zero-length label does not exist. See RFC 2182, Section
* 11. */
if (l <= 0 || l > DNS_LABEL_MAX)
return -EINVAL;
if (sz < 1)
return -ENOSPC;
return -ENOBUFS;
assert(p);
assert(dest);
@@ -198,10 +202,11 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
if (*p == '.' || *p == '\\') {
if (sz < 3)
return -ENOSPC;
/* Dot or backslash */
if (sz < 3)
return -ENOBUFS;
*(q++) = '\\';
*(q++) = *p;
@@ -216,7 +221,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
/* Proper character */
if (sz < 2)
return -ENOSPC;
return -ENOBUFS;
*(q++) = *p;
sz -= 1;
@@ -226,7 +231,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
/* Everything else */
if (sz < 5)
return -ENOSPC;
return -ENOBUFS;
*(q++) = '\\';
*(q++) = '0' + (char) ((uint8_t) *p / 100);
@@ -253,7 +258,7 @@ int dns_label_escape_new(const char *p, size_t l, char **ret) {
assert(p);
assert(ret);
if (l > DNS_LABEL_MAX)
if (l <= 0 || l > DNS_LABEL_MAX)
return -EINVAL;
s = new(char, DNS_LABEL_ESCAPED_MAX);
@@ -273,32 +278,52 @@ int dns_label_escape_new(const char *p, size_t l, char **ret) {
int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
#ifdef HAVE_LIBIDN
_cleanup_free_ uint32_t *input = NULL;
size_t input_size;
size_t input_size, l;
const char *p;
bool contains_8bit = false;
char buffer[DNS_LABEL_MAX+1];
assert(encoded);
assert(decoded);
assert(decoded_max >= DNS_LABEL_MAX);
/* Converts an U-label into an A-label */
if (encoded_size <= 0)
return 0;
return -EINVAL;
for (p = encoded; p < encoded + encoded_size; p++)
if ((uint8_t) *p > 127)
contains_8bit = true;
if (!contains_8bit)
if (!contains_8bit) {
if (encoded_size > DNS_LABEL_MAX)
return -EINVAL;
return 0;
}
input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
if (!input)
return -ENOMEM;
if (idna_to_ascii_4i(input, input_size, decoded, 0) != 0)
if (idna_to_ascii_4i(input, input_size, buffer, 0) != 0)
return -EINVAL;
return strlen(decoded);
l = strlen(buffer);
/* Verify that the the result is not longer than one DNS label. */
if (l <= 0 || l > DNS_LABEL_MAX)
return -EINVAL;
if (l > decoded_max)
return -ENOBUFS;
memcpy(decoded, buffer, l);
/* If there's room, append a trailing NUL byte, but only then */
if (decoded_max > l)
decoded[l] = 0;
return (int) l;
#else
return 0;
#endif
@@ -312,11 +337,14 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
uint32_t *output = NULL;
size_t w;
/* To be invoked after unescaping */
/* To be invoked after unescaping. Converts an A-label into an U-label. */
assert(encoded);
assert(decoded);
if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX)
return -EINVAL;
if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1)
return 0;
@@ -336,11 +364,16 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
if (!result)
return -ENOMEM;
if (w <= 0)
return 0;
if (w+1 > decoded_max)
return -EINVAL;
if (w > decoded_max)
return -ENOBUFS;
memcpy(decoded, result, w);
/* Append trailing NUL byte if there's space, but only then. */
if (decoded_max > w)
decoded[w] = 0;
memcpy(decoded, result, w+1);
return w;
#else
return 0;
@@ -409,6 +442,9 @@ int dns_name_concat(const char *a, const char *b, char **_ret) {
n += r;
}
if (n > DNS_HOSTNAME_MAX)
return -EINVAL;
if (_ret) {
if (!GREEDY_REALLOC(ret, allocated, n + 1))
return -ENOMEM;
@@ -511,24 +547,32 @@ int dns_name_equal(const char *x, const char *y) {
r = dns_label_unescape(&x, la, sizeof(la));
if (r < 0)
return r;
k = dns_label_undo_idna(la, r, la, sizeof(la));
if (k < 0)
return k;
if (k > 0)
r = k;
if (r > 0) {
k = dns_label_undo_idna(la, r, la, sizeof(la));
if (k < 0)
return k;
if (k > 0)
r = k;
}
q = dns_label_unescape(&y, lb, sizeof(lb));
if (q < 0)
return q;
w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
if (w < 0)
return w;
if (w > 0)
q = w;
if (q > 0) {
w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
if (w < 0)
return w;
if (w > 0)
q = w;
}
/* If one name had fewer labels than the other, this
* will show up as empty label here, which the
* strcasecmp() below will properly consider different
* from a non-empty label. */
la[r] = lb[q] = 0;
if (strcasecmp(la, lb))
if (strcasecmp(la, lb) != 0)
return false;
}
}
@@ -549,11 +593,13 @@ int dns_name_endswith(const char *name, const char *suffix) {
r = dns_label_unescape(&n, ln, sizeof(ln));
if (r < 0)
return r;
k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
if (k < 0)
return k;
if (k > 0)
r = k;
if (r > 0) {
k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
if (k < 0)
return k;
if (k > 0)
r = k;
}
if (!saved_n)
saved_n = n;
@@ -561,11 +607,13 @@ int dns_name_endswith(const char *name, const char *suffix) {
q = dns_label_unescape(&s, ls, sizeof(ls));
if (q < 0)
return q;
w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
if (w < 0)
return w;
if (w > 0)
q = w;
if (q > 0) {
w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
if (w < 0)
return w;
if (w > 0)
q = w;
}
if (r == 0 && q == 0)
return true;
@@ -605,11 +653,13 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
r = dns_label_unescape(&n, ln, sizeof(ln));
if (r < 0)
return r;
k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
if (k < 0)
return k;
if (k > 0)
r = k;
if (r > 0) {
k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
if (k < 0)
return k;
if (k > 0)
r = k;
}
if (!saved_after)
saved_after = n;
@@ -617,11 +667,13 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
q = dns_label_unescape(&s, ls, sizeof(ls));
if (q < 0)
return q;
w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
if (w < 0)
return w;
if (w > 0)
q = w;
if (q > 0) {
w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
if (w < 0)
return w;
if (w > 0)
q = w;
}
if (r == 0 && q == 0)
break;
@@ -812,37 +864,46 @@ bool dns_name_is_single_label(const char *name) {
return dns_name_is_root(name);
}
/* Encode a domain name according to RFC 1035 Section 3.1 */
/* Encode a domain name according to RFC 1035 Section 3.1, without compression */
int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len) {
uint8_t *label_length;
uint8_t *out;
uint8_t *label_length, *out;
int r;
assert_return(buffer, -EINVAL);
assert_return(domain, -EINVAL);
assert_return(domain[0], -EINVAL);
assert(domain);
assert(buffer);
out = buffer;
do {
/* reserve a byte for label length */
if (len == 0)
/* Reserve a byte for label length */
if (len <= 0)
return -ENOBUFS;
len--;
label_length = out;
out++;
/* convert and copy a single label */
/* Convert and copy a single label. Note that
* dns_label_unescape() returns 0 when it hits the end
* of the domain name, which we rely on here to encode
* the trailing NUL byte. */
r = dns_label_unescape(&domain, (char *) out, len);
if (r < 0)
return r;
/* fill label length, move forward */
/* Fill label length, move forward */
*label_length = r;
out += r;
len -= r;
} while (r != 0);
/* Verify the maximum size of the encoded name. The trailing
* dot + NUL byte account are included this time, hence
* compare against DNS_HOSTNAME_MAX + 2 (which is 255) this
* time. */
if (out - buffer > DNS_HOSTNAME_MAX + 2)
return -EINVAL;
return out - buffer;
}

View File

@@ -25,9 +25,15 @@
#include "hashmap.h"
#include "in-addr-util.h"
/* Length of a single label, with all escaping removed, excluding any trailing dot or NUL byte */
#define DNS_LABEL_MAX 63
/* Worst case length of a single label, with all escaping applied and room for a trailing NUL byte. */
#define DNS_LABEL_ESCAPED_MAX (DNS_LABEL_MAX*4+1)
/* Maximum length of a full hostname, consisting of a series of unescaped labels, and no trailing dot or NUL byte */
#define DNS_HOSTNAME_MAX 253
int dns_label_unescape(const char **name, char *dest, size_t sz);
int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz);
int dns_label_escape(const char *p, size_t l, char *dest, size_t sz);

View File

@@ -39,7 +39,7 @@ static void test_dns_label_unescape_one(const char *what, const char *expect, si
static void test_dns_label_unescape(void) {
test_dns_label_unescape_one("hallo", "hallo", 6, 5);
test_dns_label_unescape_one("hallo", "hallo", 4, -ENOSPC);
test_dns_label_unescape_one("hallo", "hallo", 4, -ENOBUFS);
test_dns_label_unescape_one("", "", 10, 0);
test_dns_label_unescape_one("hallo\\.foobar", "hallo.foobar", 20, 12);
test_dns_label_unescape_one("hallo.foobar", "hallo", 10, 5);
@@ -66,11 +66,38 @@ static void test_dns_name_to_wire_format_one(const char *what, const char *expec
}
static void test_dns_name_to_wire_format(void) {
const char out1[] = { 3, 'f', 'o', 'o', 0 };
const char out2[] = { 5, 'h', 'a', 'l', 'l', 'o', 3, 'f', 'o', 'o', 3, 'b', 'a', 'r', 0 };
const char out3[] = { 4, ' ', 'f', 'o', 'o', 3, 'b', 'a', 'r', 0 };
static const char out0[] = { 0 };
static const char out1[] = { 3, 'f', 'o', 'o', 0 };
static const char out2[] = { 5, 'h', 'a', 'l', 'l', 'o', 3, 'f', 'o', 'o', 3, 'b', 'a', 'r', 0 };
static const char out3[] = { 4, ' ', 'f', 'o', 'o', 3, 'b', 'a', 'r', 0 };
static const char out4[] = { 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
3, 'a', '1', '2', 0 };
test_dns_name_to_wire_format_one("", NULL, 0, -EINVAL);
test_dns_name_to_wire_format_one("", out0, sizeof(out0), sizeof(out0));
test_dns_name_to_wire_format_one("foo", out1, sizeof(out1), sizeof(out1));
test_dns_name_to_wire_format_one("foo", out1, sizeof(out1) + 1, sizeof(out1));
@@ -80,6 +107,9 @@ static void test_dns_name_to_wire_format(void) {
test_dns_name_to_wire_format_one("hallo.foo..bar", NULL, 32, -EINVAL);
test_dns_name_to_wire_format_one("\\032foo.bar", out3, sizeof(out3), sizeof(out3));
test_dns_name_to_wire_format_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a123", NULL, 500, -EINVAL);
test_dns_name_to_wire_format_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12", out4, sizeof(out4), sizeof(out4));
}
static void test_dns_label_unescape_suffix_one(const char *what, const char *expect1, const char *expect2, size_t buffer_sz, int ret1, int ret2) {
@@ -102,7 +132,7 @@ static void test_dns_label_unescape_suffix_one(const char *what, const char *exp
static void test_dns_label_unescape_suffix(void) {
test_dns_label_unescape_suffix_one("hallo", "hallo", "", 6, 5, 0);
test_dns_label_unescape_suffix_one("hallo", "hallo", "", 4, -ENOSPC, -ENOSPC);
test_dns_label_unescape_suffix_one("hallo", "hallo", "", 4, -ENOBUFS, -ENOBUFS);
test_dns_label_unescape_suffix_one("", "", "", 10, 0, 0);
test_dns_label_unescape_suffix_one("hallo\\.foobar", "hallo.foobar", "", 20, 12, 0);
test_dns_label_unescape_suffix_one("hallo.foobar", "foobar", "hallo", 10, 6, 5);
@@ -136,7 +166,7 @@ static void test_dns_label_escape_one(const char *what, size_t l, const char *ex
}
static void test_dns_label_escape(void) {
test_dns_label_escape_one("", 0, "", 0);
test_dns_label_escape_one("", 0, NULL, -EINVAL);
test_dns_label_escape_one("hallo", 5, "hallo", 5);
test_dns_label_escape_one("hallo", 6, NULL, -EINVAL);
test_dns_label_escape_one("hallo hallo.foobar,waldi", 24, "hallo\\032hallo\\.foobar\\044waldi", 31);
@@ -314,6 +344,24 @@ static void test_dns_name_is_valid(void) {
test_dns_name_is_valid_one("\\zbar", 0);
test_dns_name_is_valid_one("ä", 1);
test_dns_name_is_valid_one("\n", 0);
/* 256 characters*/
test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345", 0);
/* 255 characters*/
test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a1234", 0);
/* 254 characters*/
test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a123", 0);
/* 253 characters*/
test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12", 1);
/* label of 64 chars length */
test_dns_name_is_valid_one("a123456789a123456789a123456789a123456789a123456789a123456789a123", 0);
/* label of 63 chars length */
test_dns_name_is_valid_one("a123456789a123456789a123456789a123456789a123456789a123456789a12", 1);
}
static void test_dns_service_name_is_valid(void) {