dhcpcd/auth.c
Roy Marples a67f0194df DHCP messages are really BOOTP so lets name the structure accordingly.
While here, stop making assumptions about the size of a DHCP packet based
on a fixed structure from a default MTU of 1500.
Instead, make it flexable and set the DHCP packet size to the interface MTU
less UDP and IP headers.
As a result, we need to know the size of the DHCP packet received
rather than just walking the old fixed DHCP message structure.

This makes Coverity happy about tainted scalar values when parsing DHCP
messages.
2016-05-06 13:26:41 +00:00

672 lines
15 KiB
C

/*
* dhcpcd - DHCP client daemon
* Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
* All rights reserved
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/file.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "config.h"
#include "auth.h"
#include "crypt/crypt.h"
#include "dhcp.h"
#include "dhcp6.h"
#include "dhcpcd.h"
#ifdef __sun
#define htonll
#define ntohll
#endif
#ifndef htonll
#if (BYTE_ORDER == LITTLE_ENDIAN)
static inline uint64_t
htonll(uint64_t x)
{
return (uint64_t)htonl((uint32_t)(x >> 32)) |
(uint64_t)htonl((uint32_t)(x & 0xffffffff)) << 32;
}
#else /* (BYTE_ORDER == LITTLE_ENDIAN) */
#define htonll(x) (x)
#endif
#endif /* htonll */
#ifndef ntohll
#if (BYTE_ORDER == LITTLE_ENDIAN)
static inline uint64_t
ntohll(uint64_t x)
{
return (uint64_t)ntohl((uint32_t)(x >> 32)) |
(uint64_t)ntohl((uint32_t)(x & 0xffffffff)) << 32;
}
#else /* (BYTE_ORDER == LITTLE_ENDIAN) */
#define ntohll(x) (x)
#endif
#endif /* ntohll */
#define HMAC_LENGTH 16
void
dhcp_auth_reset(struct authstate *state)
{
state->replay = 0;
if (state->token) {
free(state->token->key);
free(state->token->realm);
free(state->token);
state->token = NULL;
}
if (state->reconf) {
free(state->reconf->key);
free(state->reconf->realm);
free(state->reconf);
state->reconf = NULL;
}
}
/*
* Authenticate a DHCP message.
* m and mlen refer to the whole message.
* t is the DHCP type, pass it 4 or 6.
* data and dlen refer to the authentication option within the message.
*/
const struct token *
dhcp_auth_validate(struct authstate *state, const struct auth *auth,
const uint8_t *m, size_t mlen, int mp, int mt,
const uint8_t *data, size_t dlen)
{
uint8_t protocol, algorithm, rdm, *mm, type;
uint64_t replay;
uint32_t secretid;
const uint8_t *d, *realm;
size_t realm_len;
const struct token *t;
time_t now;
uint8_t hmac[HMAC_LENGTH];
if (dlen < 3 + sizeof(replay)) {
errno = EINVAL;
return NULL;
}
/* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
if (data < m || data > m + mlen || data + dlen > m + mlen) {
errno = ERANGE;
return NULL;
}
d = data;
protocol = *d++;
algorithm = *d++;
rdm = *d++;
if (!(auth->options & DHCPCD_AUTH_SEND)) {
/* If we didn't send any authorisation, it can only be a
* reconfigure key */
if (protocol != AUTH_PROTO_RECONFKEY) {
errno = EINVAL;
return NULL;
}
} else if (protocol != auth->protocol ||
algorithm != auth->algorithm ||
rdm != auth->rdm)
{
/* As we don't require authentication, we should still
* accept a reconfigure key */
if (protocol != AUTH_PROTO_RECONFKEY ||
auth->options & DHCPCD_AUTH_REQUIRE)
{
errno = EPERM;
return NULL;
}
}
dlen -= 3;
memcpy(&replay, d, sizeof(replay));
replay = ntohll(replay);
if (state->token) {
if (state->replay == (replay ^ 0x8000000000000000ULL)) {
/* We don't know if the singular point is increasing
* or decreasing. */
errno = EPERM;
return NULL;
}
if ((uint64_t)(replay - state->replay) <= 0) {
/* Replay attack detected */
errno = EPERM;
return NULL;
}
}
d+= sizeof(replay);
dlen -= sizeof(replay);
realm = NULL;
realm_len = 0;
/* Extract realm and secret.
* Rest of data is MAC. */
switch (protocol) {
case AUTH_PROTO_TOKEN:
secretid = 0;
break;
case AUTH_PROTO_DELAYED:
if (dlen < sizeof(secretid) + sizeof(hmac)) {
errno = EINVAL;
return NULL;
}
memcpy(&secretid, d, sizeof(secretid));
d += sizeof(secretid);
dlen -= sizeof(secretid);
break;
case AUTH_PROTO_DELAYEDREALM:
if (dlen < sizeof(secretid) + sizeof(hmac)) {
errno = EINVAL;
return NULL;
}
realm_len = dlen - (sizeof(secretid) + sizeof(hmac));
if (realm_len) {
realm = d;
d += realm_len;
dlen -= realm_len;
}
memcpy(&secretid, d, sizeof(secretid));
d += sizeof(secretid);
dlen -= sizeof(secretid);
break;
case AUTH_PROTO_RECONFKEY:
if (dlen != 1 + 16) {
errno = EINVAL;
return NULL;
}
type = *d++;
dlen--;
switch (type) {
case 1:
if ((mp == 4 && mt == DHCP_ACK) ||
(mp == 6 && mt == DHCP6_REPLY))
{
if (state->reconf == NULL) {
state->reconf =
malloc(sizeof(*state->reconf));
if (state->reconf == NULL)
return NULL;
state->reconf->key = malloc(16);
if (state->reconf->key == NULL) {
free(state->reconf);
state->reconf = NULL;
return NULL;
}
state->reconf->secretid = 0;
state->reconf->expire = 0;
state->reconf->realm = NULL;
state->reconf->realm_len = 0;
state->reconf->key_len = 16;
}
memcpy(state->reconf->key, d, 16);
} else {
errno = EINVAL;
return NULL;
}
if (state->reconf == NULL)
errno = ENOENT;
/* Free the old token so we log acceptance */
if (state->token) {
free(state->token);
state->token = NULL;
}
/* Nothing to validate, just accepting the key */
return state->reconf;
case 2:
if (!((mp == 4 && mt == DHCP_FORCERENEW) ||
(mp == 6 && mt == DHCP6_RECONFIGURE)))
{
errno = EINVAL;
return NULL;
}
if (state->reconf == NULL) {
errno = ENOENT;
return NULL;
}
t = state->reconf;
goto gottoken;
default:
errno = EINVAL;
return NULL;
}
default:
errno = ENOTSUP;
return NULL;
}
/* Find a token for the realm and secret */
secretid = ntohl(secretid);
TAILQ_FOREACH(t, &auth->tokens, next) {
if (t->secretid == secretid &&
t->realm_len == realm_len &&
(t->realm_len == 0 ||
memcmp(t->realm, realm, t->realm_len) == 0))
break;
}
if (t == NULL) {
errno = ESRCH;
return NULL;
}
if (t->expire) {
if (time(&now) == -1)
return NULL;
if (t->expire < now) {
errno = EFAULT;
return NULL;
}
}
gottoken:
/* First message from the server */
if (state->token &&
(state->token->secretid != t->secretid ||
state->token->realm_len != t->realm_len ||
memcmp(state->token->realm, t->realm, t->realm_len)))
{
errno = EPERM;
return NULL;
}
/* Special case as no hashing needs to be done. */
if (protocol == AUTH_PROTO_TOKEN) {
if (dlen != t->key_len || memcmp(d, t->key, dlen)) {
errno = EPERM;
return NULL;
}
goto finish;
}
/* Make a duplicate of the message, but zero out the MAC part */
mm = malloc(mlen);
if (mm == NULL)
return NULL;
memcpy(mm, m, mlen);
memset(mm + (d - m), 0, dlen);
/* RFC3318, section 5.2 - zero giaddr and hops */
if (mp == 4) {
*(mm + offsetof(struct bootp, hops)) = '\0';
memset(mm + offsetof(struct bootp, giaddr), 0, 4);
}
memset(hmac, 0, sizeof(hmac));
switch (algorithm) {
case AUTH_ALG_HMAC_MD5:
hmac_md5(mm, mlen, t->key, t->key_len, hmac);
break;
default:
errno = ENOSYS;
free(mm);
return NULL;
}
free(mm);
if (memcmp(d, &hmac, dlen)) {
errno = EPERM;
return NULL;
}
finish:
/* If we got here then authentication passed */
state->replay = replay;
if (state->token == NULL) {
/* We cannot just save a pointer because a reconfigure will
* recreate the token list. So we duplicate it. */
state->token = malloc(sizeof(*state->token));
if (state->token) {
state->token->secretid = t->secretid;
state->token->key = malloc(t->key_len);
if (state->token->key) {
state->token->key_len = t->key_len;
memcpy(state->token->key, t->key, t->key_len);
} else {
free(state->token);
state->token = NULL;
return NULL;
}
if (t->realm_len) {
state->token->realm = malloc(t->realm_len);
if (state->token->realm) {
state->token->realm_len = t->realm_len;
memcpy(state->token->realm, t->realm,
t->realm_len);
} else {
free(state->token->key);
free(state->token);
state->token = NULL;
return NULL;
}
} else {
state->token->realm = NULL;
state->token->realm_len = 0;
}
}
/* If we cannot save the token, we must invalidate */
if (state->token == NULL)
return NULL;
}
return t;
}
static uint64_t
get_next_rdm_monotonic_counter(struct auth *auth)
{
FILE *fp;
uint64_t rdm;
#ifdef LOCK_EX
int flocked;
#endif
fp = fopen(RDM_MONOFILE, "r+");
if (fp == NULL) {
if (errno != ENOENT)
return ++auth->last_replay; /* report error? */
fp = fopen(RDM_MONOFILE, "w");
if (fp == NULL)
return ++auth->last_replay; /* report error? */
#ifdef LOCK_EX
flocked = flock(fileno(fp), LOCK_EX);
#endif
rdm = 0;
} else {
#ifdef LOCK_EX
flocked = flock(fileno(fp), LOCK_EX);
#endif
if (fscanf(fp, "0x%016" PRIu64, &rdm) != 1)
rdm = 0; /* truncated? report error? */
}
rdm++;
if (fseek(fp, 0, SEEK_SET) == -1 ||
ftruncate(fileno(fp), 0) == -1 ||
fprintf(fp, "0x%016" PRIu64 "\n", rdm) != 19 ||
fflush(fp) == EOF)
{
if (!auth->last_replay_set) {
auth->last_replay = rdm;
auth->last_replay_set = 1;
} else
rdm = ++auth->last_replay;
/* report error? */
}
#ifdef LOCK_EX
if (flocked == 0)
flock(fileno(fp), LOCK_UN);
#endif
fclose(fp);
return rdm;
}
#define JAN_1970 2208988800U /* 1970 - 1900 in seconds */
static uint64_t
get_next_rdm_monotonic_clock(struct auth *auth)
{
struct timespec ts;
uint32_t pack[2];
double frac;
uint64_t rdm;
if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
return ++auth->last_replay; /* report error? */
pack[0] = htonl((uint32_t)ts.tv_sec + JAN_1970);
frac = ((double)ts.tv_nsec / 1e9 * 0x100000000ULL);
pack[1] = htonl((uint32_t)frac);
memcpy(&rdm, &pack, sizeof(rdm));
return rdm;
}
static uint64_t
get_next_rdm_monotonic(struct auth *auth)
{
if (auth->options & DHCPCD_AUTH_RDM_COUNTER)
return get_next_rdm_monotonic_counter(auth);
return get_next_rdm_monotonic_clock(auth);
}
/*
* Encode a DHCP message.
* Either we know which token to use from the server response
* or we are using a basic configuration token.
* token is the token to encrypt with.
* m and mlen refer to the whole message.
* mp is the DHCP type, pass it 4 or 6.
* mt is the DHCP message type.
* data and dlen refer to the authentication option within the message.
*/
ssize_t
dhcp_auth_encode(struct auth *auth, const struct token *t,
uint8_t *m, size_t mlen, int mp, int mt,
uint8_t *data, size_t dlen)
{
uint64_t rdm;
uint8_t hmac[HMAC_LENGTH];
time_t now;
uint8_t hops, *p, info;
uint32_t giaddr, secretid;
if (auth->protocol == 0 && t == NULL) {
TAILQ_FOREACH(t, &auth->tokens, next) {
if (t->secretid == 0 &&
t->realm_len == 0)
break;
}
if (t == NULL) {
errno = EINVAL;
return -1;
}
if (t->expire) {
if (time(&now) == -1)
return -1;
if (t->expire < now) {
errno = EPERM;
return -1;
}
}
}
switch(auth->protocol) {
case AUTH_PROTO_TOKEN:
case AUTH_PROTO_DELAYED:
case AUTH_PROTO_DELAYEDREALM:
/* We don't ever send a reconf key */
break;
default:
errno = ENOTSUP;
return -1;
}
switch(auth->algorithm) {
case AUTH_ALG_HMAC_MD5:
break;
default:
errno = ENOTSUP;
return -1;
}
switch(auth->rdm) {
case AUTH_RDM_MONOTONIC:
break;
default:
errno = ENOTSUP;
return -1;
}
/* DISCOVER or INFORM messages don't write auth info */
if ((mp == 4 && (mt == DHCP_DISCOVER || mt == DHCP_INFORM)) ||
(mp == 6 && (mt == DHCP6_SOLICIT || mt == DHCP6_INFORMATION_REQ)))
info = 0;
else
info = 1;
/* Work out the auth area size.
* We only need to do this for DISCOVER messages */
if (data == NULL) {
dlen = 1 + 1 + 1 + 8;
switch(auth->protocol) {
case AUTH_PROTO_TOKEN:
dlen += t->key_len;
break;
case AUTH_PROTO_DELAYEDREALM:
if (info && t)
dlen += t->realm_len;
/* FALLTHROUGH */
case AUTH_PROTO_DELAYED:
if (info && t)
dlen += sizeof(t->secretid) + sizeof(hmac);
break;
}
return (ssize_t)dlen;
}
if (dlen < 1 + 1 + 1 + 8) {
errno = ENOBUFS;
return -1;
}
/* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
if (data < m || data > m + mlen || data + dlen > m + mlen) {
errno = ERANGE;
return -1;
}
/* Write out our option */
*data++ = auth->protocol;
*data++ = auth->algorithm;
*data++ = auth->rdm;
switch (auth->rdm) {
case AUTH_RDM_MONOTONIC:
rdm = get_next_rdm_monotonic(auth);
break;
default:
/* This block appeases gcc, clang doesn't need it */
rdm = get_next_rdm_monotonic(auth);
break;
}
rdm = htonll(rdm);
memcpy(data, &rdm, 8);
data += 8;
dlen -= 1 + 1 + 1 + 8;
/* Special case as no hashing needs to be done. */
if (auth->protocol == AUTH_PROTO_TOKEN) {
/* Should be impossible, but still */
if (t == NULL) {
errno = EINVAL;
return -1;
}
if (dlen < t->key_len) {
errno = ENOBUFS;
return -1;
}
memcpy(data, t->key, t->key_len);
return (ssize_t)(dlen - t->key_len);
}
/* DISCOVER or INFORM messages don't write auth info */
if (!info)
return (ssize_t)dlen;
/* Loading a saved lease without an authentication option */
if (t == NULL)
return 0;
/* Write out the Realm */
if (auth->protocol == AUTH_PROTO_DELAYEDREALM) {
if (dlen < t->realm_len) {
errno = ENOBUFS;
return -1;
}
memcpy(data, t->realm, t->realm_len);
data += t->realm_len;
dlen -= t->realm_len;
}
/* Write out the SecretID */
if (auth->protocol == AUTH_PROTO_DELAYED ||
auth->protocol == AUTH_PROTO_DELAYEDREALM)
{
if (dlen < sizeof(t->secretid)) {
errno = ENOBUFS;
return -1;
}
secretid = htonl(t->secretid);
memcpy(data, &secretid, sizeof(secretid));
data += sizeof(secretid);
dlen -= sizeof(secretid);
}
/* Zero what's left, the MAC */
memset(data, 0, dlen);
/* RFC3318, section 5.2 - zero giaddr and hops */
if (mp == 4) {
p = m + offsetof(struct bootp, hops);
hops = *p;
*p = '\0';
p = m + offsetof(struct bootp, giaddr);
memcpy(&giaddr, p, sizeof(giaddr));
memset(p, 0, sizeof(giaddr));
} else {
/* appease GCC again */
hops = 0;
giaddr = 0;
}
/* Create our hash and write it out */
switch(auth->algorithm) {
case AUTH_ALG_HMAC_MD5:
hmac_md5(m, mlen, t->key, t->key_len, hmac);
memcpy(data, hmac, sizeof(hmac));
break;
}
/* RFC3318, section 5.2 - restore giaddr and hops */
if (mp == 4) {
p = m + offsetof(struct bootp, hops);
*p = hops;
p = m + offsetof(struct bootp, giaddr);
memcpy(p, &giaddr, sizeof(giaddr));
}
/* Done! */
return (int)(dlen - sizeof(hmac)); /* should be zero */
}