diff options
Diffstat (limited to 'src/lib')
321 files changed, 52176 insertions, 0 deletions
diff --git a/src/lib/arch/.may_include b/src/lib/arch/.may_include new file mode 100644 index 0000000000..4285c3dcb8 --- /dev/null +++ b/src/lib/arch/.may_include @@ -0,0 +1,2 @@ +orconfig.h +lib/cc/*.h diff --git a/src/lib/arch/bytes.h b/src/lib/arch/bytes.h new file mode 100644 index 0000000000..fa82241b28 --- /dev/null +++ b/src/lib/arch/bytes.h @@ -0,0 +1,182 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_BYTES_H +#define TOR_BYTES_H + +/** + * \file bytes.h + * + * \brief Inline functions for reading and writing multibyte values from + * the middle of strings, and for manipulating byte order. + **/ + +#include <string.h> +#include "lib/cc/torint.h" + +/* The uint8 variants are defined to make the code more uniform. */ +static inline uint8_t +get_uint8(const void *cp) +{ + return *(const uint8_t*)(cp); +} +static inline void +set_uint8(void *cp, uint8_t v) +{ + *(uint8_t*)cp = v; +} + +/** + * Read a 16-bit value beginning at <b>cp</b>. Equivalent to + * *(uint16_t*)(cp), but will not cause segfaults on platforms that forbid + * unaligned memory access. + */ +static inline uint16_t +get_uint16(const void *cp) +{ + uint16_t v; + memcpy(&v,cp,2); + return v; +} +/** + * Read a 32-bit value beginning at <b>cp</b>. Equivalent to + * *(uint32_t*)(cp), but will not cause segfaults on platforms that forbid + * unaligned memory access. + */ +static inline uint32_t +get_uint32(const void *cp) +{ + uint32_t v; + memcpy(&v,cp,4); + return v; +} +/** + * Read a 64-bit value beginning at <b>cp</b>. Equivalent to + * *(uint64_t*)(cp), but will not cause segfaults on platforms that forbid + * unaligned memory access. + */ +static inline uint64_t +get_uint64(const void *cp) +{ + uint64_t v; + memcpy(&v,cp,8); + return v; +} + +/** + * Set a 16-bit value beginning at <b>cp</b> to <b>v</b>. Equivalent to + * *(uint16_t*)(cp) = v, but will not cause segfaults on platforms that forbid + * unaligned memory access. */ +static inline void +set_uint16(void *cp, uint16_t v) +{ + memcpy(cp,&v,2); +} +/** + * Set a 32-bit value beginning at <b>cp</b> to <b>v</b>. Equivalent to + * *(uint32_t*)(cp) = v, but will not cause segfaults on platforms that forbid + * unaligned memory access. */ +static inline void +set_uint32(void *cp, uint32_t v) +{ + memcpy(cp,&v,4); +} +/** + * Set a 64-bit value beginning at <b>cp</b> to <b>v</b>. Equivalent to + * *(uint64_t*)(cp) = v, but will not cause segfaults on platforms that forbid + * unaligned memory access. */ +static inline void +set_uint64(void *cp, uint64_t v) +{ + memcpy(cp,&v,8); +} + +#ifdef WORDS_BIGENDIAN +static inline uint16_t +tor_htons(uint32_t a) +{ + return a; +} + +static inline uint16_t +tor_ntohs(uint64_t a) +{ + return a; +} + +static inline uint32_t +tor_htonl(uint32_t a) +{ + return a; +} + +static inline uint32_t +tor_ntohl(uint64_t a) +{ + return a; +} + +static inline uint64_t +tor_htonll(uint64_t a) +{ + return a; +} + +static inline uint64_t +tor_ntohll(uint64_t a) +{ + return a; +} +#else +static inline uint16_t +tor_htons(uint16_t a) +{ + /* Our compilers will indeed recognize this as bswap. */ + return + ((a & 0x00ff) << 8) | + ((a & 0xff00) >> 8); +} + +static inline uint16_t +tor_ntohs(uint16_t a) +{ + return tor_htons(a); +} + +static inline uint32_t +tor_htonl(uint32_t a) +{ + /* Our compilers will indeed recognize this as bswap. */ + return + ((a & 0x000000ff) <<24) | + ((a & 0x0000ff00) << 8) | + ((a & 0x00ff0000) >> 8) | + ((a & 0xff000000) >>24); +} + +static inline uint32_t +tor_ntohl(uint32_t a) +{ + return tor_htonl(a); +} + +/** Return a uint64_t value from <b>a</b> in network byte order. */ +static inline uint64_t +tor_htonll(uint64_t a) +{ + /* Little endian. The worst... */ + return tor_htonl((uint32_t)(a>>32)) | + (((uint64_t)tor_htonl((uint32_t)a))<<32); +} + +/** Return a uint64_t value from <b>a</b> in host byte order. */ +static inline uint64_t +tor_ntohll(uint64_t a) +{ + return tor_htonll(a); +} +#endif + +#endif diff --git a/src/lib/arch/include.am b/src/lib/arch/include.am new file mode 100644 index 0000000000..f92ee9222f --- /dev/null +++ b/src/lib/arch/include.am @@ -0,0 +1,3 @@ + +noinst_HEADERS += \ + src/lib/arch/bytes.h diff --git a/src/lib/cc/.may_include b/src/lib/cc/.may_include new file mode 100644 index 0000000000..2b06e8519c --- /dev/null +++ b/src/lib/cc/.may_include @@ -0,0 +1 @@ +orconfig.h diff --git a/src/lib/cc/compat_compiler.h b/src/lib/cc/compat_compiler.h new file mode 100644 index 0000000000..3a0f307186 --- /dev/null +++ b/src/lib/cc/compat_compiler.h @@ -0,0 +1,220 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_compiler.h + * \brief Utility macros to handle different features and behavior in different + * compilers. + **/ + +#ifndef TOR_COMPAT_COMPILER_H +#define TOR_COMPAT_COMPILER_H + +#include "orconfig.h" +#include <inttypes.h> + +#if defined(__has_feature) +# if __has_feature(address_sanitizer) +/* Some of the fancy glibc strcmp() macros include references to memory that + * clang rejects because it is off the end of a less-than-3. Clang hates this, + * even though those references never actually happen. */ +# undef strcmp +#endif /* __has_feature(address_sanitizer) */ +#endif /* defined(__has_feature) */ + +#ifndef NULL_REP_IS_ZERO_BYTES +#error "It seems your platform does not represent NULL as zero. We can't cope." +#endif + +#ifndef DOUBLE_0_REP_IS_ZERO_BYTES +#error "It seems your platform does not represent 0.0 as zeros. We can't cope." +#endif + +#if 'a'!=97 || 'z'!=122 || 'A'!=65 || ' '!=32 +#error "It seems that you encode characters in something other than ASCII." +#endif + +/* GCC can check printf and scanf types on arbitrary functions. */ +#ifdef __GNUC__ +#define CHECK_PRINTF(formatIdx, firstArg) \ + __attribute__ ((format(printf, formatIdx, firstArg))) +#else +#define CHECK_PRINTF(formatIdx, firstArg) +#endif /* defined(__GNUC__) */ +#ifdef __GNUC__ +#define CHECK_SCANF(formatIdx, firstArg) \ + __attribute__ ((format(scanf, formatIdx, firstArg))) +#else +#define CHECK_SCANF(formatIdx, firstArg) +#endif /* defined(__GNUC__) */ + +/* What GCC do we have? */ +#ifdef __GNUC__ +#define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#else +#define GCC_VERSION 0 +#endif + +/* Temporarily enable and disable warnings. */ +#ifdef __GNUC__ +# define PRAGMA_STRINGIFY_(s) #s +# define PRAGMA_JOIN_STRINGIFY_(a,b) PRAGMA_STRINGIFY_(a ## b) +/* Support for macro-generated pragmas (c99) */ +# define PRAGMA_(x) _Pragma (#x) +# ifdef __clang__ +# define PRAGMA_DIAGNOSTIC_(x) PRAGMA_(clang diagnostic x) +# else +# define PRAGMA_DIAGNOSTIC_(x) PRAGMA_(GCC diagnostic x) +# endif +# if defined(__clang__) || GCC_VERSION >= 406 +/* we have push/pop support */ +# define DISABLE_GCC_WARNING(warningopt) \ + PRAGMA_DIAGNOSTIC_(push) \ + PRAGMA_DIAGNOSTIC_(ignored PRAGMA_JOIN_STRINGIFY_(-W,warningopt)) +# define ENABLE_GCC_WARNING(warningopt) \ + PRAGMA_DIAGNOSTIC_(pop) +#else /* !(defined(__clang__) || GCC_VERSION >= 406) */ +/* older version of gcc: no push/pop support. */ +# define DISABLE_GCC_WARNING(warningopt) \ + PRAGMA_DIAGNOSTIC_(ignored PRAGMA_JOIN_STRINGIFY_(-W,warningopt)) +# define ENABLE_GCC_WARNING(warningopt) \ + PRAGMA_DIAGNOSTIC_(warning PRAGMA_JOIN_STRINGIFY_(-W,warningopt)) +#endif /* defined(__clang__) || GCC_VERSION >= 406 */ +#else /* !(defined(__GNUC__)) */ +/* not gcc at all */ +# define DISABLE_GCC_WARNING(warning) +# define ENABLE_GCC_WARNING(warning) +#endif /* defined(__GNUC__) */ + +/* inline is __inline on windows. */ +#ifdef _WIN32 +#define inline __inline +#endif + +/* Try to get a reasonable __func__ substitute in place. */ +#if defined(_MSC_VER) + +#define __func__ __FUNCTION__ + +#else +/* For platforms where autoconf works, make sure __func__ is defined + * sanely. */ +#ifndef HAVE_MACRO__func__ +#ifdef HAVE_MACRO__FUNCTION__ +#define __func__ __FUNCTION__ +#elif HAVE_MACRO__FUNC__ +#define __func__ __FUNC__ +#else +#define __func__ "???" +#endif /* defined(HAVE_MACRO__FUNCTION__) || ... */ +#endif /* !defined(HAVE_MACRO__func__) */ +#endif /* defined(_MSC_VER) */ + +#ifdef ENUM_VALS_ARE_SIGNED +#define ENUM_BF(t) unsigned +#else +/** Wrapper for having a bitfield of an enumerated type. Where possible, we + * just use the enumerated type (so the compiler can help us and notice + * problems), but if enumerated types are unsigned, we must use unsigned, + * so that the loss of precision doesn't make large values negative. */ +#define ENUM_BF(t) t +#endif /* defined(ENUM_VALS_ARE_SIGNED) */ + +/* GCC has several useful attributes. */ +#if defined(__GNUC__) && __GNUC__ >= 3 +#define ATTR_NORETURN __attribute__((noreturn)) +#define ATTR_CONST __attribute__((const)) +#define ATTR_MALLOC __attribute__((malloc)) +#define ATTR_NORETURN __attribute__((noreturn)) +#define ATTR_WUR __attribute__((warn_unused_result)) +#define ATTR_UNUSED __attribute__ ((unused)) + +/** Macro: Evaluates to <b>exp</b> and hints the compiler that the value + * of <b>exp</b> will probably be true. + * + * In other words, "if (PREDICT_LIKELY(foo))" is the same as "if (foo)", + * except that it tells the compiler that the branch will be taken most of the + * time. This can generate slightly better code with some CPUs. + */ +#define PREDICT_LIKELY(exp) __builtin_expect(!!(exp), 1) +/** Macro: Evaluates to <b>exp</b> and hints the compiler that the value + * of <b>exp</b> will probably be false. + * + * In other words, "if (PREDICT_UNLIKELY(foo))" is the same as "if (foo)", + * except that it tells the compiler that the branch will usually not be + * taken. This can generate slightly better code with some CPUs. + */ +#define PREDICT_UNLIKELY(exp) __builtin_expect(!!(exp), 0) +#else /* !(defined(__GNUC__) && __GNUC__ >= 3) */ +#define ATTR_NORETURN +#define ATTR_CONST +#define ATTR_MALLOC +#define ATTR_NORETURN +#define ATTR_UNUSED +#define ATTR_WUR +#define PREDICT_LIKELY(exp) (exp) +#define PREDICT_UNLIKELY(exp) (exp) +#endif /* defined(__GNUC__) && __GNUC__ >= 3 */ + +/** Expands to a syntactically valid empty statement. */ +#define STMT_NIL (void)0 + +/** Expands to a syntactically valid empty statement, explicitly (void)ing its + * argument. */ +#define STMT_VOID(a) while (0) { (void)(a); } + +#ifdef __GNUC__ +/** STMT_BEGIN and STMT_END are used to wrap blocks inside macros so that + * the macro can be used as if it were a single C statement. */ +#define STMT_BEGIN (void) ({ +#define STMT_END }) +#elif defined(sun) || defined(__sun__) +#define STMT_BEGIN if (1) { +#define STMT_END } else STMT_NIL +#else +#define STMT_BEGIN do { +#define STMT_END } while (0) +#endif /* defined(__GNUC__) || ... */ + +/* Some tools (like coccinelle) don't like to see operators as macro + * arguments. */ +#define OP_LT < +#define OP_GT > +#define OP_GE >= +#define OP_LE <= +#define OP_EQ == +#define OP_NE != + +#if defined(__MINGW32__) || defined(__MINGW64__) +#define MINGW_ANY +#endif + +/** Macro: yield a pointer to the field at position <b>off</b> within the + * structure <b>st</b>. Example: + * <pre> + * struct a { int foo; int bar; } x; + * off_t bar_offset = offsetof(struct a, bar); + * int *bar_p = STRUCT_VAR_P(&x, bar_offset); + * *bar_p = 3; + * </pre> + */ +#define STRUCT_VAR_P(st, off) ((void*) ( ((char*)(st)) + (off) ) ) + +/** Macro: yield a pointer to an enclosing structure given a pointer to + * a substructure at offset <b>off</b>. Example: + * <pre> + * struct base { ... }; + * struct subtype { int x; struct base b; } x; + * struct base *bp = &x.base; + * struct *sp = SUBTYPE_P(bp, struct subtype, b); + * </pre> + */ +#define SUBTYPE_P(p, subtype, basemember) \ + ((void*) ( ((char*)(p)) - offsetof(subtype, basemember) )) + +/** Macro: Yields the number of elements in array x. */ +#define ARRAY_LENGTH(x) ((sizeof(x)) / sizeof(x[0])) + +#endif /* !defined(TOR_COMPAT_H) */ diff --git a/src/lib/cc/include.am b/src/lib/cc/include.am new file mode 100644 index 0000000000..2ae90f97dd --- /dev/null +++ b/src/lib/cc/include.am @@ -0,0 +1,4 @@ + +noinst_HEADERS += \ + src/lib/cc/compat_compiler.h \ + src/lib/cc/torint.h diff --git a/src/lib/cc/torint.h b/src/lib/cc/torint.h new file mode 100644 index 0000000000..c9b2d329f2 --- /dev/null +++ b/src/lib/cc/torint.h @@ -0,0 +1,128 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file torint.h + * + * \brief Integer definitions used throughout Tor. + **/ + +#ifndef TOR_TORINT_H +#define TOR_TORINT_H + +#include "orconfig.h" + +#include <stdint.h> +#include <stdbool.h> +#include <limits.h> + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_LIMITS_H +#include <sys/limits.h> +#endif + +#ifndef SIZE_MAX +#if SIZEOF_SIZE_T == 8 +#define SIZE_MAX UINT64_MAX +#elif SIZEOF_SIZE_T == 4 +#define SIZE_MAX UINT32_MAX +#else +#error "Can't define SIZE_MAX" +#endif /* SIZEOF_SIZE_T == 8 || ... */ +#endif /* !defined(SIZE_MAX) */ + +#ifndef HAVE_SSIZE_T +#if SIZEOF_SIZE_T == 8 +typedef int64_t ssize_t; +#elif SIZEOF_SIZE_T == 4 +typedef int32_t ssize_t; +#else +#error "Can't define ssize_t." +#endif /* SIZEOF_SIZE_T == 8 || ... */ +#endif /* !defined(HAVE_SSIZE_T) */ + +/* This assumes a sane (2's-complement) representation. But if you + * aren't 2's complement, and you don't define LONG_MAX, then you're so + * bizarre that I want nothing to do with you. */ +#ifndef USING_TWOS_COMPLEMENT +#error "Seems that your platform doesn't use 2's complement arithmetic. Argh." +#endif + +#ifndef TIME_MAX + +#if (SIZEOF_TIME_T == SIZEOF_INT) +#define TIME_MAX ((time_t)INT_MAX) +#elif (SIZEOF_TIME_T == SIZEOF_LONG) +#define TIME_MAX ((time_t)LONG_MAX) +#elif (SIZEOF_TIME_T == 8) +#define TIME_MAX ((time_t)INT64_MAX) +#else +#error "Can't define TIME_MAX" +#endif /* (SIZEOF_TIME_T == SIZEOF_INT) || ... */ + +#endif /* !defined(TIME_MAX) */ + +#ifndef TIME_MIN + +#if (SIZEOF_TIME_T == SIZEOF_INT) +#define TIME_MIN ((time_t)INT_MIN) +#elif (SIZEOF_TIME_T == SIZEOF_LONG) +#define TIME_MIN ((time_t)LONG_MIN) +#elif (SIZEOF_TIME_T == 8) +#define TIME_MIN ((time_t)INT64_MIN) +#else +#error "Can't define TIME_MIN" +#endif /* (SIZEOF_TIME_T == SIZEOF_INT) || ... */ + +#endif /* !defined(TIME_MIN) */ + +#ifndef SIZE_MAX +#if (SIZEOF_SIZE_T == 4) +#define SIZE_MAX UINT32_MAX +#elif (SIZEOF_SIZE_T == 8) +#define SIZE_MAX UINT64_MAX +#else +#error "Can't define SIZE_MAX" +#endif /* (SIZEOF_SIZE_T == 4) || ... */ +#endif /* !defined(SIZE_MAX) */ + +#ifdef _WIN32 +# ifdef _WIN64 +# define TOR_PRIuSZ PRIu64 +# else +# define TOR_PRIuSZ PRIu32 +# endif +#else +# define TOR_PRIuSZ "zu" +#endif + +#ifdef _WIN32 +# ifdef _WIN64 +# define TOR_PRIdSZ PRId64 +# else +# define TOR_PRIdSZ PRId32 +# endif +#else +# define TOR_PRIdSZ "zd" +#endif + +#ifndef SSIZE_MAX +#if (SIZEOF_SIZE_T == 4) +#define SSIZE_MAX INT32_MAX +#elif (SIZEOF_SIZE_T == 8) +#define SSIZE_MAX INT64_MAX +#else +#error "Can't define SSIZE_MAX" +#endif /* (SIZEOF_SIZE_T == 4) || ... */ +#endif /* !defined(SSIZE_MAX) */ + +/** Any ssize_t larger than this amount is likely to be an underflow. */ +#define SSIZE_T_CEILING ((ssize_t)(SSIZE_MAX-16)) +/** Any size_t larger than this amount is likely to be an underflow. */ +#define SIZE_T_CEILING ((size_t)(SSIZE_MAX-16)) + +#endif /* !defined(TOR_TORINT_H) */ diff --git a/src/lib/compress/.may_include b/src/lib/compress/.may_include new file mode 100644 index 0000000000..68fe9f1c54 --- /dev/null +++ b/src/lib/compress/.may_include @@ -0,0 +1,12 @@ +orconfig.h +lib/arch/*.h +lib/cc/*.h +lib/compress/*.h +lib/container/*.h +lib/ctime/*.h +lib/intmath/*.h +lib/log/*.h +lib/malloc/*.h +lib/string/*.h +lib/testsupport/*.h +lib/thread/*.h diff --git a/src/lib/compress/compress.c b/src/lib/compress/compress.c new file mode 100644 index 0000000000..95fd73bb32 --- /dev/null +++ b/src/lib/compress/compress.c @@ -0,0 +1,681 @@ +/* Copyright (c) 2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress.c + * \brief Common compression API implementation. + * + * This file provides a unified interface to all the compression libraries Tor + * knows how to use. + **/ + +#include "orconfig.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "lib/cc/torint.h" + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/arch/bytes.h" +#include "lib/ctime/di_ops.h" +#include "lib/compress/compress.h" +#include "lib/compress/compress_lzma.h" +#include "lib/compress/compress_none.h" +#include "lib/compress/compress_zlib.h" +#include "lib/compress/compress_zstd.h" +#include "lib/intmath/cmp.h" +#include "lib/malloc/malloc.h" +#include "lib/thread/threads.h" + +/** Total number of bytes allocated for compression state overhead. */ +static atomic_counter_t total_compress_allocation; + +/** @{ */ +/* These macros define the maximum allowable compression factor. Anything of + * size greater than CHECK_FOR_COMPRESSION_BOMB_AFTER is not allowed to + * have an uncompression factor (uncompressed size:compressed size ratio) of + * any greater than MAX_UNCOMPRESSION_FACTOR. + * + * Picking a value for MAX_UNCOMPRESSION_FACTOR is a trade-off: we want it to + * be small to limit the attack multiplier, but we also want it to be large + * enough so that no legitimate document --even ones we might invent in the + * future -- ever compresses by a factor of greater than + * MAX_UNCOMPRESSION_FACTOR. Within those parameters, there's a reasonably + * large range of possible values. IMO, anything over 8 is probably safe; IMO + * anything under 50 is probably sufficient. + */ +#define MAX_UNCOMPRESSION_FACTOR 25 +#define CHECK_FOR_COMPRESSION_BOMB_AFTER (1024*64) +/** @} */ + +/** Return true if uncompressing an input of size <b>in_size</b> to an input of + * size at least <b>size_out</b> looks like a compression bomb. */ +MOCK_IMPL(int, +tor_compress_is_compression_bomb,(size_t size_in, size_t size_out)) +{ + if (size_in == 0 || size_out < CHECK_FOR_COMPRESSION_BOMB_AFTER) + return 0; + + return (size_out / size_in > MAX_UNCOMPRESSION_FACTOR); +} + +/** Guess the size that <b>in_len</b> will be after compression or + * decompression. */ +static size_t +guess_compress_size(int compress, compress_method_t method, + compression_level_t compression_level, + size_t in_len) +{ + // ignore these for now. + (void)compression_level; + if (method == NO_METHOD) { + /* Guess that we'll need an extra byte, to avoid a needless realloc + * for nul-termination */ + return (in_len < SIZE_MAX) ? in_len + 1 : in_len; + } + + /* Always guess a factor of 2. */ + if (compress) { + in_len /= 2; + } else { + if (in_len < SIZE_T_CEILING/2) + in_len *= 2; + } + return MAX(in_len, 1024); +} + +/** Internal function to implement tor_compress/tor_uncompress, depending on + * whether <b>compress</b> is set. All arguments are as for tor_compress or + * tor_uncompress. */ +static int +tor_compress_impl(int compress, + char **out, size_t *out_len, + const char *in, size_t in_len, + compress_method_t method, + compression_level_t compression_level, + int complete_only, + int protocol_warn_level) +{ + tor_compress_state_t *stream; + int rv; + + stream = tor_compress_new(compress, method, compression_level); + + if (stream == NULL) { + log_warn(LD_GENERAL, "NULL stream while %scompressing", + compress?"":"de"); + log_debug(LD_GENERAL, "method: %d level: %d at len: %lu", + method, compression_level, (unsigned long)in_len); + return -1; + } + + size_t in_len_orig = in_len; + size_t out_remaining, out_alloc; + char *outptr; + + out_remaining = out_alloc = + guess_compress_size(compress, method, compression_level, in_len); + *out = outptr = tor_malloc(out_remaining); + + const int finish = complete_only || compress; + + while (1) { + switch (tor_compress_process(stream, + &outptr, &out_remaining, + &in, &in_len, finish)) { + case TOR_COMPRESS_DONE: + if (in_len == 0 || compress) { + goto done; + } else { + // More data is present, and we're decompressing. So we may need to + // reinitialize the stream if we are handling multiple concatenated + // inputs. + tor_compress_free(stream); + stream = tor_compress_new(compress, method, compression_level); + if (stream == NULL) { + log_warn(LD_GENERAL, "NULL stream while %scompressing", + compress?"":"de"); + goto err; + } + } + break; + case TOR_COMPRESS_OK: + if (compress || complete_only) { + log_fn(protocol_warn_level, LD_PROTOCOL, + "Unexpected %s while %scompressing", + complete_only?"end of input":"result", + compress?"":"de"); + log_debug(LD_GENERAL, "method: %d level: %d at len: %lu", + method, compression_level, (unsigned long)in_len); + goto err; + } else { + if (in_len == 0) { + goto done; + } + } + break; + case TOR_COMPRESS_BUFFER_FULL: { + if (!compress && outptr < *out+out_alloc) { + // A buffer error in this case means that we have a problem + // with our input. + log_fn(protocol_warn_level, LD_PROTOCOL, + "Possible truncated or corrupt compressed data"); + goto err; + } + if (out_alloc >= SIZE_T_CEILING / 2) { + log_warn(LD_GENERAL, "While %scompressing data: ran out of space.", + compress?"":"un"); + goto err; + } + if (!compress && + tor_compress_is_compression_bomb(in_len_orig, out_alloc)) { + // This should already have been caught down in the backend logic. + // LCOV_EXCL_START + tor_assert_nonfatal_unreached(); + goto err; + // LCOV_EXCL_STOP + } + const size_t offset = outptr - *out; + out_alloc *= 2; + *out = tor_realloc(*out, out_alloc); + outptr = *out + offset; + out_remaining = out_alloc - offset; + break; + } + case TOR_COMPRESS_ERROR: + log_fn(protocol_warn_level, LD_GENERAL, + "Error while %scompressing data: bad input?", + compress?"":"un"); + goto err; // bad data. + + // LCOV_EXCL_START + default: + tor_assert_nonfatal_unreached(); + goto err; + // LCOV_EXCL_STOP + } + } + done: + *out_len = outptr - *out; + if (compress && tor_compress_is_compression_bomb(*out_len, in_len_orig)) { + log_warn(LD_BUG, "We compressed something and got an insanely high " + "compression factor; other Tors would think this was a " + "compression bomb."); + goto err; + } + if (!compress) { + // NUL-terminate our output. + if (out_alloc == *out_len) + *out = tor_realloc(*out, out_alloc + 1); + (*out)[*out_len] = '\0'; + } + rv = 0; + goto out; + + err: + tor_free(*out); + *out_len = 0; + rv = -1; + goto out; + + out: + tor_compress_free(stream); + return rv; +} + +/** Given <b>in_len</b> bytes at <b>in</b>, compress them into a newly + * allocated buffer, using the method described in <b>method</b>. Store the + * compressed string in *<b>out</b>, and its length in *<b>out_len</b>. + * Return 0 on success, -1 on failure. + */ +int +tor_compress(char **out, size_t *out_len, + const char *in, size_t in_len, + compress_method_t method) +{ + return tor_compress_impl(1, out, out_len, in, in_len, method, + BEST_COMPRESSION, + 1, LOG_WARN); +} + +/** Given zero or more compressed strings of total length <b>in_len</b> bytes + * at <b>in</b>, uncompress them into a newly allocated buffer, using the + * method described in <b>method</b>. Store the uncompressed string in + * *<b>out</b>, and its length in *<b>out_len</b>. Return 0 on success, -1 on + * failure. + * + * If any bytes are written to <b>out</b>, an extra byte NUL is always + * written at the end, but not counted in <b>out_len</b>. This is a + * safety feature to ensure that the output can be treated as a + * NUL-terminated string -- though of course, callers should check + * out_len anyway. + * + * If <b>complete_only</b> is true, we consider a truncated input as a + * failure; otherwise we decompress as much as we can. Warn about truncated + * or corrupt inputs at <b>protocol_warn_level</b>. + */ +int +tor_uncompress(char **out, size_t *out_len, + const char *in, size_t in_len, + compress_method_t method, + int complete_only, + int protocol_warn_level) +{ + return tor_compress_impl(0, out, out_len, in, in_len, method, + BEST_COMPRESSION, + complete_only, protocol_warn_level); +} + +/** Try to tell whether the <b>in_len</b>-byte string in <b>in</b> is likely + * to be compressed or not. If it is, return the likeliest compression method. + * Otherwise, return UNKNOWN_METHOD. + */ +compress_method_t +detect_compression_method(const char *in, size_t in_len) +{ + if (in_len > 2 && fast_memeq(in, "\x1f\x8b", 2)) { + return GZIP_METHOD; + } else if (in_len > 2 && (in[0] & 0x0f) == 8 && + (tor_ntohs(get_uint16(in)) % 31) == 0) { + return ZLIB_METHOD; + } else if (in_len > 2 && + fast_memeq(in, "\x5d\x00\x00", 3)) { + return LZMA_METHOD; + } else if (in_len > 3 && + fast_memeq(in, "\x28\xb5\x2f\xfd", 4)) { + return ZSTD_METHOD; + } else { + return UNKNOWN_METHOD; + } +} + +/** Return 1 if a given <b>method</b> is supported; otherwise 0. */ +int +tor_compress_supports_method(compress_method_t method) +{ + switch (method) { + case GZIP_METHOD: + case ZLIB_METHOD: + return tor_zlib_method_supported(); + case LZMA_METHOD: + return tor_lzma_method_supported(); + case ZSTD_METHOD: + return tor_zstd_method_supported(); + case NO_METHOD: + return 1; + case UNKNOWN_METHOD: + default: + return 0; + } +} + +/** + * Return a bitmask of the supported compression types, where 1<<m is + * set in the bitmask if and only if compression with method <b>m</b> is + * supported. + */ +unsigned +tor_compress_get_supported_method_bitmask(void) +{ + static unsigned supported = 0; + if (supported == 0) { + compress_method_t m; + for (m = NO_METHOD; m <= UNKNOWN_METHOD; ++m) { + if (tor_compress_supports_method(m)) { + supported |= (1u << m); + } + } + } + return supported; +} + +/** Table of compression method names. These should have an "x-" prefix, + * if they are not listed in the IANA content coding registry. */ +static const struct { + const char *name; + compress_method_t method; +} compression_method_names[] = { + { "gzip", GZIP_METHOD }, + { "deflate", ZLIB_METHOD }, + // We call this "x-tor-lzma" rather than "x-lzma", because we impose a + // lower maximum memory usage on the decoding side. + { "x-tor-lzma", LZMA_METHOD }, + { "x-zstd" , ZSTD_METHOD }, + { "identity", NO_METHOD }, + + /* Later entries in this table are not canonical; these are recognized but + * not emitted. */ + { "x-gzip", GZIP_METHOD }, +}; + +/** Return the canonical string representation of the compression method + * <b>method</b>, or NULL if the method isn't recognized. */ +const char * +compression_method_get_name(compress_method_t method) +{ + unsigned i; + for (i = 0; i < ARRAY_LENGTH(compression_method_names); ++i) { + if (method == compression_method_names[i].method) + return compression_method_names[i].name; + } + return NULL; +} + +/** Table of compression human readable method names. */ +static const struct { + compress_method_t method; + const char *name; +} compression_method_human_names[] = { + { NO_METHOD, "uncompressed" }, + { GZIP_METHOD, "gzipped" }, + { ZLIB_METHOD, "deflated" }, + { LZMA_METHOD, "LZMA compressed" }, + { ZSTD_METHOD, "Zstandard compressed" }, + { UNKNOWN_METHOD, "unknown encoding" }, +}; + +/** Return a human readable string representation of the compression method + * <b>method</b>, or NULL if the method isn't recognized. */ +const char * +compression_method_get_human_name(compress_method_t method) +{ + unsigned i; + for (i = 0; i < ARRAY_LENGTH(compression_method_human_names); ++i) { + if (method == compression_method_human_names[i].method) + return compression_method_human_names[i].name; + } + return NULL; +} + +/** Return the compression method represented by the string <b>name</b>, or + * UNKNOWN_METHOD if the string isn't recognized. */ +compress_method_t +compression_method_get_by_name(const char *name) +{ + unsigned i; + for (i = 0; i < ARRAY_LENGTH(compression_method_names); ++i) { + if (!strcmp(compression_method_names[i].name, name)) + return compression_method_names[i].method; + } + return UNKNOWN_METHOD; +} + +/** Return a string representation of the version of the library providing the + * compression method given in <b>method</b>. Returns NULL if <b>method</b> is + * unknown or unsupported. */ +const char * +tor_compress_version_str(compress_method_t method) +{ + switch (method) { + case GZIP_METHOD: + case ZLIB_METHOD: + return tor_zlib_get_version_str(); + case LZMA_METHOD: + return tor_lzma_get_version_str(); + case ZSTD_METHOD: + return tor_zstd_get_version_str(); + case NO_METHOD: + case UNKNOWN_METHOD: + default: + return NULL; + } +} + +/** Return a string representation of the version of the library, found at + * compile time, providing the compression method given in <b>method</b>. + * Returns NULL if <b>method</b> is unknown or unsupported. */ +const char * +tor_compress_header_version_str(compress_method_t method) +{ + switch (method) { + case GZIP_METHOD: + case ZLIB_METHOD: + return tor_zlib_get_header_version_str(); + case LZMA_METHOD: + return tor_lzma_get_header_version_str(); + case ZSTD_METHOD: + return tor_zstd_get_header_version_str(); + case NO_METHOD: + case UNKNOWN_METHOD: + default: + return NULL; + } +} + +/** Return the approximate number of bytes allocated for all + * supported compression schemas. */ +size_t +tor_compress_get_total_allocation(void) +{ + return atomic_counter_get(&total_compress_allocation) + + tor_zlib_get_total_allocation() + + tor_lzma_get_total_allocation() + + tor_zstd_get_total_allocation(); +} + +/** Internal state for an incremental compression/decompression. The body of + * this struct is not exposed. */ +struct tor_compress_state_t { + compress_method_t method; /**< The compression method. */ + + union { + tor_zlib_compress_state_t *zlib_state; + tor_lzma_compress_state_t *lzma_state; + tor_zstd_compress_state_t *zstd_state; + } u; /**< Compression backend state. */ +}; + +/** Construct and return a tor_compress_state_t object using <b>method</b>. If + * <b>compress</b>, it's for compression; otherwise it's for decompression. */ +tor_compress_state_t * +tor_compress_new(int compress, compress_method_t method, + compression_level_t compression_level) +{ + tor_compress_state_t *state; + + state = tor_malloc_zero(sizeof(tor_compress_state_t)); + state->method = method; + + switch (method) { + case GZIP_METHOD: + case ZLIB_METHOD: { + tor_zlib_compress_state_t *zlib_state = + tor_zlib_compress_new(compress, method, compression_level); + + if (zlib_state == NULL) + goto err; + + state->u.zlib_state = zlib_state; + break; + } + case LZMA_METHOD: { + tor_lzma_compress_state_t *lzma_state = + tor_lzma_compress_new(compress, method, compression_level); + + if (lzma_state == NULL) + goto err; + + state->u.lzma_state = lzma_state; + break; + } + case ZSTD_METHOD: { + tor_zstd_compress_state_t *zstd_state = + tor_zstd_compress_new(compress, method, compression_level); + + if (zstd_state == NULL) + goto err; + + state->u.zstd_state = zstd_state; + break; + } + case NO_METHOD: { + break; + } + case UNKNOWN_METHOD: + goto err; + } + + atomic_counter_add(&total_compress_allocation, + sizeof(tor_compress_state_t)); + return state; + + err: + tor_free(state); + return NULL; +} + +/** Compress/decompress some bytes using <b>state</b>. Read up to + * *<b>in_len</b> bytes from *<b>in</b>, and write up to *<b>out_len</b> bytes + * to *<b>out</b>, adjusting the values as we go. If <b>finish</b> is true, + * we've reached the end of the input. + * + * Return TOR_COMPRESS_DONE if we've finished the entire + * compression/decompression. + * Return TOR_COMPRESS_OK if we're processed everything from the input. + * Return TOR_COMPRESS_BUFFER_FULL if we're out of space on <b>out</b>. + * Return TOR_COMPRESS_ERROR if the stream is corrupt. + */ +tor_compress_output_t +tor_compress_process(tor_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish) +{ + tor_assert(state != NULL); + const size_t in_len_orig = *in_len; + const size_t out_len_orig = *out_len; + tor_compress_output_t rv; + + if (*out_len == 0 && (*in_len > 0 || finish)) { + // If we still have input data, but no space for output data, we might as + // well return early and let the caller do the reallocation of the out + // variable. + return TOR_COMPRESS_BUFFER_FULL; + } + + switch (state->method) { + case GZIP_METHOD: + case ZLIB_METHOD: + rv = tor_zlib_compress_process(state->u.zlib_state, + out, out_len, in, in_len, + finish); + break; + case LZMA_METHOD: + rv = tor_lzma_compress_process(state->u.lzma_state, + out, out_len, in, in_len, + finish); + break; + case ZSTD_METHOD: + rv = tor_zstd_compress_process(state->u.zstd_state, + out, out_len, in, in_len, + finish); + break; + case NO_METHOD: + rv = tor_cnone_compress_process(out, out_len, in, in_len, + finish); + break; + default: + case UNKNOWN_METHOD: + goto err; + } + if (BUG((rv == TOR_COMPRESS_OK) && + *in_len == in_len_orig && + *out_len == out_len_orig)) { + log_warn(LD_GENERAL, + "More info on the bug: method == %s, finish == %d, " + " *in_len == in_len_orig == %lu, " + "*out_len == out_len_orig == %lu", + compression_method_get_human_name(state->method), finish, + (unsigned long)in_len_orig, (unsigned long)out_len_orig); + return TOR_COMPRESS_ERROR; + } + + return rv; + err: + return TOR_COMPRESS_ERROR; +} + +/** Deallocate <b>state</b>. */ +void +tor_compress_free_(tor_compress_state_t *state) +{ + if (state == NULL) + return; + + switch (state->method) { + case GZIP_METHOD: + case ZLIB_METHOD: + tor_zlib_compress_free(state->u.zlib_state); + break; + case LZMA_METHOD: + tor_lzma_compress_free(state->u.lzma_state); + break; + case ZSTD_METHOD: + tor_zstd_compress_free(state->u.zstd_state); + break; + case NO_METHOD: + break; + case UNKNOWN_METHOD: + break; + } + + atomic_counter_sub(&total_compress_allocation, + sizeof(tor_compress_state_t)); + tor_free(state); +} + +/** Return the approximate number of bytes allocated for <b>state</b>. */ +size_t +tor_compress_state_size(const tor_compress_state_t *state) +{ + tor_assert(state != NULL); + + size_t size = sizeof(tor_compress_state_t); + + switch (state->method) { + case GZIP_METHOD: + case ZLIB_METHOD: + size += tor_zlib_compress_state_size(state->u.zlib_state); + break; + case LZMA_METHOD: + size += tor_lzma_compress_state_size(state->u.lzma_state); + break; + case ZSTD_METHOD: + size += tor_zstd_compress_state_size(state->u.zstd_state); + break; + case NO_METHOD: + case UNKNOWN_METHOD: + break; + } + + return size; +} + +/** Initialize all compression modules. */ +void +tor_compress_init(void) +{ + atomic_counter_init(&total_compress_allocation); + + tor_zlib_init(); + tor_lzma_init(); + tor_zstd_init(); +} + +/** Warn if we had any problems while setting up our compression libraries. + * + * (This isn't part of tor_compress_init, since the logs aren't set up yet.) + */ +void +tor_compress_log_init_warnings(void) +{ + tor_zstd_warn_if_version_mismatched(); +} diff --git a/src/lib/compress/compress.h b/src/lib/compress/compress.h new file mode 100644 index 0000000000..5f16a2ab27 --- /dev/null +++ b/src/lib/compress/compress.h @@ -0,0 +1,99 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress.h + * \brief Headers for compress.c + **/ + +#ifndef TOR_COMPRESS_H +#define TOR_COMPRESS_H + +#include <stddef.h> +#include "lib/testsupport/testsupport.h" + +/** Enumeration of what kind of compression to use. Only ZLIB_METHOD and + * GZIP_METHOD is guaranteed to be supported by the compress/uncompress + * functions here. Call tor_compress_supports_method() to check if a given + * compression schema is supported by Tor. */ +typedef enum compress_method_t { + NO_METHOD=0, // This method must be first. + GZIP_METHOD=1, + ZLIB_METHOD=2, + LZMA_METHOD=3, + ZSTD_METHOD=4, + UNKNOWN_METHOD=5, // This method must be last. Add new ones in the middle. +} compress_method_t; + +/** + * Enumeration to define tradeoffs between memory usage and compression level. + * BEST_COMPRESSION saves the most bandwidth; LOW_COMPRESSION saves the most + * memory. + **/ +typedef enum compression_level_t { + BEST_COMPRESSION, HIGH_COMPRESSION, MEDIUM_COMPRESSION, LOW_COMPRESSION +} compression_level_t; + +int tor_compress(char **out, size_t *out_len, + const char *in, size_t in_len, + compress_method_t method); + +int tor_uncompress(char **out, size_t *out_len, + const char *in, size_t in_len, + compress_method_t method, + int complete_only, + int protocol_warn_level); + +compress_method_t detect_compression_method(const char *in, size_t in_len); + +MOCK_DECL(int,tor_compress_is_compression_bomb,(size_t size_in, + size_t size_out)); + +int tor_compress_supports_method(compress_method_t method); +unsigned tor_compress_get_supported_method_bitmask(void); +const char *compression_method_get_name(compress_method_t method); +const char *compression_method_get_human_name(compress_method_t method); +compress_method_t compression_method_get_by_name(const char *name); + +const char *tor_compress_version_str(compress_method_t method); + +const char *tor_compress_header_version_str(compress_method_t method); + +size_t tor_compress_get_total_allocation(void); + +/** Return values from tor_compress_process; see that function's documentation + * for details. */ +typedef enum { + TOR_COMPRESS_OK, + TOR_COMPRESS_DONE, + TOR_COMPRESS_BUFFER_FULL, + TOR_COMPRESS_ERROR +} tor_compress_output_t; + +/** Internal state for an incremental compression/decompression. */ +typedef struct tor_compress_state_t tor_compress_state_t; + +tor_compress_state_t *tor_compress_new(int compress, + compress_method_t method, + compression_level_t level); + +tor_compress_output_t tor_compress_process(tor_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish); +void tor_compress_free_(tor_compress_state_t *state); +#define tor_compress_free(st) \ + FREE_AND_NULL(tor_compress_state_t, tor_compress_free_, (st)) + +size_t tor_compress_state_size(const tor_compress_state_t *state); + +void tor_compress_init(void); +void tor_compress_log_init_warnings(void); + +struct buf_t; +int buf_add_compress(struct buf_t *buf, struct tor_compress_state_t *state, + const char *data, size_t data_len, int done); + +#endif /* !defined(TOR_COMPRESS_H) */ diff --git a/src/lib/compress/compress_buf.c b/src/lib/compress/compress_buf.c new file mode 100644 index 0000000000..198128b261 --- /dev/null +++ b/src/lib/compress/compress_buf.c @@ -0,0 +1,83 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_buf.c + * + * \brief Working with compressed data in buffers. + **/ + +#define BUFFERS_PRIVATE +#include "lib/cc/compat_compiler.h" +#include "lib/container/buffers.h" +#include "lib/compress/compress.h" +#include "lib/log/util_bug.h" + +#ifdef PARANOIA +/** Helper: If PARANOIA is defined, assert that the buffer in local variable + * <b>buf</b> is well-formed. */ +#define check() STMT_BEGIN buf_assert_ok(buf); STMT_END +#else +#define check() STMT_NIL +#endif /* defined(PARANOIA) */ + +/** Compress or uncompress the <b>data_len</b> bytes in <b>data</b> using the + * compression state <b>state</b>, appending the result to <b>buf</b>. If + * <b>done</b> is true, flush the data in the state and finish the + * compression/uncompression. Return -1 on failure, 0 on success. */ +int +buf_add_compress(buf_t *buf, tor_compress_state_t *state, + const char *data, size_t data_len, + const int done) +{ + char *next; + size_t old_avail, avail; + int over = 0; + + do { + int need_new_chunk = 0; + if (!buf->tail || ! CHUNK_REMAINING_CAPACITY(buf->tail)) { + size_t cap = data_len / 4; + buf_add_chunk_with_capacity(buf, cap, 1); + } + next = CHUNK_WRITE_PTR(buf->tail); + avail = old_avail = CHUNK_REMAINING_CAPACITY(buf->tail); + switch (tor_compress_process(state, &next, &avail, + &data, &data_len, done)) { + case TOR_COMPRESS_DONE: + over = 1; + break; + case TOR_COMPRESS_ERROR: + return -1; + case TOR_COMPRESS_OK: + if (data_len == 0) { + tor_assert_nonfatal(!done); + over = 1; + } + break; + case TOR_COMPRESS_BUFFER_FULL: + if (avail) { + /* The compression module says we need more room + * (TOR_COMPRESS_BUFFER_FULL). Start a new chunk automatically, + * whether were going to or not. */ + need_new_chunk = 1; + } + if (data_len == 0 && !done) { + /* We've consumed all the input data, though, so there's no + * point in forging ahead right now. */ + over = 1; + } + break; + } + buf->datalen += old_avail - avail; + buf->tail->datalen += old_avail - avail; + if (need_new_chunk) { + buf_add_chunk_with_capacity(buf, data_len/4, 1); + } + + } while (!over); + check(); + return 0; +} diff --git a/src/lib/compress/compress_lzma.c b/src/lib/compress/compress_lzma.c new file mode 100644 index 0000000000..2dab37e433 --- /dev/null +++ b/src/lib/compress/compress_lzma.c @@ -0,0 +1,362 @@ +/* Copyright (c) 2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_lzma.c + * \brief Compression backend for LZMA. + * + * This module should never be invoked directly. Use the compress module + * instead. + **/ + +#include "orconfig.h" + +#include "lib/compress/compress.h" +#include "lib/compress/compress_lzma.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/thread/threads.h" + +#ifdef HAVE_LZMA +#include <lzma.h> +#endif + +/** The maximum amount of memory we allow the LZMA decoder to use, in bytes. */ +#define MEMORY_LIMIT (16 * 1024 * 1024) + +/** Total number of bytes allocated for LZMA state. */ +static atomic_counter_t total_lzma_allocation; + +#ifdef HAVE_LZMA +/** Given <b>level</b> return the memory level. */ +static int +memory_level(compression_level_t level) +{ + switch (level) { + default: + case BEST_COMPRESSION: + case HIGH_COMPRESSION: return 6; + case MEDIUM_COMPRESSION: return 4; + case LOW_COMPRESSION: return 2; + } +} + +/** Convert a given <b>error</b> to a human readable error string. */ +static const char * +lzma_error_str(lzma_ret error) +{ + switch (error) { + case LZMA_OK: + return "Operation completed successfully"; + case LZMA_STREAM_END: + return "End of stream"; + case LZMA_NO_CHECK: + return "Input stream lacks integrity check"; + case LZMA_UNSUPPORTED_CHECK: + return "Unable to calculate integrity check"; + case LZMA_GET_CHECK: + return "Integrity check available"; + case LZMA_MEM_ERROR: + return "Unable to allocate memory"; + case LZMA_MEMLIMIT_ERROR: + return "Memory limit reached"; + case LZMA_FORMAT_ERROR: + return "Unknown file format"; + case LZMA_OPTIONS_ERROR: + return "Unsupported options"; + case LZMA_DATA_ERROR: + return "Corrupt input data"; + case LZMA_BUF_ERROR: + return "Unable to progress"; + case LZMA_PROG_ERROR: + return "Programming error"; + default: + return "Unknown LZMA error"; + } +} +#endif /* defined(HAVE_LZMA) */ + +/** Return 1 if LZMA compression is supported; otherwise 0. */ +int +tor_lzma_method_supported(void) +{ +#ifdef HAVE_LZMA + return 1; +#else + return 0; +#endif +} + +/** Return a string representation of the version of the currently running + * version of liblzma. Returns NULL if LZMA is unsupported. */ +const char * +tor_lzma_get_version_str(void) +{ +#ifdef HAVE_LZMA + return lzma_version_string(); +#else + return NULL; +#endif +} + +/** Return a string representation of the version of liblzma used at + * compilation time. Returns NULL if LZMA is unsupported. */ +const char * +tor_lzma_get_header_version_str(void) +{ +#ifdef HAVE_LZMA + return LZMA_VERSION_STRING; +#else + return NULL; +#endif +} + +/** Internal LZMA state for incremental compression/decompression. + * The body of this struct is not exposed. */ +struct tor_lzma_compress_state_t { +#ifdef HAVE_LZMA + lzma_stream stream; /**< The LZMA stream. */ +#endif + + int compress; /**< True if we are compressing; false if we are inflating */ + + /** Number of bytes read so far. Used to detect compression bombs. */ + size_t input_so_far; + /** Number of bytes written so far. Used to detect compression bombs. */ + size_t output_so_far; + + /** Approximate number of bytes allocated for this object. */ + size_t allocation; +}; + +#ifdef HAVE_LZMA +/** Return an approximate number of bytes stored in memory to hold the LZMA + * encoder/decoder state. */ +static size_t +tor_lzma_state_size_precalc(int compress, compression_level_t level) +{ + uint64_t memory_usage; + + if (compress) + memory_usage = lzma_easy_encoder_memusage(memory_level(level)); + else + memory_usage = lzma_easy_decoder_memusage(memory_level(level)); + + if (memory_usage == UINT64_MAX) { + // LCOV_EXCL_START + log_warn(LD_GENERAL, "Unsupported compression level passed to LZMA %s", + compress ? "encoder" : "decoder"); + goto err; + // LCOV_EXCL_STOP + } + + if (memory_usage + sizeof(tor_lzma_compress_state_t) > SIZE_MAX) + memory_usage = SIZE_MAX; + else + memory_usage += sizeof(tor_lzma_compress_state_t); + + return (size_t)memory_usage; + + // LCOV_EXCL_START + err: + return 0; + // LCOV_EXCL_STOP +} +#endif /* defined(HAVE_LZMA) */ + +/** Construct and return a tor_lzma_compress_state_t object using + * <b>method</b>. If <b>compress</b>, it's for compression; otherwise it's for + * decompression. */ +tor_lzma_compress_state_t * +tor_lzma_compress_new(int compress, + compress_method_t method, + compression_level_t level) +{ + tor_assert(method == LZMA_METHOD); + +#ifdef HAVE_LZMA + tor_lzma_compress_state_t *result; + lzma_ret retval; + lzma_options_lzma stream_options; + + // Note that we do not explicitly initialize the lzma_stream object here, + // since the LZMA_STREAM_INIT "just" initializes all members to 0, which is + // also what `tor_malloc_zero()` does. + result = tor_malloc_zero(sizeof(tor_lzma_compress_state_t)); + result->compress = compress; + result->allocation = tor_lzma_state_size_precalc(compress, level); + + if (compress) { + lzma_lzma_preset(&stream_options, memory_level(level)); + + retval = lzma_alone_encoder(&result->stream, &stream_options); + + if (retval != LZMA_OK) { + // LCOV_EXCL_START + log_warn(LD_GENERAL, "Error from LZMA encoder: %s (%u).", + lzma_error_str(retval), retval); + goto err; + // LCOV_EXCL_STOP + } + } else { + retval = lzma_alone_decoder(&result->stream, MEMORY_LIMIT); + + if (retval != LZMA_OK) { + // LCOV_EXCL_START + log_warn(LD_GENERAL, "Error from LZMA decoder: %s (%u).", + lzma_error_str(retval), retval); + goto err; + // LCOV_EXCL_STOP + } + } + + atomic_counter_add(&total_lzma_allocation, result->allocation); + return result; + + /* LCOV_EXCL_START */ + err: + tor_free(result); + return NULL; + /* LCOV_EXCL_STOP */ +#else /* !(defined(HAVE_LZMA)) */ + (void)compress; + (void)method; + (void)level; + + return NULL; +#endif /* defined(HAVE_LZMA) */ +} + +/** Compress/decompress some bytes using <b>state</b>. Read up to + * *<b>in_len</b> bytes from *<b>in</b>, and write up to *<b>out_len</b> bytes + * to *<b>out</b>, adjusting the values as we go. If <b>finish</b> is true, + * we've reached the end of the input. + * + * Return TOR_COMPRESS_DONE if we've finished the entire + * compression/decompression. + * Return TOR_COMPRESS_OK if we're processed everything from the input. + * Return TOR_COMPRESS_BUFFER_FULL if we're out of space on <b>out</b>. + * Return TOR_COMPRESS_ERROR if the stream is corrupt. + */ +tor_compress_output_t +tor_lzma_compress_process(tor_lzma_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish) +{ +#ifdef HAVE_LZMA + lzma_ret retval; + lzma_action action; + + tor_assert(state != NULL); + tor_assert(*in_len <= UINT_MAX); + tor_assert(*out_len <= UINT_MAX); + + state->stream.next_in = (unsigned char *)*in; + state->stream.avail_in = *in_len; + state->stream.next_out = (unsigned char *)*out; + state->stream.avail_out = *out_len; + + action = finish ? LZMA_FINISH : LZMA_RUN; + + retval = lzma_code(&state->stream, action); + + state->input_so_far += state->stream.next_in - ((unsigned char *)*in); + state->output_so_far += state->stream.next_out - ((unsigned char *)*out); + + *out = (char *)state->stream.next_out; + *out_len = state->stream.avail_out; + *in = (const char *)state->stream.next_in; + *in_len = state->stream.avail_in; + + if (! state->compress && + tor_compress_is_compression_bomb(state->input_so_far, + state->output_so_far)) { + log_warn(LD_DIR, "Possible compression bomb; abandoning stream."); + return TOR_COMPRESS_ERROR; + } + + switch (retval) { + case LZMA_OK: + if (state->stream.avail_out == 0 || finish) + return TOR_COMPRESS_BUFFER_FULL; + + return TOR_COMPRESS_OK; + + case LZMA_BUF_ERROR: + if (state->stream.avail_in == 0 && !finish) + return TOR_COMPRESS_OK; + + return TOR_COMPRESS_BUFFER_FULL; + + case LZMA_STREAM_END: + return TOR_COMPRESS_DONE; + + // We list all the possible values of `lzma_ret` here to silence the + // `switch-enum` warning and to detect if a new member was added. + case LZMA_NO_CHECK: + case LZMA_UNSUPPORTED_CHECK: + case LZMA_GET_CHECK: + case LZMA_MEM_ERROR: + case LZMA_MEMLIMIT_ERROR: + case LZMA_FORMAT_ERROR: + case LZMA_OPTIONS_ERROR: + case LZMA_DATA_ERROR: + case LZMA_PROG_ERROR: + default: + log_warn(LD_GENERAL, "LZMA %s didn't finish: %s.", + state->compress ? "compression" : "decompression", + lzma_error_str(retval)); + return TOR_COMPRESS_ERROR; + } +#else /* !(defined(HAVE_LZMA)) */ + (void)state; + (void)out; + (void)out_len; + (void)in; + (void)in_len; + (void)finish; + return TOR_COMPRESS_ERROR; +#endif /* defined(HAVE_LZMA) */ +} + +/** Deallocate <b>state</b>. */ +void +tor_lzma_compress_free_(tor_lzma_compress_state_t *state) +{ + if (state == NULL) + return; + + atomic_counter_sub(&total_lzma_allocation, state->allocation); + +#ifdef HAVE_LZMA + lzma_end(&state->stream); +#endif + + tor_free(state); +} + +/** Return the approximate number of bytes allocated for <b>state</b>. */ +size_t +tor_lzma_compress_state_size(const tor_lzma_compress_state_t *state) +{ + tor_assert(state != NULL); + return state->allocation; +} + +/** Return the approximate number of bytes allocated for all LZMA states. */ +size_t +tor_lzma_get_total_allocation(void) +{ + return atomic_counter_get(&total_lzma_allocation); +} + +/** Initialize the lzma module */ +void +tor_lzma_init(void) +{ + atomic_counter_init(&total_lzma_allocation); +} diff --git a/src/lib/compress/compress_lzma.h b/src/lib/compress/compress_lzma.h new file mode 100644 index 0000000000..556ab437dc --- /dev/null +++ b/src/lib/compress/compress_lzma.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_lzma.h + * \brief Header for compress_lzma.c + **/ + +#ifndef TOR_COMPRESS_LZMA_H +#define TOR_COMPRESS_LZMA_H + +int tor_lzma_method_supported(void); + +const char *tor_lzma_get_version_str(void); + +const char *tor_lzma_get_header_version_str(void); + +/** Internal state for an incremental LZMA compression/decompression. */ +typedef struct tor_lzma_compress_state_t tor_lzma_compress_state_t; + +tor_lzma_compress_state_t * +tor_lzma_compress_new(int compress, + compress_method_t method, + compression_level_t compression_level); + +tor_compress_output_t +tor_lzma_compress_process(tor_lzma_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish); + +void tor_lzma_compress_free_(tor_lzma_compress_state_t *state); +#define tor_lzma_compress_free(st) \ + FREE_AND_NULL(tor_lzma_compress_state_t, \ + tor_lzma_compress_free_, (st)) + +size_t tor_lzma_compress_state_size(const tor_lzma_compress_state_t *state); + +size_t tor_lzma_get_total_allocation(void); + +void tor_lzma_init(void); + +#endif /* !defined(TOR_COMPRESS_LZMA_H) */ + diff --git a/src/lib/compress/compress_none.c b/src/lib/compress/compress_none.c new file mode 100644 index 0000000000..0b5760773a --- /dev/null +++ b/src/lib/compress/compress_none.c @@ -0,0 +1,54 @@ +/* Copyright (c) 2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_none.c + * \brief Compression backend for identity compression. + * + * We actually define this backend so that we can treat the identity transform + * as another case of compression. + * + * This module should never be invoked directly. Use the compress module + * instead. + **/ + +#include "orconfig.h" + +#include "lib/log/log.h" +#include "lib/compress/compress.h" +#include "lib/compress/compress_none.h" +#include "lib/intmath/cmp.h" + +#include <string.h> + +/** Transfer some bytes using the identity transformation. Read up to + * *<b>in_len</b> bytes from *<b>in</b>, and write up to *<b>out_len</b> bytes + * to *<b>out</b>, adjusting the values as we go. If <b>finish</b> is true, + * we've reached the end of the input. + * + * Return TOR_COMPRESS_DONE if we've finished the entire + * compression/decompression. + * Return TOR_COMPRESS_OK if we're processed everything from the input. + * Return TOR_COMPRESS_BUFFER_FULL if we're out of space on <b>out</b>. + * Return TOR_COMPRESS_ERROR if the stream is corrupt. + */ +tor_compress_output_t +tor_cnone_compress_process(char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish) +{ + size_t n_to_copy = MIN(*in_len, *out_len); + + memcpy(*out, *in, n_to_copy); + *out += n_to_copy; + *in += n_to_copy; + *out_len -= n_to_copy; + *in_len -= n_to_copy; + if (*in_len == 0) { + return finish ? TOR_COMPRESS_DONE : TOR_COMPRESS_OK; + } else { + return TOR_COMPRESS_BUFFER_FULL; + } +} diff --git a/src/lib/compress/compress_none.h b/src/lib/compress/compress_none.h new file mode 100644 index 0000000000..2bb9c3d66c --- /dev/null +++ b/src/lib/compress/compress_none.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_none.h + * \brief Header for compress_none.c + **/ + +#ifndef TOR_COMPRESS_NONE_H +#define TOR_COMPRESS_NONE_H + +tor_compress_output_t +tor_cnone_compress_process(char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish); + +#endif /* !defined(TOR_COMPRESS_NONE_H) */ + diff --git a/src/lib/compress/compress_zlib.c b/src/lib/compress/compress_zlib.c new file mode 100644 index 0000000000..df0d1bff5f --- /dev/null +++ b/src/lib/compress/compress_zlib.c @@ -0,0 +1,304 @@ +/* Copyright (c) 2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_zlib.c + * \brief Compression backend for gzip and zlib. + * + * This module should never be invoked directly. Use the compress module + * instead. + **/ + +#include "orconfig.h" + +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/compress/compress.h" +#include "lib/compress/compress_zlib.h" +#include "lib/thread/threads.h" + +/* zlib 1.2.4 and 1.2.5 do some "clever" things with macros. Instead of + saying "(defined(FOO) ? FOO : 0)" they like to say "FOO-0", on the theory + that nobody will care if the compile outputs a no-such-identifier warning. + + Sorry, but we like -Werror over here, so I guess we need to define these. + I hope that zlib 1.2.6 doesn't break these too. +*/ +#ifndef _LARGEFILE64_SOURCE +#define _LARGEFILE64_SOURCE 0 +#endif +#ifndef _LFS64_LARGEFILE +#define _LFS64_LARGEFILE 0 +#endif +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 0 +#endif +#ifndef off64_t +#define off64_t int64_t +#endif + +#include <zlib.h> + +#if defined ZLIB_VERNUM && ZLIB_VERNUM < 0x1200 +#error "We require zlib version 1.2 or later." +#endif + +static size_t tor_zlib_state_size_precalc(int inflate, + int windowbits, int memlevel); + +/** Total number of bytes allocated for zlib state */ +static atomic_counter_t total_zlib_allocation; + +/** Given <b>level</b> return the memory level. */ +static int +memory_level(compression_level_t level) +{ + switch (level) { + default: + case BEST_COMPRESSION: return 9; + case HIGH_COMPRESSION: return 8; + case MEDIUM_COMPRESSION: return 7; + case LOW_COMPRESSION: return 6; + } +} + +/** Return the 'bits' value to tell zlib to use <b>method</b>.*/ +static inline int +method_bits(compress_method_t method, compression_level_t level) +{ + /* Bits+16 means "use gzip" in zlib >= 1.2 */ + const int flag = method == GZIP_METHOD ? 16 : 0; + switch (level) { + default: + case BEST_COMPRESSION: + case HIGH_COMPRESSION: return flag + 15; + case MEDIUM_COMPRESSION: return flag + 13; + case LOW_COMPRESSION: return flag + 11; + } +} + +/** Return 1 if zlib/gzip compression is supported; otherwise 0. */ +int +tor_zlib_method_supported(void) +{ + /* We currently always support zlib/gzip, but we keep this function around in + * case we some day decide to deprecate zlib/gzip support. + */ + return 1; +} + +/** Return a string representation of the version of the currently running + * version of zlib. */ +const char * +tor_zlib_get_version_str(void) +{ + return zlibVersion(); +} + +/** Return a string representation of the version of the version of zlib +* used at compilation. */ +const char * +tor_zlib_get_header_version_str(void) +{ + return ZLIB_VERSION; +} + +/** Internal zlib state for an incremental compression/decompression. + * The body of this struct is not exposed. */ +struct tor_zlib_compress_state_t { + struct z_stream_s stream; /**< The zlib stream */ + int compress; /**< True if we are compressing; false if we are inflating */ + + /** Number of bytes read so far. Used to detect zlib bombs. */ + size_t input_so_far; + /** Number of bytes written so far. Used to detect zlib bombs. */ + size_t output_so_far; + + /** Approximate number of bytes allocated for this object. */ + size_t allocation; +}; + +/** Return an approximate number of bytes used in RAM to hold a state with + * window bits <b>windowBits</b> and compression level 'memlevel' */ +static size_t +tor_zlib_state_size_precalc(int inflate_, int windowbits, int memlevel) +{ + windowbits &= 15; + +#define A_FEW_KILOBYTES 2048 + + if (inflate_) { + /* From zconf.h: + + "The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects." + */ + return sizeof(tor_zlib_compress_state_t) + sizeof(struct z_stream_s) + + (1 << 15) + A_FEW_KILOBYTES; + } else { + /* Also from zconf.h: + + "The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + ... plus a few kilobytes for small objects." + */ + return sizeof(tor_zlib_compress_state_t) + sizeof(struct z_stream_s) + + (1 << (windowbits + 2)) + (1 << (memlevel + 9)) + A_FEW_KILOBYTES; + } +#undef A_FEW_KILOBYTES +} + +/** Construct and return a tor_zlib_compress_state_t object using + * <b>method</b>. If <b>compress</b>, it's for compression; otherwise it's for + * decompression. */ +tor_zlib_compress_state_t * +tor_zlib_compress_new(int compress_, + compress_method_t method, + compression_level_t compression_level) +{ + tor_zlib_compress_state_t *out; + int bits, memlevel; + + if (! compress_) { + /* use this setting for decompression, since we might have the + * max number of window bits */ + compression_level = BEST_COMPRESSION; + } + + out = tor_malloc_zero(sizeof(tor_zlib_compress_state_t)); + out->stream.zalloc = Z_NULL; + out->stream.zfree = Z_NULL; + out->stream.opaque = NULL; + out->compress = compress_; + bits = method_bits(method, compression_level); + memlevel = memory_level(compression_level); + if (compress_) { + if (deflateInit2(&out->stream, Z_BEST_COMPRESSION, Z_DEFLATED, + bits, memlevel, + Z_DEFAULT_STRATEGY) != Z_OK) + goto err; // LCOV_EXCL_LINE + } else { + if (inflateInit2(&out->stream, bits) != Z_OK) + goto err; // LCOV_EXCL_LINE + } + out->allocation = tor_zlib_state_size_precalc(!compress_, bits, memlevel); + + atomic_counter_add(&total_zlib_allocation, out->allocation); + + return out; + + err: + tor_free(out); + return NULL; +} + +/** Compress/decompress some bytes using <b>state</b>. Read up to + * *<b>in_len</b> bytes from *<b>in</b>, and write up to *<b>out_len</b> bytes + * to *<b>out</b>, adjusting the values as we go. If <b>finish</b> is true, + * we've reached the end of the input. + * + * Return TOR_COMPRESS_DONE if we've finished the entire + * compression/decompression. + * Return TOR_COMPRESS_OK if we're processed everything from the input. + * Return TOR_COMPRESS_BUFFER_FULL if we're out of space on <b>out</b>. + * Return TOR_COMPRESS_ERROR if the stream is corrupt. + */ +tor_compress_output_t +tor_zlib_compress_process(tor_zlib_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish) +{ + int err; + tor_assert(state != NULL); + if (*in_len > UINT_MAX || + *out_len > UINT_MAX) { + return TOR_COMPRESS_ERROR; + } + + state->stream.next_in = (unsigned char*) *in; + state->stream.avail_in = (unsigned int)*in_len; + state->stream.next_out = (unsigned char*) *out; + state->stream.avail_out = (unsigned int)*out_len; + + if (state->compress) { + err = deflate(&state->stream, finish ? Z_FINISH : Z_NO_FLUSH); + } else { + err = inflate(&state->stream, finish ? Z_FINISH : Z_SYNC_FLUSH); + } + + state->input_so_far += state->stream.next_in - ((unsigned char*)*in); + state->output_so_far += state->stream.next_out - ((unsigned char*)*out); + + *out = (char*) state->stream.next_out; + *out_len = state->stream.avail_out; + *in = (const char *) state->stream.next_in; + *in_len = state->stream.avail_in; + + if (! state->compress && + tor_compress_is_compression_bomb(state->input_so_far, + state->output_so_far)) { + log_warn(LD_DIR, "Possible zlib bomb; abandoning stream."); + return TOR_COMPRESS_ERROR; + } + + switch (err) + { + case Z_STREAM_END: + return TOR_COMPRESS_DONE; + case Z_BUF_ERROR: + if (state->stream.avail_in == 0 && !finish) + return TOR_COMPRESS_OK; + return TOR_COMPRESS_BUFFER_FULL; + case Z_OK: + if (state->stream.avail_out == 0 || finish) + return TOR_COMPRESS_BUFFER_FULL; + return TOR_COMPRESS_OK; + default: + log_warn(LD_GENERAL, "Gzip returned an error: %s", + state->stream.msg ? state->stream.msg : "<no message>"); + return TOR_COMPRESS_ERROR; + } +} + +/** Deallocate <b>state</b>. */ +void +tor_zlib_compress_free_(tor_zlib_compress_state_t *state) +{ + if (state == NULL) + return; + + atomic_counter_sub(&total_zlib_allocation, state->allocation); + + if (state->compress) + deflateEnd(&state->stream); + else + inflateEnd(&state->stream); + + tor_free(state); +} + +/** Return the approximate number of bytes allocated for <b>state</b>. */ +size_t +tor_zlib_compress_state_size(const tor_zlib_compress_state_t *state) +{ + tor_assert(state != NULL); + return state->allocation; +} + +/** Return the approximate number of bytes allocated for all zlib states. */ +size_t +tor_zlib_get_total_allocation(void) +{ + return atomic_counter_get(&total_zlib_allocation); +} + +/** Set up global state for the zlib module */ +void +tor_zlib_init(void) +{ + atomic_counter_init(&total_zlib_allocation); +} diff --git a/src/lib/compress/compress_zlib.h b/src/lib/compress/compress_zlib.h new file mode 100644 index 0000000000..e4f248cd9b --- /dev/null +++ b/src/lib/compress/compress_zlib.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_zlib.h + * \brief Header for compress_zlib.c + **/ + +#ifndef TOR_COMPRESS_ZLIB_H +#define TOR_COMPRESS_ZLIB_H + +int tor_zlib_method_supported(void); + +const char *tor_zlib_get_version_str(void); + +const char *tor_zlib_get_header_version_str(void); + +/** Internal state for an incremental zlib/gzip compression/decompression. */ +typedef struct tor_zlib_compress_state_t tor_zlib_compress_state_t; + +tor_zlib_compress_state_t * +tor_zlib_compress_new(int compress, + compress_method_t method, + compression_level_t compression_level); + +tor_compress_output_t +tor_zlib_compress_process(tor_zlib_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish); + +void tor_zlib_compress_free_(tor_zlib_compress_state_t *state); +#define tor_zlib_compress_free(st) \ + FREE_AND_NULL(tor_zlib_compress_state_t, \ + tor_zlib_compress_free_, (st)) + +size_t tor_zlib_compress_state_size(const tor_zlib_compress_state_t *state); + +size_t tor_zlib_get_total_allocation(void); + +void tor_zlib_init(void); + +#endif /* !defined(TOR_COMPRESS_ZLIB_H) */ + diff --git a/src/lib/compress/compress_zstd.c b/src/lib/compress/compress_zstd.c new file mode 100644 index 0000000000..45d0d4d602 --- /dev/null +++ b/src/lib/compress/compress_zstd.c @@ -0,0 +1,541 @@ +/* Copyright (c) 2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_zstd.c + * \brief Compression backend for Zstandard. + * + * This module should never be invoked directly. Use the compress module + * instead. + **/ + +#include "orconfig.h" + +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/compress/compress.h" +#include "lib/compress/compress_zstd.h" +#include "lib/string/printf.h" +#include "lib/thread/threads.h" + +#ifdef ENABLE_ZSTD_ADVANCED_APIS +/* This is a lie, but we make sure it doesn't get us in trouble by wrapping + * all invocations of zstd's static-only functions in a check to make sure + * that the compile-time version matches the run-time version. */ +#define ZSTD_STATIC_LINKING_ONLY +#endif + +#ifdef HAVE_ZSTD +#ifdef HAVE_CFLAG_WUNUSED_CONST_VARIABLE +DISABLE_GCC_WARNING(unused-const-variable) +#endif +#include <zstd.h> +#ifdef HAVE_CFLAG_WUNUSED_CONST_VARIABLE +ENABLE_GCC_WARNING(unused-const-variable) +#endif +#endif + +/** Total number of bytes allocated for Zstandard state. */ +static atomic_counter_t total_zstd_allocation; + +#ifdef HAVE_ZSTD +/** Given <b>level</b> return the memory level. */ +static int +memory_level(compression_level_t level) +{ + switch (level) { + default: + case BEST_COMPRESSION: + case HIGH_COMPRESSION: return 9; + case MEDIUM_COMPRESSION: return 8; + case LOW_COMPRESSION: return 7; + } +} +#endif /* defined(HAVE_ZSTD) */ + +/** Return 1 if Zstandard compression is supported; otherwise 0. */ +int +tor_zstd_method_supported(void) +{ +#ifdef HAVE_ZSTD + return 1; +#else + return 0; +#endif +} + +#ifdef HAVE_ZSTD +/** Format a zstd version number as a string in <b>buf</b>. */ +static void +tor_zstd_format_version(char *buf, size_t buflen, unsigned version_number) +{ + tor_snprintf(buf, buflen, + "%u.%u.%u", + version_number / 10000 % 100, + version_number / 100 % 100, + version_number % 100); +} +#endif + +#define VERSION_STR_MAX_LEN 16 /* more than enough space for 99.99.99 */ + +/** Return a string representation of the version of the currently running + * version of libzstd. Returns NULL if Zstandard is unsupported. */ +const char * +tor_zstd_get_version_str(void) +{ +#ifdef HAVE_ZSTD + static char version_str[VERSION_STR_MAX_LEN]; + + tor_zstd_format_version(version_str, sizeof(version_str), + ZSTD_versionNumber()); + + return version_str; +#else /* !(defined(HAVE_ZSTD)) */ + return NULL; +#endif /* defined(HAVE_ZSTD) */ +} + +/** Return a string representation of the version of the version of libzstd + * used at compilation time. Returns NULL if Zstandard is unsupported. */ +const char * +tor_zstd_get_header_version_str(void) +{ +#ifdef HAVE_ZSTD + return ZSTD_VERSION_STRING; +#else + return NULL; +#endif +} + +#ifdef TOR_UNIT_TESTS +static int static_apis_disable_for_testing = 0; +#endif + +/** Return true iff we can use the "static-only" APIs. */ +int +tor_zstd_can_use_static_apis(void) +{ +#if defined(ZSTD_STATIC_LINKING_ONLY) && defined(HAVE_ZSTD) +#ifdef TOR_UNIT_TESTS + if (static_apis_disable_for_testing) { + return 0; + } +#endif + return (ZSTD_VERSION_NUMBER == ZSTD_versionNumber()); +#else + return 0; +#endif +} + +/** Internal Zstandard state for incremental compression/decompression. + * The body of this struct is not exposed. */ +struct tor_zstd_compress_state_t { +#ifdef HAVE_ZSTD + union { + /** Compression stream. Used when <b>compress</b> is true. */ + ZSTD_CStream *compress_stream; + /** Decompression stream. Used when <b>compress</b> is false. */ + ZSTD_DStream *decompress_stream; + } u; /**< Zstandard stream objects. */ +#endif /* defined(HAVE_ZSTD) */ + + int compress; /**< True if we are compressing; false if we are inflating */ + int have_called_end; /**< True if we are compressing and we've called + * ZSTD_endStream */ + + /** Number of bytes read so far. Used to detect compression bombs. */ + size_t input_so_far; + /** Number of bytes written so far. Used to detect compression bombs. */ + size_t output_so_far; + + /** Approximate number of bytes allocated for this object. */ + size_t allocation; +}; + +#ifdef HAVE_ZSTD +/** Return an approximate number of bytes stored in memory to hold the + * Zstandard compression/decompression state. This is a fake estimate + * based on inspecting the zstd source: tor_zstd_state_size_precalc() is + * more accurate when it's allowed to use "static-only" functions */ +static size_t +tor_zstd_state_size_precalc_fake(int compress, int preset) +{ + tor_assert(preset > 0); + + size_t memory_usage = sizeof(tor_zstd_compress_state_t); + + // The Zstandard library provides a number of functions that would be useful + // here, but they are, unfortunately, still considered experimental and are + // thus only available in libzstd if we link against the library statically. + // + // The code in this function tries to approximate the calculations without + // being able to use the following: + // + // - We do not have access to neither the internal members of ZSTD_CStream + // and ZSTD_DStream and their internal context objects. + // + // - We cannot use ZSTD_sizeof_CStream() and ZSTD_sizeof_DStream() since they + // are unexposed. + // + // In the future it might be useful to check if libzstd have started + // providing these functions in a stable manner and simplify this function. + if (compress) { + // We try to approximate the ZSTD_sizeof_CStream(ZSTD_CStream *stream) + // function here. This function uses the following fields to make its + // estimate: + + // - sizeof(ZSTD_CStream): Around 192 bytes on a 64-bit machine: + memory_usage += 192; + + // - ZSTD_sizeof_CCtx(stream->cctx): This function requires access to + // variables that are not exposed via the public API. We use a _very_ + // simplified function to calculate the estimated amount of bytes used in + // this struct. + // memory_usage += (preset - 0.5) * 1024 * 1024; + memory_usage += (preset * 1024 * 1024) - (512 * 1024); + // - ZSTD_sizeof_CDict(stream->cdictLocal): Unused in Tor: 0 bytes. + // - stream->outBuffSize: 128 KB: + memory_usage += 128 * 1024; + // - stream->inBuffSize: 2048 KB: + memory_usage += 2048 * 1024; + } else { + // We try to approximate the ZSTD_sizeof_DStream(ZSTD_DStream *stream) + // function here. This function uses the following fields to make its + // estimate: + + // - sizeof(ZSTD_DStream): Around 208 bytes on a 64-bit machine: + memory_usage += 208; + // - ZSTD_sizeof_DCtx(stream->dctx): Around 150 KB. + memory_usage += 150 * 1024; + + // - ZSTD_sizeof_DDict(stream->ddictLocal): Unused in Tor: 0 bytes. + // - stream->inBuffSize: 0 KB. + // - stream->outBuffSize: 0 KB. + } + + return memory_usage; +} + +/** Return an approximate number of bytes stored in memory to hold the + * Zstandard compression/decompression state. */ +static size_t +tor_zstd_state_size_precalc(int compress, int preset) +{ +#ifdef ZSTD_STATIC_LINKING_ONLY + if (tor_zstd_can_use_static_apis()) { + if (compress) { +#ifdef HAVE_ZSTD_ESTIMATECSTREAMSIZE + return ZSTD_estimateCStreamSize(preset); +#endif + } else { +#ifdef HAVE_ZSTD_ESTIMATEDCTXSIZE + /* Could use DStream, but that takes a windowSize. */ + return ZSTD_estimateDCtxSize(); +#endif + } + } +#endif + return tor_zstd_state_size_precalc_fake(compress, preset); +} +#endif /* defined(HAVE_ZSTD) */ + +/** Construct and return a tor_zstd_compress_state_t object using + * <b>method</b>. If <b>compress</b>, it's for compression; otherwise it's for + * decompression. */ +tor_zstd_compress_state_t * +tor_zstd_compress_new(int compress, + compress_method_t method, + compression_level_t level) +{ + tor_assert(method == ZSTD_METHOD); + +#ifdef HAVE_ZSTD + const int preset = memory_level(level); + tor_zstd_compress_state_t *result; + size_t retval; + + result = tor_malloc_zero(sizeof(tor_zstd_compress_state_t)); + result->compress = compress; + result->allocation = tor_zstd_state_size_precalc(compress, preset); + + if (compress) { + result->u.compress_stream = ZSTD_createCStream(); + + if (result->u.compress_stream == NULL) { + // LCOV_EXCL_START + log_warn(LD_GENERAL, "Error while creating Zstandard compression " + "stream"); + goto err; + // LCOV_EXCL_STOP + } + + retval = ZSTD_initCStream(result->u.compress_stream, preset); + + if (ZSTD_isError(retval)) { + // LCOV_EXCL_START + log_warn(LD_GENERAL, "Zstandard stream initialization error: %s", + ZSTD_getErrorName(retval)); + goto err; + // LCOV_EXCL_STOP + } + } else { + result->u.decompress_stream = ZSTD_createDStream(); + + if (result->u.decompress_stream == NULL) { + // LCOV_EXCL_START + log_warn(LD_GENERAL, "Error while creating Zstandard decompression " + "stream"); + goto err; + // LCOV_EXCL_STOP + } + + retval = ZSTD_initDStream(result->u.decompress_stream); + + if (ZSTD_isError(retval)) { + // LCOV_EXCL_START + log_warn(LD_GENERAL, "Zstandard stream initialization error: %s", + ZSTD_getErrorName(retval)); + goto err; + // LCOV_EXCL_STOP + } + } + + atomic_counter_add(&total_zstd_allocation, result->allocation); + return result; + + err: + // LCOV_EXCL_START + if (compress) { + ZSTD_freeCStream(result->u.compress_stream); + } else { + ZSTD_freeDStream(result->u.decompress_stream); + } + + tor_free(result); + return NULL; + // LCOV_EXCL_STOP +#else /* !(defined(HAVE_ZSTD)) */ + (void)compress; + (void)method; + (void)level; + + return NULL; +#endif /* defined(HAVE_ZSTD) */ +} + +/** Compress/decompress some bytes using <b>state</b>. Read up to + * *<b>in_len</b> bytes from *<b>in</b>, and write up to *<b>out_len</b> bytes + * to *<b>out</b>, adjusting the values as we go. If <b>finish</b> is true, + * we've reached the end of the input. + * + * Return TOR_COMPRESS_DONE if we've finished the entire + * compression/decompression. + * Return TOR_COMPRESS_OK if we're processed everything from the input. + * Return TOR_COMPRESS_BUFFER_FULL if we're out of space on <b>out</b>. + * Return TOR_COMPRESS_ERROR if the stream is corrupt. + */ +tor_compress_output_t +tor_zstd_compress_process(tor_zstd_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish) +{ +#ifdef HAVE_ZSTD + size_t retval; + + tor_assert(state != NULL); + tor_assert(*in_len <= UINT_MAX); + tor_assert(*out_len <= UINT_MAX); + + ZSTD_inBuffer input = { *in, *in_len, 0 }; + ZSTD_outBuffer output = { *out, *out_len, 0 }; + + if (BUG(finish == 0 && state->have_called_end)) { + finish = 1; + } + + if (state->compress) { + if (! state->have_called_end) + retval = ZSTD_compressStream(state->u.compress_stream, + &output, &input); + else + retval = 0; + } else { + retval = ZSTD_decompressStream(state->u.decompress_stream, + &output, &input); + } + + state->input_so_far += input.pos; + state->output_so_far += output.pos; + + *out = (char *)output.dst + output.pos; + *out_len = output.size - output.pos; + *in = (char *)input.src + input.pos; + *in_len = input.size - input.pos; + + if (! state->compress && + tor_compress_is_compression_bomb(state->input_so_far, + state->output_so_far)) { + log_warn(LD_DIR, "Possible compression bomb; abandoning stream."); + return TOR_COMPRESS_ERROR; + } + + if (ZSTD_isError(retval)) { + log_warn(LD_GENERAL, "Zstandard %s didn't finish: %s.", + state->compress ? "compression" : "decompression", + ZSTD_getErrorName(retval)); + return TOR_COMPRESS_ERROR; + } + + if (state->compress && !state->have_called_end) { + retval = ZSTD_flushStream(state->u.compress_stream, &output); + + *out = (char *)output.dst + output.pos; + *out_len = output.size - output.pos; + + if (ZSTD_isError(retval)) { + log_warn(LD_GENERAL, "Zstandard compression unable to flush: %s.", + ZSTD_getErrorName(retval)); + return TOR_COMPRESS_ERROR; + } + + // ZSTD_flushStream returns 0 if the frame is done, or >0 if it + // is incomplete. + if (retval > 0) { + return TOR_COMPRESS_BUFFER_FULL; + } + } + + if (!finish) { + // The caller says we're not done with the input, so no need to write an + // epilogue. + return TOR_COMPRESS_OK; + } else if (state->compress) { + if (*in_len) { + // We say that we're not done with the input, so we can't write an + // epilogue. + return TOR_COMPRESS_OK; + } + + retval = ZSTD_endStream(state->u.compress_stream, &output); + state->have_called_end = 1; + *out = (char *)output.dst + output.pos; + *out_len = output.size - output.pos; + + if (ZSTD_isError(retval)) { + log_warn(LD_GENERAL, "Zstandard compression unable to write " + "epilogue: %s.", + ZSTD_getErrorName(retval)); + return TOR_COMPRESS_ERROR; + } + + // endStream returns the number of bytes that is needed to write the + // epilogue. + if (retval > 0) + return TOR_COMPRESS_BUFFER_FULL; + + return TOR_COMPRESS_DONE; + } else /* if (!state->compress) */ { + // ZSTD_decompressStream returns 0 if the frame is done, or >0 if it + // is incomplete. + // We check this above. + tor_assert_nonfatal(!ZSTD_isError(retval)); + // Start a new frame if this frame is done + if (retval == 0) + return TOR_COMPRESS_DONE; + // Don't check out_len, it might have some space left if the next output + // chunk is larger than the remaining space + else if (*in_len > 0) + return TOR_COMPRESS_BUFFER_FULL; + else + return TOR_COMPRESS_OK; + } + +#else /* !(defined(HAVE_ZSTD)) */ + (void)state; + (void)out; + (void)out_len; + (void)in; + (void)in_len; + (void)finish; + + return TOR_COMPRESS_ERROR; +#endif /* defined(HAVE_ZSTD) */ +} + +/** Deallocate <b>state</b>. */ +void +tor_zstd_compress_free_(tor_zstd_compress_state_t *state) +{ + if (state == NULL) + return; + + atomic_counter_sub(&total_zstd_allocation, state->allocation); + +#ifdef HAVE_ZSTD + if (state->compress) { + ZSTD_freeCStream(state->u.compress_stream); + } else { + ZSTD_freeDStream(state->u.decompress_stream); + } +#endif /* defined(HAVE_ZSTD) */ + + tor_free(state); +} + +/** Return the approximate number of bytes allocated for <b>state</b>. */ +size_t +tor_zstd_compress_state_size(const tor_zstd_compress_state_t *state) +{ + tor_assert(state != NULL); + return state->allocation; +} + +/** Return the approximate number of bytes allocated for all Zstandard + * states. */ +size_t +tor_zstd_get_total_allocation(void) +{ + return atomic_counter_get(&total_zstd_allocation); +} + +/** Initialize the zstd module */ +void +tor_zstd_init(void) +{ + atomic_counter_init(&total_zstd_allocation); +} + +/** Warn if the header and library versions don't match. */ +void +tor_zstd_warn_if_version_mismatched(void) +{ +#if defined(HAVE_ZSTD) && defined(ENABLE_ZSTD_ADVANCED_APIS) + if (! tor_zstd_can_use_static_apis()) { + char header_version[VERSION_STR_MAX_LEN]; + char runtime_version[VERSION_STR_MAX_LEN]; + tor_zstd_format_version(header_version, sizeof(header_version), + ZSTD_VERSION_NUMBER); + tor_zstd_format_version(runtime_version, sizeof(runtime_version), + ZSTD_versionNumber()); + + log_warn(LD_GENERAL, + "Tor was compiled with zstd %s, but is running with zstd %s. " + "For safety, we'll avoid using advanced zstd functionality.", + header_version, runtime_version); + } +#endif +} + +#ifdef TOR_UNIT_TESTS +/** Testing only: disable usage of static-only APIs, so we can make sure that + * we still work without them. */ +void +tor_zstd_set_static_apis_disabled_for_testing(int disabled) +{ + static_apis_disable_for_testing = disabled; +} +#endif diff --git a/src/lib/compress/compress_zstd.h b/src/lib/compress/compress_zstd.h new file mode 100644 index 0000000000..47f950b9e0 --- /dev/null +++ b/src/lib/compress/compress_zstd.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compress_zstd.h + * \brief Header for compress_zstd.c + **/ + +#ifndef TOR_COMPRESS_ZSTD_H +#define TOR_COMPRESS_ZSTD_H + +int tor_zstd_method_supported(void); + +const char *tor_zstd_get_version_str(void); + +const char *tor_zstd_get_header_version_str(void); + +int tor_zstd_can_use_static_apis(void); + +/** Internal state for an incremental Zstandard compression/decompression. */ +typedef struct tor_zstd_compress_state_t tor_zstd_compress_state_t; + +tor_zstd_compress_state_t * +tor_zstd_compress_new(int compress, + compress_method_t method, + compression_level_t compression_level); + +tor_compress_output_t +tor_zstd_compress_process(tor_zstd_compress_state_t *state, + char **out, size_t *out_len, + const char **in, size_t *in_len, + int finish); + +void tor_zstd_compress_free_(tor_zstd_compress_state_t *state); +#define tor_zstd_compress_free(st) \ + FREE_AND_NULL(tor_zstd_compress_state_t, \ + tor_zstd_compress_free_, (st)) + +size_t tor_zstd_compress_state_size(const tor_zstd_compress_state_t *state); + +size_t tor_zstd_get_total_allocation(void); + +void tor_zstd_init(void); +void tor_zstd_warn_if_version_mismatched(void); + +#ifdef TOR_UNIT_TESTS +void tor_zstd_set_static_apis_disabled_for_testing(int disabled); +#endif + +#endif /* !defined(TOR_COMPRESS_ZSTD_H) */ + diff --git a/src/lib/compress/include.am b/src/lib/compress/include.am new file mode 100644 index 0000000000..75c9032bd2 --- /dev/null +++ b/src/lib/compress/include.am @@ -0,0 +1,26 @@ + +noinst_LIBRARIES += src/lib/libtor-compress.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-compress-testing.a +endif + +src_lib_libtor_compress_a_SOURCES = \ + src/lib/compress/compress.c \ + src/lib/compress/compress_buf.c \ + src/lib/compress/compress_lzma.c \ + src/lib/compress/compress_none.c \ + src/lib/compress/compress_zlib.c \ + src/lib/compress/compress_zstd.c + +src_lib_libtor_compress_testing_a_SOURCES = \ + $(src_lib_libtor_compress_a_SOURCES) +src_lib_libtor_compress_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_compress_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/compress/compress.h \ + src/lib/compress/compress_lzma.h \ + src/lib/compress/compress_none.h \ + src/lib/compress/compress_zlib.h \ + src/lib/compress/compress_zstd.h diff --git a/src/lib/container/.may_include b/src/lib/container/.may_include new file mode 100644 index 0000000000..90de5eda40 --- /dev/null +++ b/src/lib/container/.may_include @@ -0,0 +1,18 @@ +orconfig.h +lib/cc/*.h +lib/container/*.h +lib/ctime/*.h +lib/defs/*.h +lib/malloc/*.h +lib/err/*.h +lib/smartlist_core/*.h +lib/string/*.h +lib/testsupport/testsupport.h +lib/intmath/*.h +lib/log/*.h + +# XXXX I am unsure about this one. It's only here for buffers.c +lib/time/*.h + +ht.h +siphash.h diff --git a/src/lib/container/bitarray.h b/src/lib/container/bitarray.h new file mode 100644 index 0000000000..910d5fea65 --- /dev/null +++ b/src/lib/container/bitarray.h @@ -0,0 +1,86 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_BITARRAY_H +#define TOR_BITARRAY_H + +/** + * \file bitarray.h + * + * \brief Implements a variable-sized (but non-resizeable) bit-array. + **/ + +#include "orconfig.h" +#include <string.h> +#include "lib/cc/torint.h" +#include "lib/malloc/malloc.h" + +#if SIZEOF_INT == 4 +#define BITARRAY_SHIFT 5 +#elif SIZEOF_INT == 8 +#define BITARRAY_SHIFT 6 +#else +#error "int is neither 4 nor 8 bytes. I can't deal with that." +#endif /* SIZEOF_INT == 4 || ... */ +#define BITARRAY_MASK ((1u<<BITARRAY_SHIFT)-1) + +/** A random-access array of one-bit-wide elements. */ +typedef unsigned int bitarray_t; +/** Create a new bit array that can hold <b>n_bits</b> bits. */ +static inline bitarray_t * +bitarray_init_zero(unsigned int n_bits) +{ + /* round up to the next int. */ + size_t sz = (n_bits+BITARRAY_MASK) >> BITARRAY_SHIFT; + return tor_calloc(sz, sizeof(unsigned int)); +} +/** Expand <b>ba</b> from holding <b>n_bits_old</b> to <b>n_bits_new</b>, + * clearing all new bits. Returns a possibly changed pointer to the + * bitarray. */ +static inline bitarray_t * +bitarray_expand(bitarray_t *ba, + unsigned int n_bits_old, unsigned int n_bits_new) +{ + size_t sz_old = (n_bits_old+BITARRAY_MASK) >> BITARRAY_SHIFT; + size_t sz_new = (n_bits_new+BITARRAY_MASK) >> BITARRAY_SHIFT; + char *ptr; + if (sz_new <= sz_old) + return ba; + ptr = tor_reallocarray(ba, sz_new, sizeof(unsigned int)); + /* This memset does nothing to the older excess bytes. But they were + * already set to 0 by bitarry_init_zero. */ + memset(ptr+sz_old*sizeof(unsigned int), 0, + (sz_new-sz_old)*sizeof(unsigned int)); + return (bitarray_t*) ptr; +} +/** Free the bit array <b>ba</b>. */ +static inline void +bitarray_free_(bitarray_t *ba) +{ + tor_free(ba); +} +#define bitarray_free(ba) FREE_AND_NULL(bitarray_t, bitarray_free_, (ba)) + +/** Set the <b>bit</b>th bit in <b>b</b> to 1. */ +static inline void +bitarray_set(bitarray_t *b, int bit) +{ + b[bit >> BITARRAY_SHIFT] |= (1u << (bit & BITARRAY_MASK)); +} +/** Set the <b>bit</b>th bit in <b>b</b> to 0. */ +static inline void +bitarray_clear(bitarray_t *b, int bit) +{ + b[bit >> BITARRAY_SHIFT] &= ~ (1u << (bit & BITARRAY_MASK)); +} +/** Return true iff <b>bit</b>th bit in <b>b</b> is nonzero. NOTE: does + * not necessarily return 1 on true. */ +static inline unsigned int +bitarray_is_set(bitarray_t *b, int bit) +{ + return b[bit >> BITARRAY_SHIFT] & (1u << (bit & BITARRAY_MASK)); +} + +#endif /* !defined(TOR_CONTAINER_H) */ diff --git a/src/lib/container/bloomfilt.c b/src/lib/container/bloomfilt.c new file mode 100644 index 0000000000..9aa9b1ee56 --- /dev/null +++ b/src/lib/container/bloomfilt.c @@ -0,0 +1,113 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file bloomfilt.c + * \brief Uses bitarray_t to implement a bloom filter. + **/ + +#include <stdlib.h> + +#include "lib/malloc/malloc.h" +#include "lib/container/bloomfilt.h" +#include "lib/intmath/bits.h" +#include "lib/log/util_bug.h" +#include "siphash.h" + +/** How many bloom-filter bits we set per address. This is twice the + * BLOOMFILT_N_HASHES value, since we split the siphash output into two 32-bit + * values. */ +#define N_BITS_PER_ITEM (BLOOMFILT_N_HASHES * 2) + +struct bloomfilt_t { + /** siphash keys to make BLOOMFILT_N_HASHES independent hashes for each + * items. */ + struct sipkey key[BLOOMFILT_N_HASHES]; + bloomfilt_hash_fn hashfn; /**< Function used to generate hashes */ + uint32_t mask; /**< One less than the number of bits in <b>ba</b>; always + * one less than a power of two. */ + bitarray_t *ba; /**< A bit array to implement the Bloom filter. */ +}; + +#define BIT(set, n) ((n) & (set)->mask) + +/** Add the element <b>item</b> to <b>set</b>. */ +void +bloomfilt_add(bloomfilt_t *set, + const void *item) +{ + int i; + for (i = 0; i < BLOOMFILT_N_HASHES; ++i) { + uint64_t h = set->hashfn(&set->key[i], item); + uint32_t high_bits = (uint32_t)(h >> 32); + uint32_t low_bits = (uint32_t)(h); + bitarray_set(set->ba, BIT(set, high_bits)); + bitarray_set(set->ba, BIT(set, low_bits)); + } +} + +/** If <b>item</b> is in <b>set</b>, return nonzero. Otherwise, + * <em>probably</em> return zero. */ +int +bloomfilt_probably_contains(const bloomfilt_t *set, + const void *item) +{ + int i, matches = 0; + for (i = 0; i < BLOOMFILT_N_HASHES; ++i) { + uint64_t h = set->hashfn(&set->key[i], item); + uint32_t high_bits = (uint32_t)(h >> 32); + uint32_t low_bits = (uint32_t)(h); + // Note that !! is necessary here, since bitarray_is_set does not + // necessarily return 1 on true. + matches += !! bitarray_is_set(set->ba, BIT(set, high_bits)); + matches += !! bitarray_is_set(set->ba, BIT(set, low_bits)); + } + return matches == N_BITS_PER_ITEM; +} + +/** Return a newly allocated bloomfilt_t, optimized to hold a total of + * <b>max_elements</b> elements with a reasonably low false positive weight. + * + * Uses the siphash-based function <b>hashfn</b> to compute hard-to-collide + * functions of the items, and the key material <b>random_key</b> to + * key the hash. There must be BLOOMFILT_KEY_LEN bytes in the supplied key. + **/ +bloomfilt_t * +bloomfilt_new(int max_elements, + bloomfilt_hash_fn hashfn, + const uint8_t *random_key) +{ + /* The probability of false positives is about P=(1 - exp(-kn/m))^k, where k + * is the number of hash functions per entry, m is the bits in the array, + * and n is the number of elements inserted. For us, k==4, n<=max_elements, + * and m==n_bits= approximately max_elements*32. This gives + * P<(1-exp(-4*n/(32*n)))^4 == (1-exp(1/-8))^4 == .00019 + * + * It would be more optimal in space vs false positives to get this false + * positive rate by going for k==13, and m==18.5n, but we also want to + * conserve CPU, and k==13 is pretty big. + */ + int n_bits = 1u << (tor_log2(max_elements)+5); + bloomfilt_t *r = tor_malloc(sizeof(bloomfilt_t)); + r->mask = n_bits - 1; + r->ba = bitarray_init_zero(n_bits); + + tor_assert(sizeof(r->key) == BLOOMFILT_KEY_LEN); + memcpy(r->key, random_key, sizeof(r->key)); + + r->hashfn = hashfn; + + return r; +} + +/** Free all storage held in <b>set</b>. */ +void +bloomfilt_free_(bloomfilt_t *set) +{ + if (!set) + return; + bitarray_free(set->ba); + tor_free(set); +} diff --git a/src/lib/container/bloomfilt.h b/src/lib/container/bloomfilt.h new file mode 100644 index 0000000000..0ce18bd3ec --- /dev/null +++ b/src/lib/container/bloomfilt.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_BLOOMFILT_H +#define TOR_BLOOMFILT_H + +/** + * \file bloomfilt.h + * + * \brief Header for bloomfilt.c + **/ + +#include "orconfig.h" +#include "lib/cc/torint.h" +#include "lib/container/bitarray.h" + +/** A set of elements, implemented as a Bloom filter. */ +typedef struct bloomfilt_t bloomfilt_t; + +/** How many 64-bit siphash values to extract per item. */ +#define BLOOMFILT_N_HASHES 2 + +/** How much key material do we need to randomize hashes? */ +#define BLOOMFILT_KEY_LEN (BLOOMFILT_N_HASHES * 16) + +struct sipkey; +typedef uint64_t (*bloomfilt_hash_fn)(const struct sipkey *key, + const void *item); + +void bloomfilt_add(bloomfilt_t *set, const void *item); +int bloomfilt_probably_contains(const bloomfilt_t *set, const void *item); + +bloomfilt_t *bloomfilt_new(int max_elements, + bloomfilt_hash_fn hashfn, + const uint8_t *random_key); +void bloomfilt_free_(bloomfilt_t* set); +#define bloomfilt_free(set) FREE_AND_NULL(bloomfilt_t, bloomfilt_free_, (set)) + +#endif /* !defined(TOR_BLOOMFILT_H) */ diff --git a/src/lib/container/buffers.c b/src/lib/container/buffers.c new file mode 100644 index 0000000000..bda4245049 --- /dev/null +++ b/src/lib/container/buffers.c @@ -0,0 +1,925 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file buffers.c + * \brief Implements a generic buffer interface. + * + * A buf_t is a (fairly) opaque byte-oriented FIFO that can read to or flush + * from memory, sockets, file descriptors, TLS connections, or another buf_t. + * Buffers are implemented as linked lists of memory chunks. + * + * All socket-backed and TLS-based connection_t objects have a pair of + * buffers: one for incoming data, and one for outcoming data. These are fed + * and drained from functions in connection.c, trigged by events that are + * monitored in main.c. + * + * This module only handles the buffer implementation itself. To use a buffer + * with the network, a compressor, or a TLS connection, see the other buffer_* + * modules. + **/ + +#define BUFFERS_PRIVATE +#include "orconfig.h" +#include <stddef.h> +#include "lib/container/buffers.h" +#include "lib/cc/torint.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/ctime/di_ops.h" +#include "lib/malloc/malloc.h" +#include "lib/string/printf.h" +#include "lib/time/compat_time.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <stdlib.h> +#include <string.h> + +//#define PARANOIA + +#ifdef PARANOIA +/** Helper: If PARANOIA is defined, assert that the buffer in local variable + * <b>buf</b> is well-formed. */ +#define check() STMT_BEGIN buf_assert_ok(buf); STMT_END +#else +#define check() STMT_NIL +#endif /* defined(PARANOIA) */ + +/* Implementation notes: + * + * After flirting with memmove, and dallying with ring-buffers, we're finally + * getting up to speed with the 1970s and implementing buffers as a linked + * list of small chunks. Each buffer has such a list; data is removed from + * the head of the list, and added at the tail. The list is singly linked, + * and the buffer keeps a pointer to the head and the tail. + * + * Every chunk, except the tail, contains at least one byte of data. Data in + * each chunk is contiguous. + * + * When you need to treat the first N characters on a buffer as a contiguous + * string, use the buf_pullup function to make them so. Don't do this more + * than necessary. + * + * The major free Unix kernels have handled buffers like this since, like, + * forever. + */ + +/* Chunk manipulation functions */ + +#define CHUNK_HEADER_LEN offsetof(chunk_t, mem[0]) + +/* We leave this many NUL bytes at the end of the buffer. */ +#ifdef DISABLE_MEMORY_SENTINELS +#define SENTINEL_LEN 0 +#else +#define SENTINEL_LEN 4 +#endif + +/* Header size plus NUL bytes at the end */ +#define CHUNK_OVERHEAD (CHUNK_HEADER_LEN + SENTINEL_LEN) + +/** Return the number of bytes needed to allocate a chunk to hold + * <b>memlen</b> bytes. */ +#define CHUNK_ALLOC_SIZE(memlen) (CHUNK_OVERHEAD + (memlen)) +/** Return the number of usable bytes in a chunk allocated with + * malloc(<b>memlen</b>). */ +#define CHUNK_SIZE_WITH_ALLOC(memlen) ((memlen) - CHUNK_OVERHEAD) + +#define DEBUG_SENTINEL + +#if defined(DEBUG_SENTINEL) && !defined(DISABLE_MEMORY_SENTINELS) +#define DBG_S(s) s +#else +#define DBG_S(s) (void)0 +#endif + +#ifdef DISABLE_MEMORY_SENTINELS +#define CHUNK_SET_SENTINEL(chunk, alloclen) STMT_NIL +#else +#define CHUNK_SET_SENTINEL(chunk, alloclen) do { \ + uint8_t *a = (uint8_t*) &(chunk)->mem[(chunk)->memlen]; \ + DBG_S(uint8_t *b = &((uint8_t*)(chunk))[(alloclen)-SENTINEL_LEN]); \ + DBG_S(tor_assert(a == b)); \ + memset(a,0,SENTINEL_LEN); \ + } while (0) +#endif /* defined(DISABLE_MEMORY_SENTINELS) */ + +/** Move all bytes stored in <b>chunk</b> to the front of <b>chunk</b>->mem, + * to free up space at the end. */ +static inline void +chunk_repack(chunk_t *chunk) +{ + if (chunk->datalen && chunk->data != &chunk->mem[0]) { + memmove(chunk->mem, chunk->data, chunk->datalen); + } + chunk->data = &chunk->mem[0]; +} + +/** Keep track of total size of allocated chunks for consistency asserts */ +static size_t total_bytes_allocated_in_chunks = 0; +static void +buf_chunk_free_unchecked(chunk_t *chunk) +{ + if (!chunk) + return; +#ifdef DEBUG_CHUNK_ALLOC + tor_assert(CHUNK_ALLOC_SIZE(chunk->memlen) == chunk->DBG_alloc); +#endif + tor_assert(total_bytes_allocated_in_chunks >= + CHUNK_ALLOC_SIZE(chunk->memlen)); + total_bytes_allocated_in_chunks -= CHUNK_ALLOC_SIZE(chunk->memlen); + tor_free(chunk); +} +static inline chunk_t * +chunk_new_with_alloc_size(size_t alloc) +{ + chunk_t *ch; + ch = tor_malloc(alloc); + ch->next = NULL; + ch->datalen = 0; +#ifdef DEBUG_CHUNK_ALLOC + ch->DBG_alloc = alloc; +#endif + ch->memlen = CHUNK_SIZE_WITH_ALLOC(alloc); + total_bytes_allocated_in_chunks += alloc; + ch->data = &ch->mem[0]; + CHUNK_SET_SENTINEL(ch, alloc); + return ch; +} + +/** Expand <b>chunk</b> until it can hold <b>sz</b> bytes, and return a + * new pointer to <b>chunk</b>. Old pointers are no longer valid. */ +static inline chunk_t * +chunk_grow(chunk_t *chunk, size_t sz) +{ + off_t offset; + const size_t memlen_orig = chunk->memlen; + const size_t orig_alloc = CHUNK_ALLOC_SIZE(memlen_orig); + const size_t new_alloc = CHUNK_ALLOC_SIZE(sz); + tor_assert(sz > chunk->memlen); + offset = chunk->data - chunk->mem; + chunk = tor_realloc(chunk, new_alloc); + chunk->memlen = sz; + chunk->data = chunk->mem + offset; +#ifdef DEBUG_CHUNK_ALLOC + tor_assert(chunk->DBG_alloc == orig_alloc); + chunk->DBG_alloc = new_alloc; +#endif + total_bytes_allocated_in_chunks += new_alloc - orig_alloc; + CHUNK_SET_SENTINEL(chunk, new_alloc); + return chunk; +} + +/** Every chunk should take up at least this many bytes. */ +#define MIN_CHUNK_ALLOC 256 +/** No chunk should take up more than this many bytes. */ +#define MAX_CHUNK_ALLOC 65536 + +/** Return the allocation size we'd like to use to hold <b>target</b> + * bytes. */ +size_t +buf_preferred_chunk_size(size_t target) +{ + tor_assert(target <= SIZE_T_CEILING - CHUNK_OVERHEAD); + if (CHUNK_ALLOC_SIZE(target) >= MAX_CHUNK_ALLOC) + return CHUNK_ALLOC_SIZE(target); + size_t sz = MIN_CHUNK_ALLOC; + while (CHUNK_SIZE_WITH_ALLOC(sz) < target) { + sz <<= 1; + } + return sz; +} + +/** Collapse data from the first N chunks from <b>buf</b> into buf->head, + * growing it as necessary, until buf->head has the first <b>bytes</b> bytes + * of data from the buffer, or until buf->head has all the data in <b>buf</b>. + * + * Set *<b>head_out</b> to point to the first byte of available data, and + * *<b>len_out</b> to the number of bytes of data available at + * *<b>head_out</b>. Note that *<b>len_out</b> may be more or less than + * <b>bytes</b>, depending on the number of bytes available. + */ +void +buf_pullup(buf_t *buf, size_t bytes, const char **head_out, size_t *len_out) +{ + chunk_t *dest, *src; + size_t capacity; + if (!buf->head) { + *head_out = NULL; + *len_out = 0; + return; + } + + check(); + if (buf->datalen < bytes) + bytes = buf->datalen; + + capacity = bytes; + if (buf->head->datalen >= bytes) { + *head_out = buf->head->data; + *len_out = buf->head->datalen; + return; + } + + if (buf->head->memlen >= capacity) { + /* We don't need to grow the first chunk, but we might need to repack it.*/ + size_t needed = capacity - buf->head->datalen; + if (CHUNK_REMAINING_CAPACITY(buf->head) < needed) + chunk_repack(buf->head); + tor_assert(CHUNK_REMAINING_CAPACITY(buf->head) >= needed); + } else { + chunk_t *newhead; + size_t newsize; + /* We need to grow the chunk. */ + chunk_repack(buf->head); + newsize = CHUNK_SIZE_WITH_ALLOC(buf_preferred_chunk_size(capacity)); + newhead = chunk_grow(buf->head, newsize); + tor_assert(newhead->memlen >= capacity); + if (newhead != buf->head) { + if (buf->tail == buf->head) + buf->tail = newhead; + buf->head = newhead; + } + } + + dest = buf->head; + while (dest->datalen < bytes) { + size_t n = bytes - dest->datalen; + src = dest->next; + tor_assert(src); + if (n >= src->datalen) { + memcpy(CHUNK_WRITE_PTR(dest), src->data, src->datalen); + dest->datalen += src->datalen; + dest->next = src->next; + if (buf->tail == src) + buf->tail = dest; + buf_chunk_free_unchecked(src); + } else { + memcpy(CHUNK_WRITE_PTR(dest), src->data, n); + dest->datalen += n; + src->data += n; + src->datalen -= n; + tor_assert(dest->datalen == bytes); + } + } + + check(); + *head_out = buf->head->data; + *len_out = buf->head->datalen; +} + +#ifdef TOR_UNIT_TESTS +/* Write sz bytes from cp into a newly allocated buffer buf. + * Returns NULL when passed a NULL cp or zero sz. + * Asserts on failure: only for use in unit tests. + * buf must be freed using buf_free(). */ +buf_t * +buf_new_with_data(const char *cp, size_t sz) +{ + /* Validate arguments */ + if (!cp || sz <= 0) { + return NULL; + } + + tor_assert(sz < SSIZE_T_CEILING); + + /* Allocate a buffer */ + buf_t *buf = buf_new_with_capacity(sz); + tor_assert(buf); + buf_assert_ok(buf); + tor_assert(!buf->head); + + /* Allocate a chunk that is sz bytes long */ + buf->head = chunk_new_with_alloc_size(CHUNK_ALLOC_SIZE(sz)); + buf->tail = buf->head; + tor_assert(buf->head); + buf_assert_ok(buf); + tor_assert(buf_allocation(buf) >= sz); + + /* Copy the data and size the buffers */ + tor_assert(sz <= buf_slack(buf)); + tor_assert(sz <= CHUNK_REMAINING_CAPACITY(buf->head)); + memcpy(&buf->head->mem[0], cp, sz); + buf->datalen = sz; + buf->head->datalen = sz; + buf->head->data = &buf->head->mem[0]; + buf_assert_ok(buf); + + /* Make sure everything is large enough */ + tor_assert(buf_allocation(buf) >= sz); + tor_assert(buf_allocation(buf) >= buf_datalen(buf) + buf_slack(buf)); + /* Does the buffer implementation allocate more than the requested size? + * (for example, by rounding up). If so, these checks will fail. */ + tor_assert(buf_datalen(buf) == sz); + tor_assert(buf_slack(buf) == 0); + + return buf; +} +#endif /* defined(TOR_UNIT_TESTS) */ + +/** Remove the first <b>n</b> bytes from buf. */ +void +buf_drain(buf_t *buf, size_t n) +{ + tor_assert(buf->datalen >= n); + while (n) { + tor_assert(buf->head); + if (buf->head->datalen > n) { + buf->head->datalen -= n; + buf->head->data += n; + buf->datalen -= n; + return; + } else { + chunk_t *victim = buf->head; + n -= victim->datalen; + buf->datalen -= victim->datalen; + buf->head = victim->next; + if (buf->tail == victim) + buf->tail = NULL; + buf_chunk_free_unchecked(victim); + } + } + check(); +} + +/** Create and return a new buf with default chunk capacity <b>size</b>. + */ +buf_t * +buf_new_with_capacity(size_t size) +{ + buf_t *b = buf_new(); + b->default_chunk_size = buf_preferred_chunk_size(size); + return b; +} + +/** Allocate and return a new buffer with default capacity. */ +buf_t * +buf_new(void) +{ + buf_t *buf = tor_malloc_zero(sizeof(buf_t)); + buf->magic = BUFFER_MAGIC; + buf->default_chunk_size = 4096; + return buf; +} + +size_t +buf_get_default_chunk_size(const buf_t *buf) +{ + return buf->default_chunk_size; +} + +/** Remove all data from <b>buf</b>. */ +void +buf_clear(buf_t *buf) +{ + chunk_t *chunk, *next; + buf->datalen = 0; + for (chunk = buf->head; chunk; chunk = next) { + next = chunk->next; + buf_chunk_free_unchecked(chunk); + } + buf->head = buf->tail = NULL; +} + +/** Return the number of bytes stored in <b>buf</b> */ +MOCK_IMPL(size_t, +buf_datalen, (const buf_t *buf)) +{ + return buf->datalen; +} + +/** Return the total length of all chunks used in <b>buf</b>. */ +size_t +buf_allocation(const buf_t *buf) +{ + size_t total = 0; + const chunk_t *chunk; + for (chunk = buf->head; chunk; chunk = chunk->next) { + total += CHUNK_ALLOC_SIZE(chunk->memlen); + } + return total; +} + +/** Return the number of bytes that can be added to <b>buf</b> without + * performing any additional allocation. */ +size_t +buf_slack(const buf_t *buf) +{ + if (!buf->tail) + return 0; + else + return CHUNK_REMAINING_CAPACITY(buf->tail); +} + +/** Release storage held by <b>buf</b>. */ +void +buf_free_(buf_t *buf) +{ + if (!buf) + return; + + buf_clear(buf); + buf->magic = 0xdeadbeef; + tor_free(buf); +} + +/** Return a new copy of <b>in_chunk</b> */ +static chunk_t * +chunk_copy(const chunk_t *in_chunk) +{ + chunk_t *newch = tor_memdup(in_chunk, CHUNK_ALLOC_SIZE(in_chunk->memlen)); + total_bytes_allocated_in_chunks += CHUNK_ALLOC_SIZE(in_chunk->memlen); +#ifdef DEBUG_CHUNK_ALLOC + newch->DBG_alloc = CHUNK_ALLOC_SIZE(in_chunk->memlen); +#endif + newch->next = NULL; + if (in_chunk->data) { + off_t offset = in_chunk->data - in_chunk->mem; + newch->data = newch->mem + offset; + } + return newch; +} + +/** Return a new copy of <b>buf</b> */ +buf_t * +buf_copy(const buf_t *buf) +{ + chunk_t *ch; + buf_t *out = buf_new(); + out->default_chunk_size = buf->default_chunk_size; + for (ch = buf->head; ch; ch = ch->next) { + chunk_t *newch = chunk_copy(ch); + if (out->tail) { + out->tail->next = newch; + out->tail = newch; + } else { + out->head = out->tail = newch; + } + } + out->datalen = buf->datalen; + return out; +} + +/** Append a new chunk with enough capacity to hold <b>capacity</b> bytes to + * the tail of <b>buf</b>. If <b>capped</b>, don't allocate a chunk bigger + * than MAX_CHUNK_ALLOC. */ +chunk_t * +buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped) +{ + chunk_t *chunk; + + if (CHUNK_ALLOC_SIZE(capacity) < buf->default_chunk_size) { + chunk = chunk_new_with_alloc_size(buf->default_chunk_size); + } else if (capped && CHUNK_ALLOC_SIZE(capacity) > MAX_CHUNK_ALLOC) { + chunk = chunk_new_with_alloc_size(MAX_CHUNK_ALLOC); + } else { + chunk = chunk_new_with_alloc_size(buf_preferred_chunk_size(capacity)); + } + + chunk->inserted_time = monotime_coarse_get_stamp(); + + if (buf->tail) { + tor_assert(buf->head); + buf->tail->next = chunk; + buf->tail = chunk; + } else { + tor_assert(!buf->head); + buf->head = buf->tail = chunk; + } + check(); + return chunk; +} + +/** Return the age of the oldest chunk in the buffer <b>buf</b>, in + * timestamp units. Requires the current monotonic timestamp as its + * input <b>now</b>. + */ +uint32_t +buf_get_oldest_chunk_timestamp(const buf_t *buf, uint32_t now) +{ + if (buf->head) { + return now - buf->head->inserted_time; + } else { + return 0; + } +} + +size_t +buf_get_total_allocation(void) +{ + return total_bytes_allocated_in_chunks; +} + +/** Append <b>string_len</b> bytes from <b>string</b> to the end of + * <b>buf</b>. + * + * Return the new length of the buffer on success, -1 on failure. + */ +int +buf_add(buf_t *buf, const char *string, size_t string_len) +{ + if (!string_len) + return (int)buf->datalen; + check(); + + if (BUG(buf->datalen >= INT_MAX)) + return -1; + if (BUG(buf->datalen >= INT_MAX - string_len)) + return -1; + + while (string_len) { + size_t copy; + if (!buf->tail || !CHUNK_REMAINING_CAPACITY(buf->tail)) + buf_add_chunk_with_capacity(buf, string_len, 1); + + copy = CHUNK_REMAINING_CAPACITY(buf->tail); + if (copy > string_len) + copy = string_len; + memcpy(CHUNK_WRITE_PTR(buf->tail), string, copy); + string_len -= copy; + string += copy; + buf->datalen += copy; + buf->tail->datalen += copy; + } + + check(); + tor_assert(buf->datalen < INT_MAX); + return (int)buf->datalen; +} + +/** Add a nul-terminated <b>string</b> to <b>buf</b>, not including the + * terminating NUL. */ +void +buf_add_string(buf_t *buf, const char *string) +{ + buf_add(buf, string, strlen(string)); +} + +/** As tor_snprintf, but write the results into a buf_t */ +void +buf_add_printf(buf_t *buf, const char *format, ...) +{ + va_list ap; + va_start(ap,format); + buf_add_vprintf(buf, format, ap); + va_end(ap); +} + +/** As tor_vsnprintf, but write the results into a buf_t. */ +void +buf_add_vprintf(buf_t *buf, const char *format, va_list args) +{ + /* XXXX Faster implementations are easy enough, but let's optimize later */ + char *tmp; + tor_vasprintf(&tmp, format, args); + buf_add(buf, tmp, strlen(tmp)); + tor_free(tmp); +} + +/** Return a heap-allocated string containing the contents of <b>buf</b>, plus + * a NUL byte. If <b>sz_out</b> is provided, set *<b>sz_out</b> to the length + * of the returned string, not including the terminating NUL. */ +char * +buf_extract(buf_t *buf, size_t *sz_out) +{ + tor_assert(buf); + + size_t sz = buf_datalen(buf); + char *result; + result = tor_malloc(sz+1); + buf_peek(buf, result, sz); + result[sz] = 0; + if (sz_out) + *sz_out = sz; + return result; +} + +/** Helper: copy the first <b>string_len</b> bytes from <b>buf</b> + * onto <b>string</b>. + */ +void +buf_peek(const buf_t *buf, char *string, size_t string_len) +{ + chunk_t *chunk; + + tor_assert(string); + /* make sure we don't ask for too much */ + tor_assert(string_len <= buf->datalen); + /* buf_assert_ok(buf); */ + + chunk = buf->head; + while (string_len) { + size_t copy = string_len; + tor_assert(chunk); + if (chunk->datalen < copy) + copy = chunk->datalen; + memcpy(string, chunk->data, copy); + string_len -= copy; + string += copy; + chunk = chunk->next; + } +} + +/** Remove <b>string_len</b> bytes from the front of <b>buf</b>, and store + * them into <b>string</b>. Return the new buffer size. <b>string_len</b> + * must be \<= the number of bytes on the buffer. + */ +int +buf_get_bytes(buf_t *buf, char *string, size_t string_len) +{ + /* There must be string_len bytes in buf; write them onto string, + * then memmove buf back (that is, remove them from buf). + * + * Return the number of bytes still on the buffer. */ + + check(); + buf_peek(buf, string, string_len); + buf_drain(buf, string_len); + check(); + tor_assert(buf->datalen < INT_MAX); + return (int)buf->datalen; +} + +/** Move up to *<b>buf_flushlen</b> bytes from <b>buf_in</b> to + * <b>buf_out</b>, and modify *<b>buf_flushlen</b> appropriately. + * Return the number of bytes actually copied. + */ +int +buf_move_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen) +{ + /* We can do way better here, but this doesn't turn up in any profiles. */ + char b[4096]; + size_t cp, len; + + if (BUG(buf_out->datalen >= INT_MAX)) + return -1; + if (BUG(buf_out->datalen >= INT_MAX - *buf_flushlen)) + return -1; + + len = *buf_flushlen; + if (len > buf_in->datalen) + len = buf_in->datalen; + + cp = len; /* Remember the number of bytes we intend to copy. */ + tor_assert(cp < INT_MAX); + while (len) { + /* This isn't the most efficient implementation one could imagine, since + * it does two copies instead of 1, but I kinda doubt that this will be + * critical path. */ + size_t n = len > sizeof(b) ? sizeof(b) : len; + buf_get_bytes(buf_in, b, n); + buf_add(buf_out, b, n); + len -= n; + } + *buf_flushlen -= cp; + return (int)cp; +} + +/** Moves all data from <b>buf_in</b> to <b>buf_out</b>, without copying. + */ +void +buf_move_all(buf_t *buf_out, buf_t *buf_in) +{ + tor_assert(buf_out); + if (!buf_in) + return; + + if (buf_out->head == NULL) { + buf_out->head = buf_in->head; + buf_out->tail = buf_in->tail; + } else { + buf_out->tail->next = buf_in->head; + buf_out->tail = buf_in->tail; + } + + buf_out->datalen += buf_in->datalen; + buf_in->head = buf_in->tail = NULL; + buf_in->datalen = 0; +} + +/** Internal structure: represents a position in a buffer. */ +typedef struct buf_pos_t { + const chunk_t *chunk; /**< Which chunk are we pointing to? */ + int pos;/**< Which character inside the chunk's data are we pointing to? */ + size_t chunk_pos; /**< Total length of all previous chunks. */ +} buf_pos_t; + +/** Initialize <b>out</b> to point to the first character of <b>buf</b>.*/ +static void +buf_pos_init(const buf_t *buf, buf_pos_t *out) +{ + out->chunk = buf->head; + out->pos = 0; + out->chunk_pos = 0; +} + +/** Advance <b>out</b> to the first appearance of <b>ch</b> at the current + * position of <b>out</b>, or later. Return -1 if no instances are found; + * otherwise returns the absolute position of the character. */ +static off_t +buf_find_pos_of_char(char ch, buf_pos_t *out) +{ + const chunk_t *chunk; + int pos; + tor_assert(out); + if (out->chunk) { + if (out->chunk->datalen) { + tor_assert(out->pos < (off_t)out->chunk->datalen); + } else { + tor_assert(out->pos == 0); + } + } + pos = out->pos; + for (chunk = out->chunk; chunk; chunk = chunk->next) { + char *cp = memchr(chunk->data+pos, ch, chunk->datalen - pos); + if (cp) { + out->chunk = chunk; + tor_assert(cp - chunk->data < INT_MAX); + out->pos = (int)(cp - chunk->data); + return out->chunk_pos + out->pos; + } else { + out->chunk_pos += chunk->datalen; + pos = 0; + } + } + return -1; +} + +/** Advance <b>pos</b> by a single character, if there are any more characters + * in the buffer. Returns 0 on success, -1 on failure. */ +static inline int +buf_pos_inc(buf_pos_t *pos) +{ + ++pos->pos; + if (pos->pos == (off_t)pos->chunk->datalen) { + if (!pos->chunk->next) + return -1; + pos->chunk_pos += pos->chunk->datalen; + pos->chunk = pos->chunk->next; + pos->pos = 0; + } + return 0; +} + +/** Return true iff the <b>n</b>-character string in <b>s</b> appears + * (verbatim) at <b>pos</b>. */ +static int +buf_matches_at_pos(const buf_pos_t *pos, const char *s, size_t n) +{ + buf_pos_t p; + if (!n) + return 1; + + memcpy(&p, pos, sizeof(p)); + + while (1) { + char ch = p.chunk->data[p.pos]; + if (ch != *s) + return 0; + ++s; + /* If we're out of characters that don't match, we match. Check this + * _before_ we test incrementing pos, in case we're at the end of the + * string. */ + if (--n == 0) + return 1; + if (buf_pos_inc(&p)<0) + return 0; + } +} + +/** Return the first position in <b>buf</b> at which the <b>n</b>-character + * string <b>s</b> occurs, or -1 if it does not occur. */ +int +buf_find_string_offset(const buf_t *buf, const char *s, size_t n) +{ + buf_pos_t pos; + buf_pos_init(buf, &pos); + while (buf_find_pos_of_char(*s, &pos) >= 0) { + if (buf_matches_at_pos(&pos, s, n)) { + tor_assert(pos.chunk_pos + pos.pos < INT_MAX); + return (int)(pos.chunk_pos + pos.pos); + } else { + if (buf_pos_inc(&pos)<0) + return -1; + } + } + return -1; +} + +/** Return 1 iff <b>buf</b> starts with <b>cmd</b>. <b>cmd</b> must be a null + * terminated string, of no more than PEEK_BUF_STARTSWITH_MAX bytes. */ +int +buf_peek_startswith(const buf_t *buf, const char *cmd) +{ + char tmp[PEEK_BUF_STARTSWITH_MAX]; + size_t clen = strlen(cmd); + if (clen == 0) + return 1; + if (BUG(clen > sizeof(tmp))) + return 0; + if (buf->datalen < clen) + return 0; + buf_peek(buf, tmp, clen); + return fast_memeq(tmp, cmd, clen); +} + +/** Return the index within <b>buf</b> at which <b>ch</b> first appears, + * or -1 if <b>ch</b> does not appear on buf. */ +static off_t +buf_find_offset_of_char(buf_t *buf, char ch) +{ + chunk_t *chunk; + off_t offset = 0; + for (chunk = buf->head; chunk; chunk = chunk->next) { + char *cp = memchr(chunk->data, ch, chunk->datalen); + if (cp) + return offset + (cp - chunk->data); + else + offset += chunk->datalen; + } + return -1; +} + +/** Try to read a single LF-terminated line from <b>buf</b>, and write it + * (including the LF), NUL-terminated, into the *<b>data_len</b> byte buffer + * at <b>data_out</b>. Set *<b>data_len</b> to the number of bytes in the + * line, not counting the terminating NUL. Return 1 if we read a whole line, + * return 0 if we don't have a whole line yet, and return -1 if the line + * length exceeds *<b>data_len</b>. + */ +int +buf_get_line(buf_t *buf, char *data_out, size_t *data_len) +{ + size_t sz; + off_t offset; + + if (!buf->head) + return 0; + + offset = buf_find_offset_of_char(buf, '\n'); + if (offset < 0) + return 0; + sz = (size_t) offset; + if (sz+2 > *data_len) { + *data_len = sz + 2; + return -1; + } + buf_get_bytes(buf, data_out, sz+1); + data_out[sz+1] = '\0'; + *data_len = sz+1; + return 1; +} + +/** Set *<b>output</b> to contain a copy of the data in *<b>input</b> */ +int +buf_set_to_copy(buf_t **output, + const buf_t *input) +{ + if (*output) + buf_free(*output); + *output = buf_copy(input); + return 0; +} + +/** Log an error and exit if <b>buf</b> is corrupted. + */ +void +buf_assert_ok(buf_t *buf) +{ + tor_assert(buf); + tor_assert(buf->magic == BUFFER_MAGIC); + + if (! buf->head) { + tor_assert(!buf->tail); + tor_assert(buf->datalen == 0); + } else { + chunk_t *ch; + size_t total = 0; + tor_assert(buf->tail); + for (ch = buf->head; ch; ch = ch->next) { + total += ch->datalen; + tor_assert(ch->datalen <= ch->memlen); + tor_assert(ch->data >= &ch->mem[0]); + tor_assert(ch->data <= &ch->mem[0]+ch->memlen); + if (ch->data == &ch->mem[0]+ch->memlen) { + /* LCOV_EXCL_START */ + static int warned = 0; + if (! warned) { + log_warn(LD_BUG, "Invariant violation in buf.c related to #15083"); + warned = 1; + } + /* LCOV_EXCL_STOP */ + } + tor_assert(ch->data+ch->datalen <= &ch->mem[0] + ch->memlen); + if (!ch->next) + tor_assert(ch == buf->tail); + } + tor_assert(buf->datalen == total); + } +} diff --git a/src/lib/container/buffers.h b/src/lib/container/buffers.h new file mode 100644 index 0000000000..c103b93a82 --- /dev/null +++ b/src/lib/container/buffers.h @@ -0,0 +1,122 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file buffers.h + * + * \brief Header file for buffers.c. + **/ + +#ifndef TOR_BUFFERS_H +#define TOR_BUFFERS_H + +#include "lib/cc/compat_compiler.h" +#include "lib/cc/torint.h" +#include "lib/testsupport/testsupport.h" + +#include <stdarg.h> + +typedef struct buf_t buf_t; + +buf_t *buf_new(void); +buf_t *buf_new_with_capacity(size_t size); +size_t buf_get_default_chunk_size(const buf_t *buf); +void buf_free_(buf_t *buf); +#define buf_free(b) FREE_AND_NULL(buf_t, buf_free_, (b)) +void buf_clear(buf_t *buf); +buf_t *buf_copy(const buf_t *buf); + +MOCK_DECL(size_t, buf_datalen, (const buf_t *buf)); +size_t buf_allocation(const buf_t *buf); +size_t buf_slack(const buf_t *buf); + +uint32_t buf_get_oldest_chunk_timestamp(const buf_t *buf, uint32_t now); +size_t buf_get_total_allocation(void); + +int buf_add(buf_t *buf, const char *string, size_t string_len); +void buf_add_string(buf_t *buf, const char *string); +void buf_add_printf(buf_t *buf, const char *format, ...) + CHECK_PRINTF(2, 3); +void buf_add_vprintf(buf_t *buf, const char *format, va_list args) + CHECK_PRINTF(2, 0); +int buf_move_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen); +void buf_move_all(buf_t *buf_out, buf_t *buf_in); +void buf_peek(const buf_t *buf, char *string, size_t string_len); +void buf_drain(buf_t *buf, size_t n); +int buf_get_bytes(buf_t *buf, char *string, size_t string_len); +int buf_get_line(buf_t *buf, char *data_out, size_t *data_len); + +#define PEEK_BUF_STARTSWITH_MAX 16 +int buf_peek_startswith(const buf_t *buf, const char *cmd); + +int buf_set_to_copy(buf_t **output, + const buf_t *input); + +void buf_assert_ok(buf_t *buf); + +int buf_find_string_offset(const buf_t *buf, const char *s, size_t n); +void buf_pullup(buf_t *buf, size_t bytes, + const char **head_out, size_t *len_out); +char *buf_extract(buf_t *buf, size_t *sz_out); + +#ifdef BUFFERS_PRIVATE +#ifdef TOR_UNIT_TESTS +buf_t *buf_new_with_data(const char *cp, size_t sz); +#endif +size_t buf_preferred_chunk_size(size_t target); + +#define DEBUG_CHUNK_ALLOC +/** A single chunk on a buffer. */ +typedef struct chunk_t { + struct chunk_t *next; /**< The next chunk on the buffer. */ + size_t datalen; /**< The number of bytes stored in this chunk */ + size_t memlen; /**< The number of usable bytes of storage in <b>mem</b>. */ +#ifdef DEBUG_CHUNK_ALLOC + size_t DBG_alloc; +#endif + char *data; /**< A pointer to the first byte of data stored in <b>mem</b>. */ + uint32_t inserted_time; /**< Timestamp when this chunk was inserted. */ + char mem[FLEXIBLE_ARRAY_MEMBER]; /**< The actual memory used for storage in + * this chunk. */ +} chunk_t; + +/** Magic value for buf_t.magic, to catch pointer errors. */ +#define BUFFER_MAGIC 0xB0FFF312u +/** A resizeable buffer, optimized for reading and writing. */ +struct buf_t { + uint32_t magic; /**< Magic cookie for debugging: Must be set to + * BUFFER_MAGIC. */ + size_t datalen; /**< How many bytes is this buffer holding right now? */ + size_t default_chunk_size; /**< Don't allocate any chunks smaller than + * this for this buffer. */ + chunk_t *head; /**< First chunk in the list, or NULL for none. */ + chunk_t *tail; /**< Last chunk in the list, or NULL for none. */ +}; + +chunk_t *buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped); +/** If a read onto the end of a chunk would be smaller than this number, then + * just start a new chunk. */ +#define MIN_READ_LEN 8 + +/** Return the number of bytes that can be written onto <b>chunk</b> without + * running out of space. */ +static inline size_t +CHUNK_REMAINING_CAPACITY(const chunk_t *chunk) +{ + return (chunk->mem + chunk->memlen) - (chunk->data + chunk->datalen); +} + +/** Return the next character in <b>chunk</b> onto which data can be appended. + * If the chunk is full, this might be off the end of chunk->mem. */ +static inline char * +CHUNK_WRITE_PTR(chunk_t *chunk) +{ + return chunk->data + chunk->datalen; +} + +#endif /* defined(BUFFERS_PRIVATE) */ + +#endif /* !defined(TOR_BUFFERS_H) */ diff --git a/src/lib/container/handles.h b/src/lib/container/handles.h new file mode 100644 index 0000000000..ca7c94559e --- /dev/null +++ b/src/lib/container/handles.h @@ -0,0 +1,153 @@ +/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file handles.h + * \brief Macros for C weak-handle implementation. + * + * A 'handle' is a pointer to an object that is allowed to go away while + * the handle stays alive. When you dereference the handle, you might get + * the object, or you might get "NULL". + * + * Use this pattern when an object has a single obvious lifespan, so you don't + * want to use reference counting, but when other objects might need to refer + * to the first object without caring about its lifetime. + * + * To enable a type to have handles, add a HANDLE_ENTRY() field in its + * definition, as in: + * + * struct walrus { + * HANDLE_ENTRY(wlr, walrus); + * // ... + * }; + * + * And invoke HANDLE_DECL(wlr, walrus, [static]) to declare the handle + * manipulation functions (typically in a header): + * + * // opaque handle to walrus. + * typedef struct wlr_handle_t wlr_handle_t; + * + * // make a new handle + * struct wlr_handle_t *wlr_handle_new(struct walrus *); + * + * // release a handle + * void wlr_handle_free(wlr_handle_t *); + * + * // return the pointed-to walrus, or NULL. + * struct walrus *wlr_handle_get(wlr_handle_t *). + * + * // call this function when you're about to free the walrus; + * // it invalidates all handles. (IF YOU DON'T, YOU WILL HAVE + * // DANGLING REFERENCES) + * void wlr_handles_clear(struct walrus *); + * + * Finally, use HANDLE_IMPL() to define the above functions in some + * appropriate C file: HANDLE_IMPL(wlr, walrus, [static]) + * + **/ + +#ifndef TOR_HANDLE_H +#define TOR_HANDLE_H + +#include "orconfig.h" + +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" + +#define HANDLE_ENTRY(name, structname) \ + struct name ## _handle_head_t *handle_head + +#define HANDLE_DECL(name, structname, linkage) \ + typedef struct name ## _handle_t name ## _handle_t; \ + linkage name ## _handle_t *name ## _handle_new(struct structname *object); \ + linkage void name ## _handle_free_(name ## _handle_t *); \ + linkage struct structname *name ## _handle_get(name ## _handle_t *); \ + linkage void name ## _handles_clear(struct structname *object); + +/* + * Implementation notes: there are lots of possible implementations here. We + * could keep a linked list of handles, each with a backpointer to the object, + * and set all of their backpointers to NULL when the object is freed. Or we + * could have the clear function invalidate the object, but not actually let + * the object get freed until the all the handles went away. We could even + * have a hash-table mapping unique identifiers to objects, and have each + * handle be a copy of the unique identifier. (We'll want to build that last + * one eventually if we want cross-process handles.) + * + * But instead we're opting for a single independent 'head' that knows how + * many handles there are, and where the object is (or isn't). This makes + * all of our functions O(1), and most as fast as a single pointer access. + * + * The handles themselves are opaque structures holding a pointer to the head. + * We could instead have each foo_handle_t* be identical to foo_handle_head_t + * *, and save some allocations ... but doing so would make handle leaks + * harder to debug. As it stands, every handle leak is a memory leak, and + * existing memory debugging tools should help with those. We can revisit + * this decision if handles are too slow. + */ + +#define HANDLE_IMPL(name, structname, linkage) \ + /* The 'head' object for a handle-accessible type. This object */ \ + /* persists for as long as the object, or any handles, exist. */ \ + typedef struct name ## _handle_head_t { \ + struct structname *object; /* pointed-to object, or NULL */ \ + unsigned int references; /* number of existing handles */ \ + } name ## _handle_head_t; \ + \ + struct name ## _handle_t { \ + struct name ## _handle_head_t *head; /* reference to the 'head'. */ \ + }; \ + \ + linkage struct name ## _handle_t * \ + name ## _handle_new(struct structname *object) \ + { \ + tor_assert(object); \ + name ## _handle_head_t *head = object->handle_head; \ + if (PREDICT_UNLIKELY(head == NULL)) { \ + head = object->handle_head = tor_malloc_zero(sizeof(*head)); \ + head->object = object; \ + } \ + name ## _handle_t *new_ref = tor_malloc_zero(sizeof(*new_ref)); \ + new_ref->head = head; \ + ++head->references; \ + return new_ref; \ + } \ + \ + linkage void \ + name ## _handle_free_(struct name ## _handle_t *ref) \ + { \ + if (! ref) return; \ + name ## _handle_head_t *head = ref->head; \ + tor_assert(head); \ + --head->references; \ + tor_free(ref); \ + if (head->object == NULL && head->references == 0) { \ + tor_free(head); \ + return; \ + } \ + } \ + \ + linkage struct structname * \ + name ## _handle_get(struct name ## _handle_t *ref) \ + { \ + tor_assert(ref); \ + name ## _handle_head_t *head = ref->head; \ + tor_assert(head); \ + return head->object; \ + } \ + \ + linkage void \ + name ## _handles_clear(struct structname *object) \ + { \ + tor_assert(object); \ + name ## _handle_head_t *head = object->handle_head; \ + if (! head) \ + return; \ + object->handle_head = NULL; \ + head->object = NULL; \ + if (head->references == 0) { \ + tor_free(head); \ + } \ + } + +#endif /* !defined(TOR_HANDLE_H) */ diff --git a/src/lib/container/include.am b/src/lib/container/include.am new file mode 100644 index 0000000000..e6492098b5 --- /dev/null +++ b/src/lib/container/include.am @@ -0,0 +1,27 @@ + +noinst_LIBRARIES += src/lib/libtor-container.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-container-testing.a +endif + +src_lib_libtor_container_a_SOURCES = \ + src/lib/container/bloomfilt.c \ + src/lib/container/buffers.c \ + src/lib/container/map.c \ + src/lib/container/order.c \ + src/lib/container/smartlist.c + +src_lib_libtor_container_testing_a_SOURCES = \ + $(src_lib_libtor_container_a_SOURCES) +src_lib_libtor_container_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_container_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/container/bitarray.h \ + src/lib/container/bloomfilt.h \ + src/lib/container/buffers.h \ + src/lib/container/handles.h \ + src/lib/container/map.h \ + src/lib/container/order.h \ + src/lib/container/smartlist.h diff --git a/src/lib/container/map.c b/src/lib/container/map.c new file mode 100644 index 0000000000..d213ad50bf --- /dev/null +++ b/src/lib/container/map.c @@ -0,0 +1,413 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file map.c + * + * \brief Hash-table implementations of a string-to-void* map, and of + * a digest-to-void* map. + **/ + +#include "lib/container/map.h" +#include "lib/ctime/di_ops.h" +#include "lib/defs/digest_sizes.h" +#include "lib/string/util_string.h" +#include "lib/malloc/malloc.h" + +#include "lib/log/util_bug.h" + +#include <stdlib.h> +#include <string.h> + +#include "ht.h" + +/** Helper: Declare an entry type and a map type to implement a mapping using + * ht.h. The map type will be called <b>maptype</b>. The key part of each + * entry is declared using the C declaration <b>keydecl</b>. All functions + * and types associated with the map get prefixed with <b>prefix</b> */ +#define DEFINE_MAP_STRUCTS(maptype, keydecl, prefix) \ + typedef struct prefix ## entry_t { \ + HT_ENTRY(prefix ## entry_t) node; \ + void *val; \ + keydecl; \ + } prefix ## entry_t; \ + struct maptype { \ + HT_HEAD(prefix ## impl, prefix ## entry_t) head; \ + } + +DEFINE_MAP_STRUCTS(strmap_t, char *key, strmap_); +DEFINE_MAP_STRUCTS(digestmap_t, char key[DIGEST_LEN], digestmap_); +DEFINE_MAP_STRUCTS(digest256map_t, uint8_t key[DIGEST256_LEN], digest256map_); + +/** Helper: compare strmap_entry_t objects by key value. */ +static inline int +strmap_entries_eq(const strmap_entry_t *a, const strmap_entry_t *b) +{ + return !strcmp(a->key, b->key); +} + +/** Helper: return a hash value for a strmap_entry_t. */ +static inline unsigned int +strmap_entry_hash(const strmap_entry_t *a) +{ + return (unsigned) siphash24g(a->key, strlen(a->key)); +} + +/** Helper: compare digestmap_entry_t objects by key value. */ +static inline int +digestmap_entries_eq(const digestmap_entry_t *a, const digestmap_entry_t *b) +{ + return tor_memeq(a->key, b->key, DIGEST_LEN); +} + +/** Helper: return a hash value for a digest_map_t. */ +static inline unsigned int +digestmap_entry_hash(const digestmap_entry_t *a) +{ + return (unsigned) siphash24g(a->key, DIGEST_LEN); +} + +/** Helper: compare digestmap_entry_t objects by key value. */ +static inline int +digest256map_entries_eq(const digest256map_entry_t *a, + const digest256map_entry_t *b) +{ + return tor_memeq(a->key, b->key, DIGEST256_LEN); +} + +/** Helper: return a hash value for a digest_map_t. */ +static inline unsigned int +digest256map_entry_hash(const digest256map_entry_t *a) +{ + return (unsigned) siphash24g(a->key, DIGEST256_LEN); +} + +HT_PROTOTYPE(strmap_impl, strmap_entry_t, node, strmap_entry_hash, + strmap_entries_eq) +HT_GENERATE2(strmap_impl, strmap_entry_t, node, strmap_entry_hash, + strmap_entries_eq, 0.6, tor_reallocarray_, tor_free_) + +HT_PROTOTYPE(digestmap_impl, digestmap_entry_t, node, digestmap_entry_hash, + digestmap_entries_eq) +HT_GENERATE2(digestmap_impl, digestmap_entry_t, node, digestmap_entry_hash, + digestmap_entries_eq, 0.6, tor_reallocarray_, tor_free_) + +HT_PROTOTYPE(digest256map_impl, digest256map_entry_t, node, + digest256map_entry_hash, + digest256map_entries_eq) +HT_GENERATE2(digest256map_impl, digest256map_entry_t, node, + digest256map_entry_hash, + digest256map_entries_eq, 0.6, tor_reallocarray_, tor_free_) + +#define strmap_entry_free(ent) \ + FREE_AND_NULL(strmap_entry_t, strmap_entry_free_, (ent)) +#define digestmap_entry_free(ent) \ + FREE_AND_NULL(digestmap_entry_t, digestmap_entry_free_, (ent)) +#define digest256map_entry_free(ent) \ + FREE_AND_NULL(digest256map_entry_t, digest256map_entry_free_, (ent)) + +static inline void +strmap_entry_free_(strmap_entry_t *ent) +{ + tor_free(ent->key); + tor_free(ent); +} +static inline void +digestmap_entry_free_(digestmap_entry_t *ent) +{ + tor_free(ent); +} +static inline void +digest256map_entry_free_(digest256map_entry_t *ent) +{ + tor_free(ent); +} + +static inline void +strmap_assign_tmp_key(strmap_entry_t *ent, const char *key) +{ + ent->key = (char*)key; +} +static inline void +digestmap_assign_tmp_key(digestmap_entry_t *ent, const char *key) +{ + memcpy(ent->key, key, DIGEST_LEN); +} +static inline void +digest256map_assign_tmp_key(digest256map_entry_t *ent, const uint8_t *key) +{ + memcpy(ent->key, key, DIGEST256_LEN); +} +static inline void +strmap_assign_key(strmap_entry_t *ent, const char *key) +{ + ent->key = tor_strdup(key); +} +static inline void +digestmap_assign_key(digestmap_entry_t *ent, const char *key) +{ + memcpy(ent->key, key, DIGEST_LEN); +} +static inline void +digest256map_assign_key(digest256map_entry_t *ent, const uint8_t *key) +{ + memcpy(ent->key, key, DIGEST256_LEN); +} + +/** + * Macro: implement all the functions for a map that are declared in + * map.h by the DECLARE_MAP_FNS() macro. You must additionally define a + * prefix_entry_free_() function to free entries (and their keys), a + * prefix_assign_tmp_key() function to temporarily set a stack-allocated + * entry to hold a key, and a prefix_assign_key() function to set a + * heap-allocated entry to hold a key. + */ +#define IMPLEMENT_MAP_FNS(maptype, keytype, prefix) \ + /** Create and return a new empty map. */ \ + MOCK_IMPL(maptype *, \ + prefix##_new,(void)) \ + { \ + maptype *result; \ + result = tor_malloc(sizeof(maptype)); \ + HT_INIT(prefix##_impl, &result->head); \ + return result; \ + } \ + \ + /** Return the item from <b>map</b> whose key matches <b>key</b>, or \ + * NULL if no such value exists. */ \ + void * \ + prefix##_get(const maptype *map, const keytype key) \ + { \ + prefix ##_entry_t *resolve; \ + prefix ##_entry_t search; \ + tor_assert(map); \ + tor_assert(key); \ + prefix ##_assign_tmp_key(&search, key); \ + resolve = HT_FIND(prefix ##_impl, &map->head, &search); \ + if (resolve) { \ + return resolve->val; \ + } else { \ + return NULL; \ + } \ + } \ + \ + /** Add an entry to <b>map</b> mapping <b>key</b> to <b>val</b>; \ + * return the previous value, or NULL if no such value existed. */ \ + void * \ + prefix##_set(maptype *map, const keytype key, void *val) \ + { \ + prefix##_entry_t search; \ + void *oldval; \ + tor_assert(map); \ + tor_assert(key); \ + tor_assert(val); \ + prefix##_assign_tmp_key(&search, key); \ + /* We a lot of our time in this function, so the code below is */ \ + /* meant to optimize the check/alloc/set cycle by avoiding the two */\ + /* trips to the hash table that we would do in the unoptimized */ \ + /* version of this code. (Each of HT_INSERT and HT_FIND calls */ \ + /* HT_SET_HASH and HT_FIND_P.) */ \ + HT_FIND_OR_INSERT_(prefix##_impl, node, prefix##_entry_hash, \ + &(map->head), \ + prefix##_entry_t, &search, ptr, \ + { \ + /* we found an entry. */ \ + oldval = (*ptr)->val; \ + (*ptr)->val = val; \ + return oldval; \ + }, \ + { \ + /* We didn't find the entry. */ \ + prefix##_entry_t *newent = \ + tor_malloc_zero(sizeof(prefix##_entry_t)); \ + prefix##_assign_key(newent, key); \ + newent->val = val; \ + HT_FOI_INSERT_(node, &(map->head), \ + &search, newent, ptr); \ + return NULL; \ + }); \ + } \ + \ + /** Remove the value currently associated with <b>key</b> from the map. \ + * Return the value if one was set, or NULL if there was no entry for \ + * <b>key</b>. \ + * \ + * Note: you must free any storage associated with the returned value. \ + */ \ + void * \ + prefix##_remove(maptype *map, const keytype key) \ + { \ + prefix##_entry_t *resolve; \ + prefix##_entry_t search; \ + void *oldval; \ + tor_assert(map); \ + tor_assert(key); \ + prefix##_assign_tmp_key(&search, key); \ + resolve = HT_REMOVE(prefix##_impl, &map->head, &search); \ + if (resolve) { \ + oldval = resolve->val; \ + prefix##_entry_free(resolve); \ + return oldval; \ + } else { \ + return NULL; \ + } \ + } \ + \ + /** Return the number of elements in <b>map</b>. */ \ + int \ + prefix##_size(const maptype *map) \ + { \ + return HT_SIZE(&map->head); \ + } \ + \ + /** Return true iff <b>map</b> has no entries. */ \ + int \ + prefix##_isempty(const maptype *map) \ + { \ + return HT_EMPTY(&map->head); \ + } \ + \ + /** Assert that <b>map</b> is not corrupt. */ \ + void \ + prefix##_assert_ok(const maptype *map) \ + { \ + tor_assert(!prefix##_impl_HT_REP_IS_BAD_(&map->head)); \ + } \ + \ + /** Remove all entries from <b>map</b>, and deallocate storage for \ + * those entries. If free_val is provided, invoked it every value in \ + * <b>map</b>. */ \ + MOCK_IMPL(void, \ + prefix##_free_, (maptype *map, void (*free_val)(void*))) \ + { \ + prefix##_entry_t **ent, **next, *this; \ + if (!map) \ + return; \ + for (ent = HT_START(prefix##_impl, &map->head); ent != NULL; \ + ent = next) { \ + this = *ent; \ + next = HT_NEXT_RMV(prefix##_impl, &map->head, ent); \ + if (free_val) \ + free_val(this->val); \ + prefix##_entry_free(this); \ + } \ + tor_assert(HT_EMPTY(&map->head)); \ + HT_CLEAR(prefix##_impl, &map->head); \ + tor_free(map); \ + } \ + \ + /** return an <b>iterator</b> pointer to the front of a map. \ + * \ + * Iterator example: \ + * \ + * \code \ + * // uppercase values in "map", removing empty values. \ + * \ + * strmap_iter_t *iter; \ + * const char *key; \ + * void *val; \ + * char *cp; \ + * \ + * for (iter = strmap_iter_init(map); !strmap_iter_done(iter); ) { \ + * strmap_iter_get(iter, &key, &val); \ + * cp = (char*)val; \ + * if (!*cp) { \ + * iter = strmap_iter_next_rmv(map,iter); \ + * free(val); \ + * } else { \ + * for (;*cp;cp++) *cp = TOR_TOUPPER(*cp); \ + */ \ + prefix##_iter_t * \ + prefix##_iter_init(maptype *map) \ + { \ + tor_assert(map); \ + return HT_START(prefix##_impl, &map->head); \ + } \ + \ + /** Advance <b>iter</b> a single step to the next entry, and return \ + * its new value. */ \ + prefix##_iter_t * \ + prefix##_iter_next(maptype *map, prefix##_iter_t *iter) \ + { \ + tor_assert(map); \ + tor_assert(iter); \ + return HT_NEXT(prefix##_impl, &map->head, iter); \ + } \ + /** Advance <b>iter</b> a single step to the next entry, removing the \ + * current entry, and return its new value. */ \ + prefix##_iter_t * \ + prefix##_iter_next_rmv(maptype *map, prefix##_iter_t *iter) \ + { \ + prefix##_entry_t *rmv; \ + tor_assert(map); \ + tor_assert(iter); \ + tor_assert(*iter); \ + rmv = *iter; \ + iter = HT_NEXT_RMV(prefix##_impl, &map->head, iter); \ + prefix##_entry_free(rmv); \ + return iter; \ + } \ + /** Set *<b>keyp</b> and *<b>valp</b> to the current entry pointed \ + * to by iter. */ \ + void \ + prefix##_iter_get(prefix##_iter_t *iter, const keytype *keyp, \ + void **valp) \ + { \ + tor_assert(iter); \ + tor_assert(*iter); \ + tor_assert(keyp); \ + tor_assert(valp); \ + *keyp = (*iter)->key; \ + *valp = (*iter)->val; \ + } \ + /** Return true iff <b>iter</b> has advanced past the last entry of \ + * <b>map</b>. */ \ + int \ + prefix##_iter_done(prefix##_iter_t *iter) \ + { \ + return iter == NULL; \ + } + +IMPLEMENT_MAP_FNS(strmap_t, char *, strmap) +IMPLEMENT_MAP_FNS(digestmap_t, char *, digestmap) +IMPLEMENT_MAP_FNS(digest256map_t, uint8_t *, digest256map) + +/** Same as strmap_set, but first converts <b>key</b> to lowercase. */ +void * +strmap_set_lc(strmap_t *map, const char *key, void *val) +{ + /* We could be a little faster by using strcasecmp instead, and a separate + * type, but I don't think it matters. */ + void *v; + char *lc_key = tor_strdup(key); + tor_strlower(lc_key); + v = strmap_set(map,lc_key,val); + tor_free(lc_key); + return v; +} + +/** Same as strmap_get, but first converts <b>key</b> to lowercase. */ +void * +strmap_get_lc(const strmap_t *map, const char *key) +{ + void *v; + char *lc_key = tor_strdup(key); + tor_strlower(lc_key); + v = strmap_get(map,lc_key); + tor_free(lc_key); + return v; +} + +/** Same as strmap_remove, but first converts <b>key</b> to lowercase */ +void * +strmap_remove_lc(strmap_t *map, const char *key) +{ + void *v; + char *lc_key = tor_strdup(key); + tor_strlower(lc_key); + v = strmap_remove(map,lc_key); + tor_free(lc_key); + return v; +} diff --git a/src/lib/container/map.h b/src/lib/container/map.h new file mode 100644 index 0000000000..a2d1b01d12 --- /dev/null +++ b/src/lib/container/map.h @@ -0,0 +1,261 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_MAP_H +#define TOR_MAP_H + +/** + * \file map.h + * + * \brief Headers for map.c. + **/ + +#include "lib/testsupport/testsupport.h" +#include "lib/cc/torint.h" + +#include "siphash.h" + +#define DECLARE_MAP_FNS(maptype, keytype, prefix) \ + typedef struct maptype maptype; \ + typedef struct prefix##entry_t *prefix##iter_t; \ + MOCK_DECL(maptype*, prefix##new, (void)); \ + void* prefix##set(maptype *map, keytype key, void *val); \ + void* prefix##get(const maptype *map, keytype key); \ + void* prefix##remove(maptype *map, keytype key); \ + MOCK_DECL(void, prefix##free_, (maptype *map, void (*free_val)(void*))); \ + int prefix##isempty(const maptype *map); \ + int prefix##size(const maptype *map); \ + prefix##iter_t *prefix##iter_init(maptype *map); \ + prefix##iter_t *prefix##iter_next(maptype *map, prefix##iter_t *iter); \ + prefix##iter_t *prefix##iter_next_rmv(maptype *map, prefix##iter_t *iter); \ + void prefix##iter_get(prefix##iter_t *iter, keytype *keyp, void **valp); \ + int prefix##iter_done(prefix##iter_t *iter); \ + void prefix##assert_ok(const maptype *map) + +/* Map from const char * to void *. Implemented with a hash table. */ +DECLARE_MAP_FNS(strmap_t, const char *, strmap_); +/* Map from const char[DIGEST_LEN] to void *. Implemented with a hash table. */ +DECLARE_MAP_FNS(digestmap_t, const char *, digestmap_); +/* Map from const uint8_t[DIGEST256_LEN] to void *. Implemented with a hash + * table. */ +DECLARE_MAP_FNS(digest256map_t, const uint8_t *, digest256map_); + +#define MAP_FREE_AND_NULL(maptype, map, fn) \ + do { \ + maptype ## _free_((map), (fn)); \ + (map) = NULL; \ + } while (0) + +#define strmap_free(map, fn) MAP_FREE_AND_NULL(strmap, (map), (fn)) +#define digestmap_free(map, fn) MAP_FREE_AND_NULL(digestmap, (map), (fn)) +#define digest256map_free(map, fn) MAP_FREE_AND_NULL(digest256map, (map), (fn)) + +#undef DECLARE_MAP_FNS + +/** Iterates over the key-value pairs in a map <b>map</b> in order. + * <b>prefix</b> is as for DECLARE_MAP_FNS (i.e., strmap_ or digestmap_). + * The map's keys and values are of type keytype and valtype respectively; + * each iteration assigns them to keyvar and valvar. + * + * Example use: + * MAP_FOREACH(digestmap_, m, const char *, k, routerinfo_t *, r) { + * // use k and r + * } MAP_FOREACH_END. + */ +/* Unpacks to, approximately: + * { + * digestmap_iter_t *k_iter; + * for (k_iter = digestmap_iter_init(m); !digestmap_iter_done(k_iter); + * k_iter = digestmap_iter_next(m, k_iter)) { + * const char *k; + * void *r_voidp; + * routerinfo_t *r; + * digestmap_iter_get(k_iter, &k, &r_voidp); + * r = r_voidp; + * // use k and r + * } + * } + */ +#define MAP_FOREACH(prefix, map, keytype, keyvar, valtype, valvar) \ + STMT_BEGIN \ + prefix##iter_t *keyvar##_iter; \ + for (keyvar##_iter = prefix##iter_init(map); \ + !prefix##iter_done(keyvar##_iter); \ + keyvar##_iter = prefix##iter_next(map, keyvar##_iter)) { \ + keytype keyvar; \ + void *valvar##_voidp; \ + valtype valvar; \ + prefix##iter_get(keyvar##_iter, &keyvar, &valvar##_voidp); \ + valvar = valvar##_voidp; + +/** As MAP_FOREACH, except allows members to be removed from the map + * during the iteration via MAP_DEL_CURRENT. Example use: + * + * Example use: + * MAP_FOREACH(digestmap_, m, const char *, k, routerinfo_t *, r) { + * if (is_very_old(r)) + * MAP_DEL_CURRENT(k); + * } MAP_FOREACH_END. + **/ +/* Unpacks to, approximately: + * { + * digestmap_iter_t *k_iter; + * int k_del=0; + * for (k_iter = digestmap_iter_init(m); !digestmap_iter_done(k_iter); + * k_iter = k_del ? digestmap_iter_next(m, k_iter) + * : digestmap_iter_next_rmv(m, k_iter)) { + * const char *k; + * void *r_voidp; + * routerinfo_t *r; + * k_del=0; + * digestmap_iter_get(k_iter, &k, &r_voidp); + * r = r_voidp; + * if (is_very_old(r)) { + * k_del = 1; + * } + * } + * } + */ +#define MAP_FOREACH_MODIFY(prefix, map, keytype, keyvar, valtype, valvar) \ + STMT_BEGIN \ + prefix##iter_t *keyvar##_iter; \ + int keyvar##_del=0; \ + for (keyvar##_iter = prefix##iter_init(map); \ + !prefix##iter_done(keyvar##_iter); \ + keyvar##_iter = keyvar##_del ? \ + prefix##iter_next_rmv(map, keyvar##_iter) : \ + prefix##iter_next(map, keyvar##_iter)) { \ + keytype keyvar; \ + void *valvar##_voidp; \ + valtype valvar; \ + keyvar##_del=0; \ + prefix##iter_get(keyvar##_iter, &keyvar, &valvar##_voidp); \ + valvar = valvar##_voidp; + +/** Used with MAP_FOREACH_MODIFY to remove the currently-iterated-upon + * member of the map. */ +#define MAP_DEL_CURRENT(keyvar) \ + STMT_BEGIN \ + keyvar##_del = 1; \ + STMT_END + +/** Used to end a MAP_FOREACH() block. */ +#define MAP_FOREACH_END } STMT_END ; + +/** As MAP_FOREACH, but does not require declaration of prefix or keytype. + * Example use: + * DIGESTMAP_FOREACH(m, k, routerinfo_t *, r) { + * // use k and r + * } DIGESTMAP_FOREACH_END. + */ +#define DIGESTMAP_FOREACH(map, keyvar, valtype, valvar) \ + MAP_FOREACH(digestmap_, map, const char *, keyvar, valtype, valvar) + +/** As MAP_FOREACH_MODIFY, but does not require declaration of prefix or + * keytype. + * Example use: + * DIGESTMAP_FOREACH_MODIFY(m, k, routerinfo_t *, r) { + * if (is_very_old(r)) + * MAP_DEL_CURRENT(k); + * } DIGESTMAP_FOREACH_END. + */ +#define DIGESTMAP_FOREACH_MODIFY(map, keyvar, valtype, valvar) \ + MAP_FOREACH_MODIFY(digestmap_, map, const char *, keyvar, valtype, valvar) +/** Used to end a DIGESTMAP_FOREACH() block. */ +#define DIGESTMAP_FOREACH_END MAP_FOREACH_END + +#define DIGEST256MAP_FOREACH(map, keyvar, valtype, valvar) \ + MAP_FOREACH(digest256map_, map, const uint8_t *, keyvar, valtype, valvar) +#define DIGEST256MAP_FOREACH_MODIFY(map, keyvar, valtype, valvar) \ + MAP_FOREACH_MODIFY(digest256map_, map, const uint8_t *, \ + keyvar, valtype, valvar) +#define DIGEST256MAP_FOREACH_END MAP_FOREACH_END + +#define STRMAP_FOREACH(map, keyvar, valtype, valvar) \ + MAP_FOREACH(strmap_, map, const char *, keyvar, valtype, valvar) +#define STRMAP_FOREACH_MODIFY(map, keyvar, valtype, valvar) \ + MAP_FOREACH_MODIFY(strmap_, map, const char *, keyvar, valtype, valvar) +#define STRMAP_FOREACH_END MAP_FOREACH_END + +void* strmap_set_lc(strmap_t *map, const char *key, void *val); +void* strmap_get_lc(const strmap_t *map, const char *key); +void* strmap_remove_lc(strmap_t *map, const char *key); + +#define DECLARE_TYPED_DIGESTMAP_FNS(prefix, maptype, valtype) \ + typedef struct maptype maptype; \ + typedef struct prefix##iter_t *prefix##iter_t; \ + ATTR_UNUSED static inline maptype* \ + prefix##new(void) \ + { \ + return (maptype*)digestmap_new(); \ + } \ + ATTR_UNUSED static inline digestmap_t* \ + prefix##to_digestmap(maptype *map) \ + { \ + return (digestmap_t*)map; \ + } \ + ATTR_UNUSED static inline valtype* \ + prefix##get(maptype *map, const char *key) \ + { \ + return (valtype*)digestmap_get((digestmap_t*)map, key); \ + } \ + ATTR_UNUSED static inline valtype* \ + prefix##set(maptype *map, const char *key, valtype *val) \ + { \ + return (valtype*)digestmap_set((digestmap_t*)map, key, val); \ + } \ + ATTR_UNUSED static inline valtype* \ + prefix##remove(maptype *map, const char *key) \ + { \ + return (valtype*)digestmap_remove((digestmap_t*)map, key); \ + } \ + ATTR_UNUSED static inline void \ + prefix##f##ree_(maptype *map, void (*free_val)(void*)) \ + { \ + digestmap_free_((digestmap_t*)map, free_val); \ + } \ + ATTR_UNUSED static inline int \ + prefix##isempty(maptype *map) \ + { \ + return digestmap_isempty((digestmap_t*)map); \ + } \ + ATTR_UNUSED static inline int \ + prefix##size(maptype *map) \ + { \ + return digestmap_size((digestmap_t*)map); \ + } \ + ATTR_UNUSED static inline \ + prefix##iter_t *prefix##iter_init(maptype *map) \ + { \ + return (prefix##iter_t*) digestmap_iter_init((digestmap_t*)map); \ + } \ + ATTR_UNUSED static inline \ + prefix##iter_t *prefix##iter_next(maptype *map, prefix##iter_t *iter) \ + { \ + return (prefix##iter_t*) digestmap_iter_next( \ + (digestmap_t*)map, (digestmap_iter_t*)iter); \ + } \ + ATTR_UNUSED static inline prefix##iter_t* \ + prefix##iter_next_rmv(maptype *map, prefix##iter_t *iter) \ + { \ + return (prefix##iter_t*) digestmap_iter_next_rmv( \ + (digestmap_t*)map, (digestmap_iter_t*)iter); \ + } \ + ATTR_UNUSED static inline void \ + prefix##iter_get(prefix##iter_t *iter, \ + const char **keyp, \ + valtype **valp) \ + { \ + void *v; \ + digestmap_iter_get((digestmap_iter_t*) iter, keyp, &v); \ + *valp = v; \ + } \ + ATTR_UNUSED static inline int \ + prefix##iter_done(prefix##iter_t *iter) \ + { \ + return digestmap_iter_done((digestmap_iter_t*)iter); \ + } + +#endif /* !defined(TOR_CONTAINER_H) */ diff --git a/src/lib/container/order.c b/src/lib/container/order.c new file mode 100644 index 0000000000..f6503a124e --- /dev/null +++ b/src/lib/container/order.c @@ -0,0 +1,48 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file order.c + * \brief Functions for finding the n'th element of an array. + **/ + +#include <stdlib.h> + +#include "lib/container/order.h" +#include "lib/log/util_bug.h" + +/** Declare a function called <b>funcname</b> that acts as a find_nth_FOO + * function for an array of type <b>elt_t</b>*. + * + * NOTE: The implementation kind of sucks: It's O(n log n), whereas finding + * the kth element of an n-element list can be done in O(n). Then again, this + * implementation is not in critical path, and it is obviously correct. */ +#define IMPLEMENT_ORDER_FUNC(funcname, elt_t) \ + static int \ + _cmp_ ## elt_t(const void *_a, const void *_b) \ + { \ + const elt_t *a = _a, *b = _b; \ + if (*a<*b) \ + return -1; \ + else if (*a>*b) \ + return 1; \ + else \ + return 0; \ + } \ + elt_t \ + funcname(elt_t *array, int n_elements, int nth) \ + { \ + tor_assert(nth >= 0); \ + tor_assert(nth < n_elements); \ + qsort(array, n_elements, sizeof(elt_t), _cmp_ ##elt_t); \ + return array[nth]; \ + } + +IMPLEMENT_ORDER_FUNC(find_nth_int, int) +IMPLEMENT_ORDER_FUNC(find_nth_time, time_t) +IMPLEMENT_ORDER_FUNC(find_nth_double, double) +IMPLEMENT_ORDER_FUNC(find_nth_uint32, uint32_t) +IMPLEMENT_ORDER_FUNC(find_nth_int32, int32_t) +IMPLEMENT_ORDER_FUNC(find_nth_long, long) diff --git a/src/lib/container/order.h b/src/lib/container/order.h new file mode 100644 index 0000000000..a176d6d8a6 --- /dev/null +++ b/src/lib/container/order.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_ORDER_H +#define TOR_ORDER_H + +/** + * \file order.h + * + * \brief Header for order.c. + **/ + +#include "lib/cc/compat_compiler.h" +#include "lib/cc/torint.h" + +/* These functions, given an <b>array</b> of <b>n_elements</b>, return the + * <b>nth</b> lowest element. <b>nth</b>=0 gives the lowest element; + * <b>n_elements</b>-1 gives the highest; and (<b>n_elements</b>-1) / 2 gives + * the median. As a side effect, the elements of <b>array</b> are sorted. */ +int find_nth_int(int *array, int n_elements, int nth); +time_t find_nth_time(time_t *array, int n_elements, int nth); +double find_nth_double(double *array, int n_elements, int nth); +int32_t find_nth_int32(int32_t *array, int n_elements, int nth); +uint32_t find_nth_uint32(uint32_t *array, int n_elements, int nth); +long find_nth_long(long *array, int n_elements, int nth); +static inline int +median_int(int *array, int n_elements) +{ + return find_nth_int(array, n_elements, (n_elements-1)/2); +} +static inline time_t +median_time(time_t *array, int n_elements) +{ + return find_nth_time(array, n_elements, (n_elements-1)/2); +} +static inline double +median_double(double *array, int n_elements) +{ + return find_nth_double(array, n_elements, (n_elements-1)/2); +} +static inline uint32_t +median_uint32(uint32_t *array, int n_elements) +{ + return find_nth_uint32(array, n_elements, (n_elements-1)/2); +} +static inline int32_t +median_int32(int32_t *array, int n_elements) +{ + return find_nth_int32(array, n_elements, (n_elements-1)/2); +} + +static inline uint32_t +third_quartile_uint32(uint32_t *array, int n_elements) +{ + return find_nth_uint32(array, n_elements, (n_elements*3)/4); +} + +#endif /* !defined(TOR_CONTAINER_H) */ diff --git a/src/lib/container/smartlist.c b/src/lib/container/smartlist.c new file mode 100644 index 0000000000..3ab2797d68 --- /dev/null +++ b/src/lib/container/smartlist.c @@ -0,0 +1,866 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file smartlist.c + * + * \brief Higher-level functions for the "smartlist" resizeable array + * abstraction. + * + * The functions declared here use higher-level functionality than those in + * smartlist_core.c, and handle things like smartlists of different types, + * sorting, searching, heap-structured smartlists, and other convenience + * functions. + **/ + +#include "lib/container/smartlist.h" +#include "lib/err/torerr.h" +#include "lib/malloc/malloc.h" +#include "lib/defs/digest_sizes.h" +#include "lib/ctime/di_ops.h" +#include "lib/string/compat_ctype.h" +#include "lib/string/compat_string.h" +#include "lib/string/util_string.h" +#include "lib/string/printf.h" + +#include "lib/log/util_bug.h" + +#include <stdlib.h> +#include <string.h> + +/** Append the string produced by tor_asprintf(<b>pattern</b>, <b>...</b>) + * to <b>sl</b>. */ +void +smartlist_add_asprintf(struct smartlist_t *sl, const char *pattern, ...) +{ + va_list ap; + va_start(ap, pattern); + smartlist_add_vasprintf(sl, pattern, ap); + va_end(ap); +} + +/** va_list-based backend of smartlist_add_asprintf. */ +void +smartlist_add_vasprintf(struct smartlist_t *sl, const char *pattern, + va_list args) +{ + char *str = NULL; + + tor_vasprintf(&str, pattern, args); + tor_assert(str != NULL); + + smartlist_add(sl, str); +} + +/** Reverse the order of the items in <b>sl</b>. */ +void +smartlist_reverse(smartlist_t *sl) +{ + int i, j; + void *tmp; + tor_assert(sl); + for (i = 0, j = sl->num_used-1; i < j; ++i, --j) { + tmp = sl->list[i]; + sl->list[i] = sl->list[j]; + sl->list[j] = tmp; + } +} + +/** If there are any strings in sl equal to element, remove and free them. + * Does not preserve order. */ +void +smartlist_string_remove(smartlist_t *sl, const char *element) +{ + int i; + tor_assert(sl); + tor_assert(element); + for (i = 0; i < sl->num_used; ++i) { + if (!strcmp(element, sl->list[i])) { + tor_free(sl->list[i]); + sl->list[i] = sl->list[--sl->num_used]; /* swap with the end */ + i--; /* so we process the new i'th element */ + sl->list[sl->num_used] = NULL; + } + } +} + +/** Return true iff <b>sl</b> has some element E such that + * !strcmp(E,<b>element</b>) + */ +int +smartlist_contains_string(const smartlist_t *sl, const char *element) +{ + int i; + if (!sl) return 0; + for (i=0; i < sl->num_used; i++) + if (strcmp((const char*)sl->list[i],element)==0) + return 1; + return 0; +} + +/** If <b>element</b> is equal to an element of <b>sl</b>, return that + * element's index. Otherwise, return -1. */ +int +smartlist_string_pos(const smartlist_t *sl, const char *element) +{ + int i; + if (!sl) return -1; + for (i=0; i < sl->num_used; i++) + if (strcmp((const char*)sl->list[i],element)==0) + return i; + return -1; +} + +/** If <b>element</b> is the same pointer as an element of <b>sl</b>, return + * that element's index. Otherwise, return -1. */ +int +smartlist_pos(const smartlist_t *sl, const void *element) +{ + int i; + if (!sl) return -1; + for (i=0; i < sl->num_used; i++) + if (element == sl->list[i]) + return i; + return -1; +} + +/** Return true iff <b>sl</b> has some element E such that + * !strcasecmp(E,<b>element</b>) + */ +int +smartlist_contains_string_case(const smartlist_t *sl, const char *element) +{ + int i; + if (!sl) return 0; + for (i=0; i < sl->num_used; i++) + if (strcasecmp((const char*)sl->list[i],element)==0) + return 1; + return 0; +} + +/** Return true iff <b>sl</b> has some element E such that E is equal + * to the decimal encoding of <b>num</b>. + */ +int +smartlist_contains_int_as_string(const smartlist_t *sl, int num) +{ + char buf[32]; /* long enough for 64-bit int, and then some. */ + tor_snprintf(buf,sizeof(buf),"%d", num); + return smartlist_contains_string(sl, buf); +} + +/** Return true iff the two lists contain the same strings in the same + * order, or if they are both NULL. */ +int +smartlist_strings_eq(const smartlist_t *sl1, const smartlist_t *sl2) +{ + if (sl1 == NULL) + return sl2 == NULL; + if (sl2 == NULL) + return 0; + if (smartlist_len(sl1) != smartlist_len(sl2)) + return 0; + SMARTLIST_FOREACH(sl1, const char *, cp1, { + const char *cp2 = smartlist_get(sl2, cp1_sl_idx); + if (strcmp(cp1, cp2)) + return 0; + }); + return 1; +} + +/** Return true iff the two lists contain the same int pointer values in + * the same order, or if they are both NULL. */ +int +smartlist_ints_eq(const smartlist_t *sl1, const smartlist_t *sl2) +{ + if (sl1 == NULL) + return sl2 == NULL; + if (sl2 == NULL) + return 0; + if (smartlist_len(sl1) != smartlist_len(sl2)) + return 0; + SMARTLIST_FOREACH(sl1, int *, cp1, { + int *cp2 = smartlist_get(sl2, cp1_sl_idx); + if (*cp1 != *cp2) + return 0; + }); + return 1; +} + +/** + * Return true if there is shallow equality between smartlists - + * i.e. all indices correspond to exactly same object (pointer + * values are matching). Otherwise, return false. + */ +int +smartlist_ptrs_eq(const smartlist_t *s1, const smartlist_t *s2) +{ + if (s1 == s2) + return 1; + + // Note: pointers cannot both be NULL at this point, because + // above check. + if (s1 == NULL || s2 == NULL) + return 0; + + if (smartlist_len(s1) != smartlist_len(s2)) + return 0; + + for (int i = 0; i < smartlist_len(s1); i++) { + if (smartlist_get(s1, i) != smartlist_get(s2, i)) + return 0; + } + + return 1; +} + +/** Return true iff <b>sl</b> has some element E such that + * tor_memeq(E,<b>element</b>,DIGEST_LEN) + */ +int +smartlist_contains_digest(const smartlist_t *sl, const char *element) +{ + int i; + if (!sl) return 0; + for (i=0; i < sl->num_used; i++) + if (tor_memeq((const char*)sl->list[i],element,DIGEST_LEN)) + return 1; + return 0; +} + +/** Return true iff some element E of sl2 has smartlist_contains(sl1,E). + */ +int +smartlist_overlap(const smartlist_t *sl1, const smartlist_t *sl2) +{ + int i; + for (i=0; i < sl2->num_used; i++) + if (smartlist_contains(sl1, sl2->list[i])) + return 1; + return 0; +} + +/** Remove every element E of sl1 such that !smartlist_contains(sl2,E). + * Does not preserve the order of sl1. + */ +void +smartlist_intersect(smartlist_t *sl1, const smartlist_t *sl2) +{ + int i; + for (i=0; i < sl1->num_used; i++) + if (!smartlist_contains(sl2, sl1->list[i])) { + sl1->list[i] = sl1->list[--sl1->num_used]; /* swap with the end */ + i--; /* so we process the new i'th element */ + sl1->list[sl1->num_used] = NULL; + } +} + +/** Remove every element E of sl1 such that smartlist_contains(sl2,E). + * Does not preserve the order of sl1. + */ +void +smartlist_subtract(smartlist_t *sl1, const smartlist_t *sl2) +{ + int i; + for (i=0; i < sl2->num_used; i++) + smartlist_remove(sl1, sl2->list[i]); +} + +/** Allocate and return a new string containing the concatenation of + * the elements of <b>sl</b>, in order, separated by <b>join</b>. If + * <b>terminate</b> is true, also terminate the string with <b>join</b>. + * If <b>len_out</b> is not NULL, set <b>len_out</b> to the length of + * the returned string. Requires that every element of <b>sl</b> is + * NUL-terminated string. + */ +char * +smartlist_join_strings(smartlist_t *sl, const char *join, + int terminate, size_t *len_out) +{ + return smartlist_join_strings2(sl,join,strlen(join),terminate,len_out); +} + +/** As smartlist_join_strings, but instead of separating/terminated with a + * NUL-terminated string <b>join</b>, uses the <b>join_len</b>-byte sequence + * at <b>join</b>. (Useful for generating a sequence of NUL-terminated + * strings.) + */ +char * +smartlist_join_strings2(smartlist_t *sl, const char *join, + size_t join_len, int terminate, size_t *len_out) +{ + int i; + size_t n = 0; + char *r = NULL, *dst, *src; + + tor_assert(sl); + tor_assert(join); + + if (terminate) + n = join_len; + + for (i = 0; i < sl->num_used; ++i) { + n += strlen(sl->list[i]); + if (i+1 < sl->num_used) /* avoid double-counting the last one */ + n += join_len; + } + dst = r = tor_malloc(n+1); + for (i = 0; i < sl->num_used; ) { + for (src = sl->list[i]; *src; ) + *dst++ = *src++; + if (++i < sl->num_used) { + memcpy(dst, join, join_len); + dst += join_len; + } + } + if (terminate) { + memcpy(dst, join, join_len); + dst += join_len; + } + *dst = '\0'; + + if (len_out) + *len_out = dst-r; + return r; +} + +/** Sort the members of <b>sl</b> into an order defined by + * the ordering function <b>compare</b>, which returns less then 0 if a + * precedes b, greater than 0 if b precedes a, and 0 if a 'equals' b. + */ +void +smartlist_sort(smartlist_t *sl, int (*compare)(const void **a, const void **b)) +{ + if (!sl->num_used) + return; + qsort(sl->list, sl->num_used, sizeof(void*), + (int (*)(const void *,const void*))compare); +} + +/** Given a smartlist <b>sl</b> sorted with the function <b>compare</b>, + * return the most frequent member in the list. Break ties in favor of + * later elements. If the list is empty, return NULL. If count_out is + * non-null, set it to the count of the most frequent member. + */ +void * +smartlist_get_most_frequent_(const smartlist_t *sl, + int (*compare)(const void **a, const void **b), + int *count_out) +{ + const void *most_frequent = NULL; + int most_frequent_count = 0; + + const void *cur = NULL; + int i, count=0; + + if (!sl->num_used) { + if (count_out) + *count_out = 0; + return NULL; + } + for (i = 0; i < sl->num_used; ++i) { + const void *item = sl->list[i]; + if (cur && 0 == compare(&cur, &item)) { + ++count; + } else { + if (cur && count >= most_frequent_count) { + most_frequent = cur; + most_frequent_count = count; + } + cur = item; + count = 1; + } + } + if (cur && count >= most_frequent_count) { + most_frequent = cur; + most_frequent_count = count; + } + if (count_out) + *count_out = most_frequent_count; + return (void*)most_frequent; +} + +/** Given a sorted smartlist <b>sl</b> and the comparison function used to + * sort it, remove all duplicate members. If free_fn is provided, calls + * free_fn on each duplicate. Otherwise, just removes them. Preserves order. + */ +void +smartlist_uniq(smartlist_t *sl, + int (*compare)(const void **a, const void **b), + void (*free_fn)(void *a)) +{ + int i; + for (i=1; i < sl->num_used; ++i) { + if (compare((const void **)&(sl->list[i-1]), + (const void **)&(sl->list[i])) == 0) { + if (free_fn) + free_fn(sl->list[i]); + smartlist_del_keeporder(sl, i--); + } + } +} + +/** Assuming the members of <b>sl</b> are in order, return a pointer to the + * member that matches <b>key</b>. Ordering and matching are defined by a + * <b>compare</b> function that returns 0 on a match; less than 0 if key is + * less than member, and greater than 0 if key is greater then member. + */ +void * +smartlist_bsearch(const smartlist_t *sl, const void *key, + int (*compare)(const void *key, const void **member)) +{ + int found, idx; + idx = smartlist_bsearch_idx(sl, key, compare, &found); + return found ? smartlist_get(sl, idx) : NULL; +} + +/** Assuming the members of <b>sl</b> are in order, return the index of the + * member that matches <b>key</b>. If no member matches, return the index of + * the first member greater than <b>key</b>, or smartlist_len(sl) if no member + * is greater than <b>key</b>. Set <b>found_out</b> to true on a match, to + * false otherwise. Ordering and matching are defined by a <b>compare</b> + * function that returns 0 on a match; less than 0 if key is less than member, + * and greater than 0 if key is greater then member. + */ +int +smartlist_bsearch_idx(const smartlist_t *sl, const void *key, + int (*compare)(const void *key, const void **member), + int *found_out) +{ + int hi, lo, cmp, mid, len, diff; + + tor_assert(sl); + tor_assert(compare); + tor_assert(found_out); + + len = smartlist_len(sl); + + /* Check for the trivial case of a zero-length list */ + if (len == 0) { + *found_out = 0; + /* We already know smartlist_len(sl) is 0 in this case */ + return 0; + } + + /* Okay, we have a real search to do */ + tor_assert(len > 0); + lo = 0; + hi = len - 1; + + /* + * These invariants are always true: + * + * For all i such that 0 <= i < lo, sl[i] < key + * For all i such that hi < i <= len, sl[i] > key + */ + + while (lo <= hi) { + diff = hi - lo; + /* + * We want mid = (lo + hi) / 2, but that could lead to overflow, so + * instead diff = hi - lo (non-negative because of loop condition), and + * then hi = lo + diff, mid = (lo + lo + diff) / 2 = lo + (diff / 2). + */ + mid = lo + (diff / 2); + cmp = compare(key, (const void**) &(sl->list[mid])); + if (cmp == 0) { + /* sl[mid] == key; we found it */ + *found_out = 1; + return mid; + } else if (cmp > 0) { + /* + * key > sl[mid] and an index i such that sl[i] == key must + * have i > mid if it exists. + */ + + /* + * Since lo <= mid <= hi, hi can only decrease on each iteration (by + * being set to mid - 1) and hi is initially len - 1, mid < len should + * always hold, and this is not symmetric with the left end of list + * mid > 0 test below. A key greater than the right end of the list + * should eventually lead to lo == hi == mid == len - 1, and then + * we set lo to len below and fall out to the same exit we hit for + * a key in the middle of the list but not matching. Thus, we just + * assert for consistency here rather than handle a mid == len case. + */ + tor_assert(mid < len); + /* Move lo to the element immediately after sl[mid] */ + lo = mid + 1; + } else { + /* This should always be true in this case */ + tor_assert(cmp < 0); + + /* + * key < sl[mid] and an index i such that sl[i] == key must + * have i < mid if it exists. + */ + + if (mid > 0) { + /* Normal case, move hi to the element immediately before sl[mid] */ + hi = mid - 1; + } else { + /* These should always be true in this case */ + tor_assert(mid == lo); + tor_assert(mid == 0); + /* + * We were at the beginning of the list and concluded that every + * element e compares e > key. + */ + *found_out = 0; + return 0; + } + } + } + + /* + * lo > hi; we have no element matching key but we have elements falling + * on both sides of it. The lo index points to the first element > key. + */ + tor_assert(lo == hi + 1); /* All other cases should have been handled */ + tor_assert(lo >= 0); + tor_assert(lo <= len); + tor_assert(hi >= 0); + tor_assert(hi <= len); + + if (lo < len) { + cmp = compare(key, (const void **) &(sl->list[lo])); + tor_assert(cmp < 0); + } else { + cmp = compare(key, (const void **) &(sl->list[len-1])); + tor_assert(cmp > 0); + } + + *found_out = 0; + return lo; +} + +/** Helper: compare two const char **s. */ +static int +compare_string_ptrs_(const void **_a, const void **_b) +{ + return strcmp((const char*)*_a, (const char*)*_b); +} + +/** Sort a smartlist <b>sl</b> containing strings into lexically ascending + * order. */ +void +smartlist_sort_strings(smartlist_t *sl) +{ + smartlist_sort(sl, compare_string_ptrs_); +} + +/** Return the most frequent string in the sorted list <b>sl</b> */ +const char * +smartlist_get_most_frequent_string(smartlist_t *sl) +{ + return smartlist_get_most_frequent(sl, compare_string_ptrs_); +} + +/** Return the most frequent string in the sorted list <b>sl</b>. + * If <b>count_out</b> is provided, set <b>count_out</b> to the + * number of times that string appears. + */ +const char * +smartlist_get_most_frequent_string_(smartlist_t *sl, int *count_out) +{ + return smartlist_get_most_frequent_(sl, compare_string_ptrs_, count_out); +} + +/** Remove duplicate strings from a sorted list, and free them with tor_free(). + */ +void +smartlist_uniq_strings(smartlist_t *sl) +{ + smartlist_uniq(sl, compare_string_ptrs_, tor_free_); +} + +/** Helper: compare two pointers. */ +static int +compare_ptrs_(const void **_a, const void **_b) +{ + const void *a = *_a, *b = *_b; + if (a<b) + return -1; + else if (a==b) + return 0; + else + return 1; +} + +/** Sort <b>sl</b> in ascending order of the pointers it contains. */ +void +smartlist_sort_pointers(smartlist_t *sl) +{ + smartlist_sort(sl, compare_ptrs_); +} + +/* Heap-based priority queue implementation for O(lg N) insert and remove. + * Recall that the heap property is that, for every index I, h[I] < + * H[LEFT_CHILD[I]] and h[I] < H[RIGHT_CHILD[I]]. + * + * For us to remove items other than the topmost item, each item must store + * its own index within the heap. When calling the pqueue functions, tell + * them about the offset of the field that stores the index within the item. + * + * Example: + * + * typedef struct timer_t { + * struct timeval tv; + * int heap_index; + * } timer_t; + * + * static int compare(const void *p1, const void *p2) { + * const timer_t *t1 = p1, *t2 = p2; + * if (t1->tv.tv_sec < t2->tv.tv_sec) { + * return -1; + * } else if (t1->tv.tv_sec > t2->tv.tv_sec) { + * return 1; + * } else { + * return t1->tv.tv_usec - t2->tv_usec; + * } + * } + * + * void timer_heap_insert(smartlist_t *heap, timer_t *timer) { + * smartlist_pqueue_add(heap, compare, offsetof(timer_t, heap_index), + * timer); + * } + * + * void timer_heap_pop(smartlist_t *heap) { + * return smartlist_pqueue_pop(heap, compare, + * offsetof(timer_t, heap_index)); + * } + */ + +/** @{ */ +/** Functions to manipulate heap indices to find a node's parent and children. + * + * For a 1-indexed array, we would use LEFT_CHILD[x] = 2*x and RIGHT_CHILD[x] + * = 2*x + 1. But this is C, so we have to adjust a little. */ + +/* MAX_PARENT_IDX is the largest IDX in the smartlist which might have + * children whose indices fit inside an int. + * LEFT_CHILD(MAX_PARENT_IDX) == INT_MAX-2; + * RIGHT_CHILD(MAX_PARENT_IDX) == INT_MAX-1; + * LEFT_CHILD(MAX_PARENT_IDX + 1) == INT_MAX // impossible, see max list size. + */ +#define MAX_PARENT_IDX ((INT_MAX - 2) / 2) +/* If this is true, then i is small enough to potentially have children + * in the smartlist, and it is save to use LEFT_CHILD/RIGHT_CHILD on it. */ +#define IDX_MAY_HAVE_CHILDREN(i) ((i) <= MAX_PARENT_IDX) +#define LEFT_CHILD(i) ( 2*(i) + 1 ) +#define RIGHT_CHILD(i) ( 2*(i) + 2 ) +#define PARENT(i) ( ((i)-1) / 2 ) +/** }@ */ + +/** @{ */ +/** Helper macros for heaps: Given a local variable <b>idx_field_offset</b> + * set to the offset of an integer index within the heap element structure, + * IDX_OF_ITEM(p) gives you the index of p, and IDXP(p) gives you a pointer to + * where p's index is stored. Given additionally a local smartlist <b>sl</b>, + * UPDATE_IDX(i) sets the index of the element at <b>i</b> to the correct + * value (that is, to <b>i</b>). + */ +#define IDXP(p) ((int*)STRUCT_VAR_P(p, idx_field_offset)) + +#define UPDATE_IDX(i) do { \ + void *updated = sl->list[i]; \ + *IDXP(updated) = i; \ + } while (0) + +#define IDX_OF_ITEM(p) (*IDXP(p)) +/** @} */ + +/** Helper. <b>sl</b> may have at most one violation of the heap property: + * the item at <b>idx</b> may be greater than one or both of its children. + * Restore the heap property. */ +static inline void +smartlist_heapify(smartlist_t *sl, + int (*compare)(const void *a, const void *b), + int idx_field_offset, + int idx) +{ + while (1) { + if (! IDX_MAY_HAVE_CHILDREN(idx)) { + /* idx is so large that it cannot have any children, since doing so + * would mean the smartlist was over-capacity. Therefore it cannot + * violate the heap property by being greater than a child (since it + * doesn't have any). */ + return; + } + + int left_idx = LEFT_CHILD(idx); + int best_idx; + + if (left_idx >= sl->num_used) + return; + if (compare(sl->list[idx],sl->list[left_idx]) < 0) + best_idx = idx; + else + best_idx = left_idx; + if (left_idx+1 < sl->num_used && + compare(sl->list[left_idx+1],sl->list[best_idx]) < 0) + best_idx = left_idx + 1; + + if (best_idx == idx) { + return; + } else { + void *tmp = sl->list[idx]; + sl->list[idx] = sl->list[best_idx]; + sl->list[best_idx] = tmp; + UPDATE_IDX(idx); + UPDATE_IDX(best_idx); + + idx = best_idx; + } + } +} + +/** Insert <b>item</b> into the heap stored in <b>sl</b>, where order is + * determined by <b>compare</b> and the offset of the item in the heap is + * stored in an int-typed field at position <b>idx_field_offset</b> within + * item. + */ +void +smartlist_pqueue_add(smartlist_t *sl, + int (*compare)(const void *a, const void *b), + int idx_field_offset, + void *item) +{ + int idx; + smartlist_add(sl,item); + UPDATE_IDX(sl->num_used-1); + + for (idx = sl->num_used - 1; idx; ) { + int parent = PARENT(idx); + if (compare(sl->list[idx], sl->list[parent]) < 0) { + void *tmp = sl->list[parent]; + sl->list[parent] = sl->list[idx]; + sl->list[idx] = tmp; + UPDATE_IDX(parent); + UPDATE_IDX(idx); + idx = parent; + } else { + return; + } + } +} + +/** Remove and return the top-priority item from the heap stored in <b>sl</b>, + * where order is determined by <b>compare</b> and the item's position is + * stored at position <b>idx_field_offset</b> within the item. <b>sl</b> must + * not be empty. */ +void * +smartlist_pqueue_pop(smartlist_t *sl, + int (*compare)(const void *a, const void *b), + int idx_field_offset) +{ + void *top; + tor_assert(sl->num_used); + + top = sl->list[0]; + *IDXP(top)=-1; + if (--sl->num_used) { + sl->list[0] = sl->list[sl->num_used]; + sl->list[sl->num_used] = NULL; + UPDATE_IDX(0); + smartlist_heapify(sl, compare, idx_field_offset, 0); + } + sl->list[sl->num_used] = NULL; + return top; +} + +/** Remove the item <b>item</b> from the heap stored in <b>sl</b>, + * where order is determined by <b>compare</b> and the item's position is + * stored at position <b>idx_field_offset</b> within the item. <b>sl</b> must + * not be empty. */ +void +smartlist_pqueue_remove(smartlist_t *sl, + int (*compare)(const void *a, const void *b), + int idx_field_offset, + void *item) +{ + int idx = IDX_OF_ITEM(item); + tor_assert(idx >= 0); + tor_assert(sl->list[idx] == item); + --sl->num_used; + *IDXP(item) = -1; + if (idx == sl->num_used) { + sl->list[sl->num_used] = NULL; + return; + } else { + sl->list[idx] = sl->list[sl->num_used]; + sl->list[sl->num_used] = NULL; + UPDATE_IDX(idx); + smartlist_heapify(sl, compare, idx_field_offset, idx); + } +} + +/** Assert that the heap property is correctly maintained by the heap stored + * in <b>sl</b>, where order is determined by <b>compare</b>. */ +void +smartlist_pqueue_assert_ok(smartlist_t *sl, + int (*compare)(const void *a, const void *b), + int idx_field_offset) +{ + int i; + for (i = sl->num_used - 1; i >= 0; --i) { + if (i>0) + tor_assert(compare(sl->list[PARENT(i)], sl->list[i]) <= 0); + tor_assert(IDX_OF_ITEM(sl->list[i]) == i); + } +} + +/** Helper: compare two DIGEST_LEN digests. */ +static int +compare_digests_(const void **_a, const void **_b) +{ + return tor_memcmp((const char*)*_a, (const char*)*_b, DIGEST_LEN); +} + +/** Sort the list of DIGEST_LEN-byte digests into ascending order. */ +void +smartlist_sort_digests(smartlist_t *sl) +{ + smartlist_sort(sl, compare_digests_); +} + +/** Remove duplicate digests from a sorted list, and free them with tor_free(). + */ +void +smartlist_uniq_digests(smartlist_t *sl) +{ + smartlist_uniq(sl, compare_digests_, tor_free_); +} + +/** Helper: compare two DIGEST256_LEN digests. */ +static int +compare_digests256_(const void **_a, const void **_b) +{ + return tor_memcmp((const char*)*_a, (const char*)*_b, DIGEST256_LEN); +} + +/** Sort the list of DIGEST256_LEN-byte digests into ascending order. */ +void +smartlist_sort_digests256(smartlist_t *sl) +{ + smartlist_sort(sl, compare_digests256_); +} + +/** Return the most frequent member of the sorted list of DIGEST256_LEN + * digests in <b>sl</b> */ +const uint8_t * +smartlist_get_most_frequent_digest256(smartlist_t *sl) +{ + return smartlist_get_most_frequent(sl, compare_digests256_); +} + +/** Remove duplicate 256-bit digests from a sorted list, and free them with + * tor_free(). + */ +void +smartlist_uniq_digests256(smartlist_t *sl) +{ + smartlist_uniq(sl, compare_digests256_, tor_free_); +} diff --git a/src/lib/container/smartlist.h b/src/lib/container/smartlist.h new file mode 100644 index 0000000000..77682db03e --- /dev/null +++ b/src/lib/container/smartlist.h @@ -0,0 +1,168 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_SMARTLIST_H +#define TOR_SMARTLIST_H + +/** + * \file smartlist.h + * + * \brief Header for smartlist.c + **/ + +#include <stdarg.h> + +#include "lib/smartlist_core/smartlist_core.h" +#include "lib/smartlist_core/smartlist_foreach.h" +#include "lib/smartlist_core/smartlist_split.h" + +void smartlist_add_asprintf(struct smartlist_t *sl, const char *pattern, ...) + CHECK_PRINTF(2, 3); +void smartlist_add_vasprintf(struct smartlist_t *sl, const char *pattern, + va_list args) + CHECK_PRINTF(2, 0); +void smartlist_reverse(smartlist_t *sl); +void smartlist_string_remove(smartlist_t *sl, const char *element); +int smartlist_contains_string(const smartlist_t *sl, const char *element); +int smartlist_pos(const smartlist_t *sl, const void *element); +int smartlist_string_pos(const smartlist_t *, const char *elt); +int smartlist_contains_string_case(const smartlist_t *sl, const char *element); +int smartlist_contains_int_as_string(const smartlist_t *sl, int num); +int smartlist_strings_eq(const smartlist_t *sl1, const smartlist_t *sl2); +int smartlist_contains_digest(const smartlist_t *sl, const char *element); +int smartlist_ints_eq(const smartlist_t *sl1, const smartlist_t *sl2); +int smartlist_overlap(const smartlist_t *sl1, const smartlist_t *sl2); +void smartlist_intersect(smartlist_t *sl1, const smartlist_t *sl2); +void smartlist_subtract(smartlist_t *sl1, const smartlist_t *sl2); + +int smartlist_ptrs_eq(const smartlist_t *s1, + const smartlist_t *s2); + +void smartlist_sort(smartlist_t *sl, + int (*compare)(const void **a, const void **b)); +void *smartlist_get_most_frequent_(const smartlist_t *sl, + int (*compare)(const void **a, const void **b), + int *count_out); +#define smartlist_get_most_frequent(sl, compare) \ + smartlist_get_most_frequent_((sl), (compare), NULL) +void smartlist_uniq(smartlist_t *sl, + int (*compare)(const void **a, const void **b), + void (*free_fn)(void *elt)); + +void smartlist_sort_strings(smartlist_t *sl); +void smartlist_sort_digests(smartlist_t *sl); +void smartlist_sort_digests256(smartlist_t *sl); +void smartlist_sort_pointers(smartlist_t *sl); + +const char *smartlist_get_most_frequent_string(smartlist_t *sl); +const char *smartlist_get_most_frequent_string_(smartlist_t *sl, + int *count_out); +const uint8_t *smartlist_get_most_frequent_digest256(smartlist_t *sl); + +void smartlist_uniq_strings(smartlist_t *sl); +void smartlist_uniq_digests(smartlist_t *sl); +void smartlist_uniq_digests256(smartlist_t *sl); +void *smartlist_bsearch(const smartlist_t *sl, const void *key, + int (*compare)(const void *key, const void **member)); +int smartlist_bsearch_idx(const smartlist_t *sl, const void *key, + int (*compare)(const void *key, const void **member), + int *found_out); + +void smartlist_pqueue_add(smartlist_t *sl, + int (*compare)(const void *a, const void *b), + int idx_field_offset, + void *item); +void *smartlist_pqueue_pop(smartlist_t *sl, + int (*compare)(const void *a, const void *b), + int idx_field_offset); +void smartlist_pqueue_remove(smartlist_t *sl, + int (*compare)(const void *a, const void *b), + int idx_field_offset, + void *item); +void smartlist_pqueue_assert_ok(smartlist_t *sl, + int (*compare)(const void *a, const void *b), + int idx_field_offset); + +char *smartlist_join_strings(smartlist_t *sl, const char *join, int terminate, + size_t *len_out) ATTR_MALLOC; +char *smartlist_join_strings2(smartlist_t *sl, const char *join, + size_t join_len, int terminate, size_t *len_out) + ATTR_MALLOC; + +/* Helper: Given two lists of items, possibly of different types, such that + * both lists are sorted on some common field (as determined by a comparison + * expression <b>cmpexpr</b>), and such that one list (<b>sl1</b>) has no + * duplicates on the common field, loop through the lists in lockstep, and + * execute <b>unmatched_var2</b> on items in var2 that do not appear in + * var1. + * + * WARNING: It isn't safe to add remove elements from either list while the + * loop is in progress. + * + * Example use: + * SMARTLIST_FOREACH_JOIN(routerstatus_list, routerstatus_t *, rs, + * routerinfo_list, routerinfo_t *, ri, + * tor_memcmp(rs->identity_digest, ri->identity_digest, 20), + * log_info(LD_GENERAL,"No match for %s", ri->nickname)) { + * log_info(LD_GENERAL, "%s matches routerstatus %p", ri->nickname, rs); + * } SMARTLIST_FOREACH_JOIN_END(rs, ri); + **/ +/* The example above unpacks (approximately) to: + * int rs_sl_idx = 0, rs_sl_len = smartlist_len(routerstatus_list); + * int ri_sl_idx, ri_sl_len = smartlist_len(routerinfo_list); + * int rs_ri_cmp; + * routerstatus_t *rs; + * routerinfo_t *ri; + * for (; ri_sl_idx < ri_sl_len; ++ri_sl_idx) { + * ri = smartlist_get(routerinfo_list, ri_sl_idx); + * while (rs_sl_idx < rs_sl_len) { + * rs = smartlist_get(routerstatus_list, rs_sl_idx); + * rs_ri_cmp = tor_memcmp(rs->identity_digest, ri->identity_digest, 20); + * if (rs_ri_cmp > 0) { + * break; + * } else if (rs_ri_cmp == 0) { + * goto matched_ri; + * } else { + * ++rs_sl_idx; + * } + * } + * log_info(LD_GENERAL,"No match for %s", ri->nickname); + * continue; + * matched_ri: { + * log_info(LD_GENERAL,"%s matches with routerstatus %p",ri->nickname,rs); + * } + * } + */ +#define SMARTLIST_FOREACH_JOIN(sl1, type1, var1, sl2, type2, var2, \ + cmpexpr, unmatched_var2) \ + STMT_BEGIN \ + int var1 ## _sl_idx = 0, var1 ## _sl_len=(sl1)->num_used; \ + int var2 ## _sl_idx = 0, var2 ## _sl_len=(sl2)->num_used; \ + int var1 ## _ ## var2 ## _cmp; \ + type1 var1; \ + type2 var2; \ + for (; var2##_sl_idx < var2##_sl_len; ++var2##_sl_idx) { \ + var2 = (sl2)->list[var2##_sl_idx]; \ + while (var1##_sl_idx < var1##_sl_len) { \ + var1 = (sl1)->list[var1##_sl_idx]; \ + var1##_##var2##_cmp = (cmpexpr); \ + if (var1##_##var2##_cmp > 0) { \ + break; \ + } else if (var1##_##var2##_cmp == 0) { \ + goto matched_##var2; \ + } else { \ + ++var1##_sl_idx; \ + } \ + } \ + /* Ran out of v1, or no match for var2. */ \ + unmatched_var2; \ + continue; \ + matched_##var2: ; \ + +#define SMARTLIST_FOREACH_JOIN_END(var1, var2) \ + } \ + STMT_END + +#endif /* !defined(TOR_CONTAINER_H) */ diff --git a/src/lib/crypt_ops/.may_include b/src/lib/crypt_ops/.may_include new file mode 100644 index 0000000000..a0fa4ec05c --- /dev/null +++ b/src/lib/crypt_ops/.may_include @@ -0,0 +1,24 @@ +orconfig.h +lib/arch/*.h +lib/cc/*.h +lib/container/*.h +lib/crypt_ops/*.h +lib/ctime/*.h +lib/defs/*.h +lib/encoding/*.h +lib/fs/*.h +lib/lock/*.h +lib/malloc/*.h +lib/intmath/*.h +lib/sandbox/*.h +lib/string/*.h +lib/testsupport/testsupport.h +lib/thread/*.h +lib/log/*.h + +trunnel/pwbox.h + +keccak-tiny/*.h +ed25519/*.h + +siphash.h diff --git a/src/lib/crypt_ops/aes.h b/src/lib/crypt_ops/aes.h new file mode 100644 index 0000000000..7c774062d9 --- /dev/null +++ b/src/lib/crypt_ops/aes.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/* Implements a minimal interface to counter-mode AES. */ + +#ifndef TOR_AES_H +#define TOR_AES_H + +/** + * \file aes.h + * \brief Headers for aes.c + */ + +#include "lib/cc/torint.h" +#include "lib/malloc/malloc.h" + +typedef struct aes_cnt_cipher aes_cnt_cipher_t; + +aes_cnt_cipher_t* aes_new_cipher(const uint8_t *key, const uint8_t *iv, + int key_bits); +void aes_cipher_free_(aes_cnt_cipher_t *cipher); +#define aes_cipher_free(cipher) \ + FREE_AND_NULL(aes_cnt_cipher_t, aes_cipher_free_, (cipher)) +void aes_crypt_inplace(aes_cnt_cipher_t *cipher, char *data, size_t len); + +int evaluate_evp_for_aes(int force_value); +int evaluate_ctr_for_aes(void); + +#endif /* !defined(TOR_AES_H) */ diff --git a/src/lib/crypt_ops/aes_nss.c b/src/lib/crypt_ops/aes_nss.c new file mode 100644 index 0000000000..4eda5e5902 --- /dev/null +++ b/src/lib/crypt_ops/aes_nss.c @@ -0,0 +1,106 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file aes_nss.c + * \brief Use NSS to implement AES_CTR. + **/ + +#include "orconfig.h" +#include "lib/crypt_ops/aes.h" +#include "lib/crypt_ops/crypto_nss_mgt.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/log/util_bug.h" + +DISABLE_GCC_WARNING(strict-prototypes) +#include <pk11pub.h> +#include <secerr.h> +ENABLE_GCC_WARNING(strict-prototypes) + +aes_cnt_cipher_t * +aes_new_cipher(const uint8_t *key, const uint8_t *iv, + int key_bits) +{ + const CK_MECHANISM_TYPE ckm = CKM_AES_CTR; + SECItem keyItem = { .type = siBuffer, + .data = (unsigned char *)key, + .len = (key_bits / 8) }; + CK_AES_CTR_PARAMS params; + params.ulCounterBits = 128; + memcpy(params.cb, iv, 16); + SECItem ivItem = { .type = siBuffer, + .data = (unsigned char *)¶ms, + .len = sizeof(params) }; + PK11SlotInfo *slot = NULL; + PK11SymKey *keyObj = NULL; + SECItem *ivObj = NULL; + PK11Context *result = NULL; + + slot = PK11_GetBestSlot(ckm, NULL); + if (!slot) + goto err; + + keyObj = PK11_ImportSymKey(slot, ckm, PK11_OriginUnwrap, + CKA_ENCRYPT, &keyItem, NULL); + if (!keyObj) + goto err; + + ivObj = PK11_ParamFromIV(ckm, &ivItem); + if (!ivObj) + goto err; + + PORT_SetError(SEC_ERROR_IO); + result = PK11_CreateContextBySymKey(ckm, CKA_ENCRYPT, keyObj, ivObj); + + err: + memwipe(¶ms, 0, sizeof(params)); + if (ivObj) + SECITEM_FreeItem(ivObj, PR_TRUE); + if (keyObj) + PK11_FreeSymKey(keyObj); + if (slot) + PK11_FreeSlot(slot); + + tor_assert(result); + return (aes_cnt_cipher_t *)result; +} + +void +aes_cipher_free_(aes_cnt_cipher_t *cipher) +{ + if (!cipher) + return; + PK11_DestroyContext((PK11Context*) cipher, PR_TRUE); +} + +void +aes_crypt_inplace(aes_cnt_cipher_t *cipher, char *data_, size_t len_) +{ + tor_assert(len_ <= INT_MAX); + + SECStatus s; + PK11Context *ctx = (PK11Context*)cipher; + unsigned char *data = (unsigned char *)data_; + int len = (int) len_; + int result_len = 0; + + s = PK11_CipherOp(ctx, data, &result_len, len, data, len); + tor_assert(s == SECSuccess); + tor_assert(result_len == len); +} + +int +evaluate_evp_for_aes(int force_value) +{ + (void)force_value; + return 0; +} + +int +evaluate_ctr_for_aes(void) +{ + return 0; +} diff --git a/src/lib/crypt_ops/aes_openssl.c b/src/lib/crypt_ops/aes_openssl.c new file mode 100644 index 0000000000..2f985d4512 --- /dev/null +++ b/src/lib/crypt_ops/aes_openssl.c @@ -0,0 +1,410 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file aes_openssl.c + * \brief Use OpenSSL to implement AES_CTR. + **/ + +#include "orconfig.h" +#include "lib/crypt_ops/aes.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/log/util_bug.h" +#include "lib/arch/bytes.h" + +#ifdef _WIN32 /*wrkard for dtls1.h >= 0.9.8m of "#include <winsock.h>"*/ + #include <winsock2.h> + #include <ws2tcpip.h> +#endif + +#include "lib/crypt_ops/compat_openssl.h" +#include <openssl/opensslv.h> +#include "lib/crypt_ops/crypto_openssl_mgt.h" + +#if OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,0,0) +#error "We require OpenSSL >= 1.0.0" +#endif + +DISABLE_GCC_WARNING(redundant-decls) + +#include <stdlib.h> +#include <string.h> +#include <openssl/aes.h> +#include <openssl/evp.h> +#include <openssl/engine.h> +#include <openssl/modes.h> + +ENABLE_GCC_WARNING(redundant-decls) + +#include "lib/crypt_ops/aes.h" +#include "lib/log/log.h" +#include "lib/ctime/di_ops.h" + +#ifdef OPENSSL_NO_ENGINE +/* Android's OpenSSL seems to have removed all of its Engine support. */ +#define DISABLE_ENGINES +#endif + +/* We have five strategies for implementing AES counter mode. + * + * Best with x86 and x86_64: Use EVP_aes_*_ctr() and EVP_EncryptUpdate(). + * This is possible with OpenSSL 1.0.1, where the counter-mode implementation + * can use bit-sliced or vectorized AES or AESNI as appropriate. + * + * Otherwise: Pick the best possible AES block implementation that OpenSSL + * gives us, and the best possible counter-mode implementation, and combine + * them. + */ +#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_NOPATCH(1,1,0) + +/* With newer OpenSSL versions, the older fallback modes don't compile. So + * don't use them, even if we lack specific acceleration. */ + +#define USE_EVP_AES_CTR + +#elif OPENSSL_VERSION_NUMBER >= OPENSSL_V_NOPATCH(1,0,1) && \ + (defined(__i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64) || defined(__x86_64__) || \ + defined(_M_AMD64) || defined(_M_X64) || defined(__INTEL__)) + +#define USE_EVP_AES_CTR + +#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_V_NOPATCH(1,1,0) || ... */ + +/* We have 2 strategies for getting the AES block cipher: Via OpenSSL's + * AES_encrypt function, or via OpenSSL's EVP_EncryptUpdate function. + * + * If there's any hardware acceleration in play, we want to be using EVP_* so + * we can get it. Otherwise, we'll want AES_*, which seems to be about 5% + * faster than indirecting through the EVP layer. + */ + +/* We have 2 strategies for getting a plug-in counter mode: use our own, or + * use OpenSSL's. + * + * Here we have a counter mode that's faster than the one shipping with + * OpenSSL pre-1.0 (by about 10%!). But OpenSSL 1.0.0 added a counter mode + * implementation faster than the one here (by about 7%). So we pick which + * one to used based on the Openssl version above. (OpenSSL 1.0.0a fixed a + * critical bug in that counter mode implementation, so we need to test to + * make sure that we have a fixed version.) + */ + +#ifdef USE_EVP_AES_CTR + +/* We don't actually define the struct here. */ + +aes_cnt_cipher_t * +aes_new_cipher(const uint8_t *key, const uint8_t *iv, int key_bits) +{ + EVP_CIPHER_CTX *cipher = EVP_CIPHER_CTX_new(); + const EVP_CIPHER *c = NULL; + switch (key_bits) { + case 128: c = EVP_aes_128_ctr(); break; + case 192: c = EVP_aes_192_ctr(); break; + case 256: c = EVP_aes_256_ctr(); break; + default: tor_assert_unreached(); // LCOV_EXCL_LINE + } + EVP_EncryptInit(cipher, c, key, iv); + return (aes_cnt_cipher_t *) cipher; +} +void +aes_cipher_free_(aes_cnt_cipher_t *cipher_) +{ + if (!cipher_) + return; + EVP_CIPHER_CTX *cipher = (EVP_CIPHER_CTX *) cipher_; +#ifdef OPENSSL_1_1_API + EVP_CIPHER_CTX_reset(cipher); +#else + EVP_CIPHER_CTX_cleanup(cipher); +#endif + EVP_CIPHER_CTX_free(cipher); +} +void +aes_crypt_inplace(aes_cnt_cipher_t *cipher_, char *data, size_t len) +{ + int outl; + EVP_CIPHER_CTX *cipher = (EVP_CIPHER_CTX *) cipher_; + + tor_assert(len < INT_MAX); + + EVP_EncryptUpdate(cipher, (unsigned char*)data, + &outl, (unsigned char*)data, (int)len); +} +int +evaluate_evp_for_aes(int force_val) +{ + (void) force_val; + log_info(LD_CRYPTO, "This version of OpenSSL has a known-good EVP " + "counter-mode implementation. Using it."); + return 0; +} +int +evaluate_ctr_for_aes(void) +{ + return 0; +} +#else /* !(defined(USE_EVP_AES_CTR)) */ + +/*======================================================================*/ +/* Interface to AES code, and counter implementation */ + +/** Implements an AES counter-mode cipher. */ +struct aes_cnt_cipher { +/** This next element (however it's defined) is the AES key. */ + union { + EVP_CIPHER_CTX evp; + AES_KEY aes; + } key; + +#if !defined(WORDS_BIGENDIAN) +#define USING_COUNTER_VARS + /** These four values, together, implement a 128-bit counter, with + * counter0 as the low-order word and counter3 as the high-order word. */ + uint32_t counter3; + uint32_t counter2; + uint32_t counter1; + uint32_t counter0; +#endif /* !defined(WORDS_BIGENDIAN) */ + + union { + /** The counter, in big-endian order, as bytes. */ + uint8_t buf[16]; + /** The counter, in big-endian order, as big-endian words. Note that + * on big-endian platforms, this is redundant with counter3...0, + * so we just use these values instead. */ + uint32_t buf32[4]; + } ctr_buf; + + /** The encrypted value of ctr_buf. */ + uint8_t buf[16]; + /** Our current stream position within buf. */ + unsigned int pos; + + /** True iff we're using the evp implementation of this cipher. */ + uint8_t using_evp; +}; + +/** True iff we should prefer the EVP implementation for AES, either because + * we're testing it or because we have hardware acceleration configured */ +static int should_use_EVP = 0; + +/** Check whether we should use the EVP interface for AES. If <b>force_val</b> + * is nonnegative, we use use EVP iff it is true. Otherwise, we use EVP + * if there is an engine enabled for aes-ecb. */ +int +evaluate_evp_for_aes(int force_val) +{ + ENGINE *e; + + if (force_val >= 0) { + should_use_EVP = force_val; + return 0; + } +#ifdef DISABLE_ENGINES + should_use_EVP = 0; +#else + e = ENGINE_get_cipher_engine(NID_aes_128_ecb); + + if (e) { + log_info(LD_CRYPTO, "AES engine \"%s\" found; using EVP_* functions.", + ENGINE_get_name(e)); + should_use_EVP = 1; + } else { + log_info(LD_CRYPTO, "No AES engine found; using AES_* functions."); + should_use_EVP = 0; + } +#endif /* defined(DISABLE_ENGINES) */ + + return 0; +} + +/** Test the OpenSSL counter mode implementation to see whether it has the + * counter-mode bug from OpenSSL 1.0.0. If the implementation works, then + * we will use it for future encryption/decryption operations. + * + * We can't just look at the OpenSSL version, since some distributions update + * their OpenSSL packages without changing the version number. + **/ +int +evaluate_ctr_for_aes(void) +{ + /* Result of encrypting an all-zero block with an all-zero 128-bit AES key. + * This should be the same as encrypting an all-zero block with an all-zero + * 128-bit AES key in counter mode, starting at position 0 of the stream. + */ + static const unsigned char encrypt_zero[] = + "\x66\xe9\x4b\xd4\xef\x8a\x2c\x3b\x88\x4c\xfa\x59\xca\x34\x2b\x2e"; + unsigned char zero[16]; + unsigned char output[16]; + unsigned char ivec[16]; + unsigned char ivec_tmp[16]; + unsigned int pos, i; + AES_KEY key; + memset(zero, 0, sizeof(zero)); + memset(ivec, 0, sizeof(ivec)); + AES_set_encrypt_key(zero, 128, &key); + + pos = 0; + /* Encrypting a block one byte at a time should make the error manifest + * itself for known bogus openssl versions. */ + for (i=0; i<16; ++i) + AES_ctr128_encrypt(&zero[i], &output[i], 1, &key, ivec, ivec_tmp, &pos); + + if (fast_memneq(output, encrypt_zero, 16)) { + /* Counter mode is buggy */ + /* LCOV_EXCL_START */ + log_err(LD_CRYPTO, "This OpenSSL has a buggy version of counter mode; " + "quitting tor."); + exit(1); // exit ok: openssl is broken. + /* LCOV_EXCL_STOP */ + } + return 0; +} + +#if !defined(USING_COUNTER_VARS) +#define COUNTER(c, n) ((c)->ctr_buf.buf32[3-(n)]) +#else +#define COUNTER(c, n) ((c)->counter ## n) +#endif + +static void aes_set_key(aes_cnt_cipher_t *cipher, const uint8_t *key, + int key_bits); +static void aes_set_iv(aes_cnt_cipher_t *cipher, const uint8_t *iv); + +/** + * Return a newly allocated counter-mode AES128 cipher implementation, + * using the 128-bit key <b>key</b> and the 128-bit IV <b>iv</b>. + */ +aes_cnt_cipher_t* +aes_new_cipher(const uint8_t *key, const uint8_t *iv, int bits) +{ + aes_cnt_cipher_t* result = tor_malloc_zero(sizeof(aes_cnt_cipher_t)); + + aes_set_key(result, key, bits); + aes_set_iv(result, iv); + + return result; +} + +/** Set the key of <b>cipher</b> to <b>key</b>, which is + * <b>key_bits</b> bits long (must be 128, 192, or 256). Also resets + * the counter to 0. + */ +static void +aes_set_key(aes_cnt_cipher_t *cipher, const uint8_t *key, int key_bits) +{ + if (should_use_EVP) { + const EVP_CIPHER *c = 0; + switch (key_bits) { + case 128: c = EVP_aes_128_ecb(); break; + case 192: c = EVP_aes_192_ecb(); break; + case 256: c = EVP_aes_256_ecb(); break; + default: tor_assert(0); // LCOV_EXCL_LINE + } + EVP_EncryptInit(&cipher->key.evp, c, key, NULL); + cipher->using_evp = 1; + } else { + AES_set_encrypt_key(key, key_bits,&cipher->key.aes); + cipher->using_evp = 0; + } + +#ifdef USING_COUNTER_VARS + cipher->counter0 = 0; + cipher->counter1 = 0; + cipher->counter2 = 0; + cipher->counter3 = 0; +#endif /* defined(USING_COUNTER_VARS) */ + + memset(cipher->ctr_buf.buf, 0, sizeof(cipher->ctr_buf.buf)); + + cipher->pos = 0; + + memset(cipher->buf, 0, sizeof(cipher->buf)); +} + +/** Release storage held by <b>cipher</b> + */ +void +aes_cipher_free_(aes_cnt_cipher_t *cipher) +{ + if (!cipher) + return; + if (cipher->using_evp) { + EVP_CIPHER_CTX_cleanup(&cipher->key.evp); + } + memwipe(cipher, 0, sizeof(aes_cnt_cipher_t)); + tor_free(cipher); +} + +#if defined(USING_COUNTER_VARS) +#define UPDATE_CTR_BUF(c, n) STMT_BEGIN \ + (c)->ctr_buf.buf32[3-(n)] = htonl((c)->counter ## n); \ + STMT_END +#else +#define UPDATE_CTR_BUF(c, n) +#endif /* defined(USING_COUNTER_VARS) */ + +/* Helper function to use EVP with openssl's counter-mode wrapper. */ +static void +evp_block128_fn(const uint8_t in[16], + uint8_t out[16], + const void *key) +{ + EVP_CIPHER_CTX *ctx = (void*)key; + int inl=16, outl=16; + EVP_EncryptUpdate(ctx, out, &outl, in, inl); +} + +/** Encrypt <b>len</b> bytes from <b>input</b>, storing the results in place. + * Uses the key in <b>cipher</b>, and advances the counter by <b>len</b> bytes + * as it encrypts. + */ +void +aes_crypt_inplace(aes_cnt_cipher_t *cipher, char *data, size_t len) +{ + /* Note that the "128" below refers to the length of the counter, + * not the length of the AES key. */ + if (cipher->using_evp) { + /* In openssl 1.0.0, there's an if'd out EVP_aes_128_ctr in evp.h. If + * it weren't disabled, it might be better just to use that. + */ + CRYPTO_ctr128_encrypt((const unsigned char *)data, + (unsigned char *)data, + len, + &cipher->key.evp, + cipher->ctr_buf.buf, + cipher->buf, + &cipher->pos, + evp_block128_fn); + } else { + AES_ctr128_encrypt((const unsigned char *)data, + (unsigned char *)data, + len, + &cipher->key.aes, + cipher->ctr_buf.buf, + cipher->buf, + &cipher->pos); + } +} + +/** Reset the 128-bit counter of <b>cipher</b> to the 16-bit big-endian value + * in <b>iv</b>. */ +static void +aes_set_iv(aes_cnt_cipher_t *cipher, const uint8_t *iv) +{ +#ifdef USING_COUNTER_VARS + cipher->counter3 = tor_ntohl(get_uint32(iv)); + cipher->counter2 = tor_ntohl(get_uint32(iv+4)); + cipher->counter1 = tor_ntohl(get_uint32(iv+8)); + cipher->counter0 = tor_ntohl(get_uint32(iv+12)); +#endif /* defined(USING_COUNTER_VARS) */ + cipher->pos = 0; + memcpy(cipher->ctr_buf.buf, iv, 16); +} + +#endif /* defined(USE_EVP_AES_CTR) */ diff --git a/src/lib/crypt_ops/compat_openssl.h b/src/lib/crypt_ops/compat_openssl.h new file mode 100644 index 0000000000..9c10386c34 --- /dev/null +++ b/src/lib/crypt_ops/compat_openssl.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_COMPAT_OPENSSL_H +#define TOR_COMPAT_OPENSSL_H + +#include "orconfig.h" + +#ifdef ENABLE_OPENSSL + +#include <openssl/opensslv.h> +#include "lib/crypt_ops/crypto_openssl_mgt.h" + +/** + * \file compat_openssl.h + * + * \brief compatibility definitions for working with different openssl forks + **/ + +#if !defined(LIBRESSL_VERSION_NUMBER) && \ + OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,0,1) +#error "We require OpenSSL >= 1.0.1" +#endif + +#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) && \ + ! defined(LIBRESSL_VERSION_NUMBER) +/* We define this macro if we're trying to build with the majorly refactored + * API in OpenSSL 1.1 */ +#define OPENSSL_1_1_API +#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) && ... */ + +#ifndef OPENSSL_VERSION +#define OPENSSL_VERSION SSLEAY_VERSION +#endif + +#ifndef OPENSSL_1_1_API +#define OpenSSL_version(v) SSLeay_version(v) +#define OpenSSL_version_num() SSLeay() +#define RAND_OpenSSL() RAND_SSLeay() +#define STATE_IS_SW_SERVER_HELLO(st) \ + (((st) == SSL3_ST_SW_SRVR_HELLO_A) || \ + ((st) == SSL3_ST_SW_SRVR_HELLO_B)) +#define OSSL_HANDSHAKE_STATE int +#define CONST_IF_OPENSSL_1_1_API +#else /* !(!defined(OPENSSL_1_1_API)) */ +#define STATE_IS_SW_SERVER_HELLO(st) \ + ((st) == TLS_ST_SW_SRVR_HELLO) +#define CONST_IF_OPENSSL_1_1_API const +#endif /* !defined(OPENSSL_1_1_API) */ + +#endif /* defined(ENABLE_OPENSSL) */ + +#endif /* !defined(TOR_COMPAT_OPENSSL_H) */ + diff --git a/src/lib/crypt_ops/crypto_cipher.c b/src/lib/crypt_ops/crypto_cipher.c new file mode 100644 index 0000000000..7bc2edad54 --- /dev/null +++ b/src/lib/crypt_ops/crypto_cipher.c @@ -0,0 +1,190 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_cipher.c + * \brief Symmetric cryptography (low-level) with AES. + **/ + +#include "orconfig.h" + +#include "lib/crypt_ops/crypto_cipher.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" + +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/cc/torint.h" +#include "lib/crypt_ops/aes.h" + +#include <string.h> + +/** Allocate and return a new symmetric cipher using the provided key and iv. + * The key is <b>bits</b> bits long; the IV is CIPHER_IV_LEN bytes. Both + * must be provided. Key length must be 128, 192, or 256 */ +crypto_cipher_t * +crypto_cipher_new_with_iv_and_bits(const uint8_t *key, + const uint8_t *iv, + int bits) +{ + tor_assert(key); + tor_assert(iv); + + return aes_new_cipher((const uint8_t*)key, (const uint8_t*)iv, bits); +} + +/** Allocate and return a new symmetric cipher using the provided key and iv. + * The key is CIPHER_KEY_LEN bytes; the IV is CIPHER_IV_LEN bytes. Both + * must be provided. + */ +crypto_cipher_t * +crypto_cipher_new_with_iv(const char *key, const char *iv) +{ + return crypto_cipher_new_with_iv_and_bits((uint8_t*)key, (uint8_t*)iv, + 128); +} + +/** Return a new crypto_cipher_t with the provided <b>key</b> and an IV of all + * zero bytes and key length <b>bits</b>. Key length must be 128, 192, or + * 256. */ +crypto_cipher_t * +crypto_cipher_new_with_bits(const char *key, int bits) +{ + char zeroiv[CIPHER_IV_LEN]; + memset(zeroiv, 0, sizeof(zeroiv)); + return crypto_cipher_new_with_iv_and_bits((uint8_t*)key, (uint8_t*)zeroiv, + bits); +} + +/** Return a new crypto_cipher_t with the provided <b>key</b> (of + * CIPHER_KEY_LEN bytes) and an IV of all zero bytes. */ +crypto_cipher_t * +crypto_cipher_new(const char *key) +{ + return crypto_cipher_new_with_bits(key, 128); +} + +/** Free a symmetric cipher. + */ +void +crypto_cipher_free_(crypto_cipher_t *env) +{ + if (!env) + return; + + aes_cipher_free(env); +} + +/* symmetric crypto */ + +/** Encrypt <b>fromlen</b> bytes from <b>from</b> using the cipher + * <b>env</b>; on success, store the result to <b>to</b> and return 0. + * Does not check for failure. + */ +int +crypto_cipher_encrypt(crypto_cipher_t *env, char *to, + const char *from, size_t fromlen) +{ + tor_assert(env); + tor_assert(env); + tor_assert(from); + tor_assert(fromlen); + tor_assert(to); + tor_assert(fromlen < SIZE_T_CEILING); + + memcpy(to, from, fromlen); + aes_crypt_inplace(env, to, fromlen); + return 0; +} + +/** Decrypt <b>fromlen</b> bytes from <b>from</b> using the cipher + * <b>env</b>; on success, store the result to <b>to</b> and return 0. + * Does not check for failure. + */ +int +crypto_cipher_decrypt(crypto_cipher_t *env, char *to, + const char *from, size_t fromlen) +{ + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(fromlen < SIZE_T_CEILING); + + memcpy(to, from, fromlen); + aes_crypt_inplace(env, to, fromlen); + return 0; +} + +/** Encrypt <b>len</b> bytes on <b>from</b> using the cipher in <b>env</b>; + * on success. Does not check for failure. + */ +void +crypto_cipher_crypt_inplace(crypto_cipher_t *env, char *buf, size_t len) +{ + tor_assert(len < SIZE_T_CEILING); + aes_crypt_inplace(env, buf, len); +} + +/** Encrypt <b>fromlen</b> bytes (at least 1) from <b>from</b> with the key in + * <b>key</b> to the buffer in <b>to</b> of length + * <b>tolen</b>. <b>tolen</b> must be at least <b>fromlen</b> plus + * CIPHER_IV_LEN bytes for the initialization vector. On success, return the + * number of bytes written, on failure, return -1. + */ +int +crypto_cipher_encrypt_with_iv(const char *key, + char *to, size_t tolen, + const char *from, size_t fromlen) +{ + crypto_cipher_t *cipher; + tor_assert(from); + tor_assert(to); + tor_assert(fromlen < INT_MAX); + + if (fromlen < 1) + return -1; + if (tolen < fromlen + CIPHER_IV_LEN) + return -1; + + char iv[CIPHER_IV_LEN]; + crypto_rand(iv, sizeof(iv)); + cipher = crypto_cipher_new_with_iv(key, iv); + + memcpy(to, iv, CIPHER_IV_LEN); + crypto_cipher_encrypt(cipher, to+CIPHER_IV_LEN, from, fromlen); + crypto_cipher_free(cipher); + memwipe(iv, 0, sizeof(iv)); + return (int)(fromlen + CIPHER_IV_LEN); +} + +/** Decrypt <b>fromlen</b> bytes (at least 1+CIPHER_IV_LEN) from <b>from</b> + * with the key in <b>key</b> to the buffer in <b>to</b> of length + * <b>tolen</b>. <b>tolen</b> must be at least <b>fromlen</b> minus + * CIPHER_IV_LEN bytes for the initialization vector. On success, return the + * number of bytes written, on failure, return -1. + */ +int +crypto_cipher_decrypt_with_iv(const char *key, + char *to, size_t tolen, + const char *from, size_t fromlen) +{ + crypto_cipher_t *cipher; + tor_assert(key); + tor_assert(from); + tor_assert(to); + tor_assert(fromlen < INT_MAX); + + if (fromlen <= CIPHER_IV_LEN) + return -1; + if (tolen < fromlen - CIPHER_IV_LEN) + return -1; + + cipher = crypto_cipher_new_with_iv(key, from); + + crypto_cipher_encrypt(cipher, to, from+CIPHER_IV_LEN, fromlen-CIPHER_IV_LEN); + crypto_cipher_free(cipher); + return (int)(fromlen - CIPHER_IV_LEN); +} diff --git a/src/lib/crypt_ops/crypto_cipher.h b/src/lib/crypt_ops/crypto_cipher.h new file mode 100644 index 0000000000..cc4fbf7a41 --- /dev/null +++ b/src/lib/crypt_ops/crypto_cipher.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_cipher.h + * + * \brief Headers for crypto_cipher.c + **/ + +#ifndef TOR_CRYPTO_CIPHER_H +#define TOR_CRYPTO_CIPHER_H + +#include "orconfig.h" + +#include <stdio.h> +#include "lib/cc/torint.h" + +/** Length of our symmetric cipher's keys of 128-bit. */ +#define CIPHER_KEY_LEN 16 +/** Length of our symmetric cipher's IV of 128-bit. */ +#define CIPHER_IV_LEN 16 +/** Length of our symmetric cipher's keys of 256-bit. */ +#define CIPHER256_KEY_LEN 32 + +typedef struct aes_cnt_cipher crypto_cipher_t; + +/* environment setup */ +crypto_cipher_t *crypto_cipher_new(const char *key); +crypto_cipher_t *crypto_cipher_new_with_bits(const char *key, int bits); +crypto_cipher_t *crypto_cipher_new_with_iv(const char *key, const char *iv); +crypto_cipher_t *crypto_cipher_new_with_iv_and_bits(const uint8_t *key, + const uint8_t *iv, + int bits); +void crypto_cipher_free_(crypto_cipher_t *env); +#define crypto_cipher_free(c) \ + FREE_AND_NULL(crypto_cipher_t, crypto_cipher_free_, (c)) + +/* symmetric crypto */ +const char *crypto_cipher_get_key(crypto_cipher_t *env); + +int crypto_cipher_encrypt(crypto_cipher_t *env, char *to, + const char *from, size_t fromlen); +int crypto_cipher_decrypt(crypto_cipher_t *env, char *to, + const char *from, size_t fromlen); +void crypto_cipher_crypt_inplace(crypto_cipher_t *env, char *d, size_t len); + +int crypto_cipher_encrypt_with_iv(const char *key, + char *to, size_t tolen, + const char *from, size_t fromlen); +int crypto_cipher_decrypt_with_iv(const char *key, + char *to, size_t tolen, + const char *from, size_t fromlen); + +#endif /* !defined(TOR_CRYPTO_H) */ diff --git a/src/lib/crypt_ops/crypto_curve25519.c b/src/lib/crypt_ops/crypto_curve25519.c new file mode 100644 index 0000000000..de4e17a296 --- /dev/null +++ b/src/lib/crypt_ops/crypto_curve25519.c @@ -0,0 +1,366 @@ +/* Copyright (c) 2012-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_curve25519.c + * + * \brief Wrapper code for a curve25519 implementation. + * + * Curve25519 is an Elliptic-Curve Diffie Hellman handshake, designed by + * Dan Bernstein. For more information, see https://cr.yp.to/ecdh.html + * + * Tor uses Curve25519 as the basis of its "ntor" circuit extension + * handshake, and in related code. The functions in this module are + * used to find the most suitable available Curve25519 implementation, + * to provide wrappers around it, and so on. + */ + +#define CRYPTO_CURVE25519_PRIVATE +#include "orconfig.h" +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#include "lib/ctime/di_ops.h" +#include "lib/crypt_ops/crypto_curve25519.h" +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/crypt_ops/crypto_format.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +#include "ed25519/donna/ed25519_donna_tor.h" + +#include <string.h> + +/* ============================== + Part 1: wrap a suitable curve25519 implementation as curve25519_impl + ============================== */ + +#ifdef USE_CURVE25519_DONNA +int curve25519_donna(uint8_t *mypublic, + const uint8_t *secret, const uint8_t *basepoint); +#endif +#ifdef USE_CURVE25519_NACL +#ifdef HAVE_CRYPTO_SCALARMULT_CURVE25519_H +#include <crypto_scalarmult_curve25519.h> +#elif defined(HAVE_NACL_CRYPTO_SCALARMULT_CURVE25519_H) +#include <nacl/crypto_scalarmult_curve25519.h> +#endif +#endif /* defined(USE_CURVE25519_NACL) */ + +static void pick_curve25519_basepoint_impl(void); + +/** This is set to 1 if we have an optimized Ed25519-based + * implementation for multiplying a value by the basepoint; to 0 if we + * don't, and to -1 if we haven't checked. */ +static int curve25519_use_ed = -1; + +/** + * Helper function: call the most appropriate backend to compute the + * scalar "secret" times the point "point". Store the result in + * "output". Return 0 on success, negative on failure. + **/ +STATIC int +curve25519_impl(uint8_t *output, const uint8_t *secret, + const uint8_t *point) +{ + uint8_t bp[CURVE25519_PUBKEY_LEN]; + int r; + memcpy(bp, point, CURVE25519_PUBKEY_LEN); + /* Clear the high bit, in case our backend foolishly looks at it. */ + bp[31] &= 0x7f; +#ifdef USE_CURVE25519_DONNA + r = curve25519_donna(output, secret, bp); +#elif defined(USE_CURVE25519_NACL) + r = crypto_scalarmult_curve25519(output, secret, bp); +#else +#error "No implementation of curve25519 is available." +#endif /* defined(USE_CURVE25519_DONNA) || ... */ + memwipe(bp, 0, sizeof(bp)); + return r; +} + +/** + * Helper function: Multiply the scalar "secret" by the Curve25519 + * basepoint (X=9), and store the result in "output". Return 0 on + * success, -1 on failure. + */ +STATIC int +curve25519_basepoint_impl(uint8_t *output, const uint8_t *secret) +{ + int r = 0; + if (BUG(curve25519_use_ed == -1)) { + /* LCOV_EXCL_START - Only reached if we forgot to call curve25519_init() */ + pick_curve25519_basepoint_impl(); + /* LCOV_EXCL_STOP */ + } + + /* TODO: Someone should benchmark curved25519_scalarmult_basepoint versus + * an optimized NaCl build to see which should be used when compiled with + * NaCl available. I suspected that the ed25519 optimization always wins. + */ + if (PREDICT_LIKELY(curve25519_use_ed == 1)) { + curved25519_scalarmult_basepoint_donna(output, secret); + r = 0; + } else { + static const uint8_t basepoint[32] = {9}; + r = curve25519_impl(output, secret, basepoint); + } + return r; +} + +/** + * Override the decision of whether to use the Ed25519-based basepoint + * multiply function. Used for testing. + */ +void +curve25519_set_impl_params(int use_ed) +{ + curve25519_use_ed = use_ed; +} + +/* ============================== + Part 2: Wrap curve25519_impl with some convenience types and functions. + ============================== */ + +/** + * Return true iff a curve25519_public_key_t seems valid. (It's not necessary + * to see if the point is on the curve, since the twist is also secure, but we + * do need to make sure that it isn't the point at infinity.) */ +int +curve25519_public_key_is_ok(const curve25519_public_key_t *key) +{ + return !safe_mem_is_zero(key->public_key, CURVE25519_PUBKEY_LEN); +} + +/** + * Generate CURVE25519_SECKEY_LEN random bytes in <b>out</b>. If + * <b>extra_strong</b> is true, this key is possibly going to get used more + * than once, so use a better-than-usual RNG. Return 0 on success, -1 on + * failure. + * + * This function does not adjust the output of the RNG at all; the will caller + * will need to clear or set the appropriate bits to make curve25519 work. + */ +int +curve25519_rand_seckey_bytes(uint8_t *out, int extra_strong) +{ + if (extra_strong) + crypto_strongest_rand(out, CURVE25519_SECKEY_LEN); + else + crypto_rand((char*)out, CURVE25519_SECKEY_LEN); + + return 0; +} + +/** Generate a new keypair and return the secret key. If <b>extra_strong</b> + * is true, this key is possibly going to get used more than once, so + * use a better-than-usual RNG. Return 0 on success, -1 on failure. */ +int +curve25519_secret_key_generate(curve25519_secret_key_t *key_out, + int extra_strong) +{ + if (curve25519_rand_seckey_bytes(key_out->secret_key, extra_strong) < 0) + return -1; + + key_out->secret_key[0] &= 248; + key_out->secret_key[31] &= 127; + key_out->secret_key[31] |= 64; + + return 0; +} + +/** + * Given a secret key in <b>seckey</b>, create the corresponding public + * key in <b>key_out</b>. + */ +void +curve25519_public_key_generate(curve25519_public_key_t *key_out, + const curve25519_secret_key_t *seckey) +{ + curve25519_basepoint_impl(key_out->public_key, seckey->secret_key); +} + +/** + * Construct a new keypair in *<b>keypair_out</b>. If <b>extra_strong</b> + * is true, this key is possibly going to get used more than once, so + * use a better-than-usual RNG. Return 0 on success, -1 on failure. */ +int +curve25519_keypair_generate(curve25519_keypair_t *keypair_out, + int extra_strong) +{ + if (curve25519_secret_key_generate(&keypair_out->seckey, extra_strong) < 0) + return -1; + curve25519_public_key_generate(&keypair_out->pubkey, &keypair_out->seckey); + return 0; +} + +/** Store the keypair <b>keypair</b>, including its secret and public + * parts, to the file <b>fname</b>. Use the string tag <b>tag</b> to + * distinguish this from other Curve25519 keypairs. Return 0 on success, + * -1 on failure. + * + * See crypto_write_tagged_contents_to_file() for more information on + * the metaformat used for these keys.*/ +int +curve25519_keypair_write_to_file(const curve25519_keypair_t *keypair, + const char *fname, + const char *tag) +{ + uint8_t contents[CURVE25519_SECKEY_LEN + CURVE25519_PUBKEY_LEN]; + int r; + + memcpy(contents, keypair->seckey.secret_key, CURVE25519_SECKEY_LEN); + memcpy(contents+CURVE25519_SECKEY_LEN, + keypair->pubkey.public_key, CURVE25519_PUBKEY_LEN); + + r = crypto_write_tagged_contents_to_file(fname, + "c25519v1", + tag, + contents, + sizeof(contents)); + + memwipe(contents, 0, sizeof(contents)); + return r; +} + +/** Read a curve25519 keypair from a file named <b>fname</b> created by + * curve25519_keypair_write_to_file(). Store the keypair in + * <b>keypair_out</b>, and the associated tag string in <b>tag_out</b>. + * Return 0 on success, and -1 on failure. */ +int +curve25519_keypair_read_from_file(curve25519_keypair_t *keypair_out, + char **tag_out, + const char *fname) +{ + uint8_t content[CURVE25519_SECKEY_LEN + CURVE25519_PUBKEY_LEN]; + ssize_t len; + int r = -1; + + len = crypto_read_tagged_contents_from_file(fname, "c25519v1", tag_out, + content, sizeof(content)); + if (len != sizeof(content)) + goto end; + + /* Make sure that the public key matches the secret key */ + memcpy(keypair_out->seckey.secret_key, content, CURVE25519_SECKEY_LEN); + curve25519_public_key_generate(&keypair_out->pubkey, &keypair_out->seckey); + if (tor_memneq(keypair_out->pubkey.public_key, + content + CURVE25519_SECKEY_LEN, + CURVE25519_PUBKEY_LEN)) + goto end; + + r = 0; + + end: + memwipe(content, 0, sizeof(content)); + if (r != 0) { + memset(keypair_out, 0, sizeof(*keypair_out)); + tor_free(*tag_out); + } + return r; +} + +/** Perform the curve25519 ECDH handshake with <b>skey</b> and <b>pkey</b>, + * writing CURVE25519_OUTPUT_LEN bytes of output into <b>output</b>. */ +void +curve25519_handshake(uint8_t *output, + const curve25519_secret_key_t *skey, + const curve25519_public_key_t *pkey) +{ + curve25519_impl(output, skey->secret_key, pkey->public_key); +} + +/** Check whether the ed25519-based curve25519 basepoint optimization seems to + * be working. If so, return 0; otherwise return -1. */ +static int +curve25519_basepoint_spot_check(void) +{ + static const uint8_t alicesk[32] = { + 0x77,0x07,0x6d,0x0a,0x73,0x18,0xa5,0x7d, + 0x3c,0x16,0xc1,0x72,0x51,0xb2,0x66,0x45, + 0xdf,0x4c,0x2f,0x87,0xeb,0xc0,0x99,0x2a, + 0xb1,0x77,0xfb,0xa5,0x1d,0xb9,0x2c,0x2a + }; + static const uint8_t alicepk[32] = { + 0x85,0x20,0xf0,0x09,0x89,0x30,0xa7,0x54, + 0x74,0x8b,0x7d,0xdc,0xb4,0x3e,0xf7,0x5a, + 0x0d,0xbf,0x3a,0x0d,0x26,0x38,0x1a,0xf4, + 0xeb,0xa4,0xa9,0x8e,0xaa,0x9b,0x4e,0x6a + }; + const int loop_max=8; + int save_use_ed = curve25519_use_ed; + unsigned char e1[32], e2[32]; + unsigned char x[32],y[32]; + int i; + int r=0; + + memset(x, 0, sizeof(x)); + memset(y, 0, sizeof(y)); + memset(e1, 0, sizeof(e1)); + memset(e2, 0, sizeof(e2)); + e1[0]=5; + e2[0]=5; + + /* Check the most basic possible sanity via the test secret/public key pair + * used in "Cryptography in NaCl - 2. Secret keys and public keys". This + * may catch catastrophic failures on systems where Curve25519 is expensive, + * without requiring a ton of key generation. + */ + curve25519_use_ed = 1; + r |= curve25519_basepoint_impl(x, alicesk); + if (fast_memneq(x, alicepk, 32)) + goto fail; + + /* Ok, the optimization appears to produce passable results, try a few more + * values, maybe there's something subtle wrong. + */ + for (i = 0; i < loop_max; ++i) { + curve25519_use_ed = 0; + r |= curve25519_basepoint_impl(x, e1); + curve25519_use_ed = 1; + r |= curve25519_basepoint_impl(y, e2); + if (fast_memneq(x,y,32)) + goto fail; + memcpy(e1, x, 32); + memcpy(e2, x, 32); + } + + goto end; + // LCOV_EXCL_START -- we can only hit this code if there is a bug in our + // curve25519-basepoint implementation. + fail: + r = -1; + // LCOV_EXCL_STOP + end: + curve25519_use_ed = save_use_ed; + return r; +} + +/** Choose whether to use the ed25519-based curve25519-basepoint + * implementation. */ +static void +pick_curve25519_basepoint_impl(void) +{ + curve25519_use_ed = 1; + + if (curve25519_basepoint_spot_check() == 0) + return; + + /* LCOV_EXCL_START + * only reachable if our basepoint implementation broken */ + log_warn(LD_BUG|LD_CRYPTO, "The ed25519-based curve25519 basepoint " + "multiplication seems broken; using the curve25519 " + "implementation."); + curve25519_use_ed = 0; + /* LCOV_EXCL_STOP */ +} + +/** Initialize the curve25519 implementations. This is necessary if you're + * going to use them in a multithreaded setting, and not otherwise. */ +void +curve25519_init(void) +{ + pick_curve25519_basepoint_impl(); +} diff --git a/src/lib/crypt_ops/crypto_curve25519.h b/src/lib/crypt_ops/crypto_curve25519.h new file mode 100644 index 0000000000..061a7a3505 --- /dev/null +++ b/src/lib/crypt_ops/crypto_curve25519.h @@ -0,0 +1,85 @@ +/* Copyright (c) 2012-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_curve25519.h + * \brief Header for crypto_curve25519.c + **/ + +#ifndef TOR_CRYPTO_CURVE25519_H +#define TOR_CRYPTO_CURVE25519_H + +#include "lib/testsupport/testsupport.h" +#include "lib/cc/torint.h" +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/crypt_ops/crypto_openssl_mgt.h" +#include "lib/defs/x25519_sizes.h" + +/** Wrapper type for a curve25519 public key. + * + * (We define a separate type for these to make it less likely that we'll + * mistake them for secret keys.) + * */ +typedef struct curve25519_public_key_t { + uint8_t public_key[CURVE25519_PUBKEY_LEN]; +} curve25519_public_key_t; + +/** Wrapper type for a curve25519 secret key + * + * (We define a separate type for these to make it less likely that we'll + * mistake them for public keys.) + **/ +typedef struct curve25519_secret_key_t { + uint8_t secret_key[CURVE25519_SECKEY_LEN]; +} curve25519_secret_key_t; + +/** A paired public and private key for curve25519. **/ +typedef struct curve25519_keypair_t { + curve25519_public_key_t pubkey; + curve25519_secret_key_t seckey; +} curve25519_keypair_t; + +/* These functions require that we actually know how to use curve25519 keys. + * The other data structures and functions in this header let us parse them, + * store them, and move them around. + */ + +int curve25519_public_key_is_ok(const curve25519_public_key_t *); + +int curve25519_secret_key_generate(curve25519_secret_key_t *key_out, + int extra_strong); +void curve25519_public_key_generate(curve25519_public_key_t *key_out, + const curve25519_secret_key_t *seckey); +int curve25519_keypair_generate(curve25519_keypair_t *keypair_out, + int extra_strong); + +void curve25519_handshake(uint8_t *output, + const curve25519_secret_key_t *, + const curve25519_public_key_t *); + +int curve25519_keypair_write_to_file(const curve25519_keypair_t *keypair, + const char *fname, + const char *tag); + +int curve25519_keypair_read_from_file(curve25519_keypair_t *keypair_out, + char **tag_out, + const char *fname); + +int curve25519_rand_seckey_bytes(uint8_t *out, int extra_strong); + +#ifdef CRYPTO_CURVE25519_PRIVATE +STATIC int curve25519_impl(uint8_t *output, const uint8_t *secret, + const uint8_t *basepoint); + +STATIC int curve25519_basepoint_impl(uint8_t *output, const uint8_t *secret); +#endif /* defined(CRYPTO_CURVE25519_PRIVATE) */ + +int curve25519_public_from_base64(curve25519_public_key_t *pkey, + const char *input); +int curve25519_public_to_base64(char *output, + const curve25519_public_key_t *pkey); + +void curve25519_set_impl_params(int use_ed); +void curve25519_init(void); + +#endif /* !defined(TOR_CRYPTO_CURVE25519_H) */ diff --git a/src/lib/crypt_ops/crypto_dh.c b/src/lib/crypt_ops/crypto_dh.c new file mode 100644 index 0000000000..4be7948761 --- /dev/null +++ b/src/lib/crypt_ops/crypto_dh.c @@ -0,0 +1,113 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_dh.c + * \brief Block of functions related with DH utilities and operations. + * over Z_p. We aren't using this for any new crypto -- EC is more + * efficient. + **/ + +#include "lib/crypt_ops/compat_openssl.h" +#include "lib/crypt_ops/crypto_dh.h" +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/crypt_ops/crypto_hkdf.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +/** Our DH 'g' parameter */ +const unsigned DH_GENERATOR = 2; +/** This is the 1024-bit safe prime that Apache uses for its DH stuff; see + * modules/ssl/ssl_engine_dh.c; Apache also uses a generator of 2 with this + * prime. + */ +const char TLS_DH_PRIME[] = + "D67DE440CBBBDC1936D693D34AFD0AD50C84D239A45F520BB88174CB98" + "BCE951849F912E639C72FB13B4B4D7177E16D55AC179BA420B2A29FE324A" + "467A635E81FF5901377BEDDCFD33168A461AAD3B72DAE8860078045B07A7" + "DBCA7874087D1510EA9FCC9DDD330507DD62DB88AEAA747DE0F4D6E2BD68" + "B0E7393E0F24218EB3"; +/** + * This is from rfc2409, section 6.2. It's a safe prime, and + * supposedly it equals: + * 2^1024 - 2^960 - 1 + 2^64 * { [2^894 pi] + 129093 }. + */ +const char OAKLEY_PRIME_2[] = + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + "49286651ECE65381FFFFFFFFFFFFFFFF"; + +void +crypto_dh_init(void) +{ +#ifdef ENABLE_OPENSSL + crypto_dh_init_openssl(); +#endif +#ifdef ENABLE_NSS + crypto_dh_init_nss(); +#endif +} + +void +crypto_dh_free_all(void) +{ +#ifdef ENABLE_OPENSSL + crypto_dh_free_all_openssl(); +#endif +#ifdef ENABLE_NSS + crypto_dh_free_all_nss(); +#endif +} + +/** Given a DH key exchange object, and our peer's value of g^y (as a + * <b>pubkey_len</b>-byte value in <b>pubkey</b>) generate + * <b>secret_bytes_out</b> bytes of shared key material and write them + * to <b>secret_out</b>. Return the number of bytes generated on success, + * or -1 on failure. + * + * (We generate key material by computing + * SHA1( g^xy || "\x00" ) || SHA1( g^xy || "\x01" ) || ... + * where || is concatenation.) + */ +ssize_t +crypto_dh_compute_secret(int severity, crypto_dh_t *dh, + const char *pubkey, size_t pubkey_len, + char *secret_out, size_t secret_bytes_out) +{ + tor_assert(secret_bytes_out/DIGEST_LEN <= 255); + + unsigned char *secret_tmp = NULL; + size_t secret_len=0, secret_tmp_len=0; + secret_tmp_len = crypto_dh_get_bytes(dh); + secret_tmp = tor_malloc(secret_tmp_len); + + ssize_t result = crypto_dh_handshake(severity, dh, pubkey, pubkey_len, + secret_tmp, secret_tmp_len); + if (result < 0) + goto error; + + secret_len = result; + if (crypto_expand_key_material_TAP(secret_tmp, secret_len, + (uint8_t*)secret_out, secret_bytes_out)<0) + goto error; + secret_len = secret_bytes_out; + + goto done; + error: + result = -1; + done: + if (secret_tmp) { + memwipe(secret_tmp, 0, secret_tmp_len); + tor_free(secret_tmp); + } + if (result < 0) + return result; + else + return secret_len; +} diff --git a/src/lib/crypt_ops/crypto_dh.h b/src/lib/crypt_ops/crypto_dh.h new file mode 100644 index 0000000000..850d50c7ae --- /dev/null +++ b/src/lib/crypt_ops/crypto_dh.h @@ -0,0 +1,64 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_dh.h + * + * \brief Headers for crypto_dh.c + **/ + +#ifndef TOR_CRYPTO_DH_H +#define TOR_CRYPTO_DH_H + +#include "orconfig.h" +#include "lib/cc/torint.h" +#include "lib/defs/dh_sizes.h" + +typedef struct crypto_dh_t crypto_dh_t; + +extern const unsigned DH_GENERATOR; +extern const char TLS_DH_PRIME[]; +extern const char OAKLEY_PRIME_2[]; + +/* Key negotiation */ +#define DH_TYPE_CIRCUIT 1 +#define DH_TYPE_REND 2 +#define DH_TYPE_TLS 3 +void crypto_dh_init(void); +crypto_dh_t *crypto_dh_new(int dh_type); +crypto_dh_t *crypto_dh_dup(const crypto_dh_t *dh); +int crypto_dh_get_bytes(crypto_dh_t *dh); +int crypto_dh_generate_public(crypto_dh_t *dh); +int crypto_dh_get_public(crypto_dh_t *dh, char *pubkey_out, + size_t pubkey_out_len); +ssize_t crypto_dh_compute_secret(int severity, crypto_dh_t *dh, + const char *pubkey, size_t pubkey_len, + char *secret_out, size_t secret_out_len); +void crypto_dh_free_(crypto_dh_t *dh); +#define crypto_dh_free(dh) FREE_AND_NULL(crypto_dh_t, crypto_dh_free_, (dh)) + +ssize_t crypto_dh_handshake(int severity, crypto_dh_t *dh, + const char *pubkey, size_t pubkey_len, + unsigned char *secret_out, + size_t secret_bytes_out); + +void crypto_dh_free_all(void); + +/* Prototypes for private functions only used by tortls.c, crypto.c, and the + * unit tests. */ +struct dh_st; +struct dh_st *crypto_dh_new_openssl_tls(void); + +#ifdef ENABLE_OPENSSL +void crypto_dh_init_openssl(void); +void crypto_dh_free_all_openssl(void); +#endif +#ifdef ENABLE_NSS +void crypto_dh_init_nss(void); +void crypto_dh_free_all_nss(void); +#endif + +#endif /* !defined(TOR_CRYPTO_DH_H) */ diff --git a/src/lib/crypt_ops/crypto_dh_nss.c b/src/lib/crypt_ops/crypto_dh_nss.c new file mode 100644 index 0000000000..379eb84a4f --- /dev/null +++ b/src/lib/crypt_ops/crypto_dh_nss.c @@ -0,0 +1,209 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_dh_nss.h + * + * \brief NSS implementation of Diffie-Hellman over Z_p. + **/ + +#include "lib/crypt_ops/crypto_dh.h" +#include "lib/crypt_ops/crypto_nss_mgt.h" + +#include "lib/encoding/binascii.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" + +#include <cryptohi.h> +#include <keyhi.h> +#include <pk11pub.h> + +static int dh_initialized = 0; +static SECKEYDHParams tls_dh_param, circuit_dh_param; +static unsigned char tls_dh_prime_data[DH1024_KEY_LEN]; +static unsigned char circuit_dh_prime_data[DH1024_KEY_LEN]; +static unsigned char dh_generator_data[1]; + +void +crypto_dh_init_nss(void) +{ + if (dh_initialized) + return; + + int r; + r = base16_decode((char*)tls_dh_prime_data, + sizeof(tls_dh_prime_data), + TLS_DH_PRIME, strlen(TLS_DH_PRIME)); + tor_assert(r == DH1024_KEY_LEN); + r = base16_decode((char*)circuit_dh_prime_data, + sizeof(circuit_dh_prime_data), + OAKLEY_PRIME_2, strlen(OAKLEY_PRIME_2)); + tor_assert(r == DH1024_KEY_LEN); + dh_generator_data[0] = DH_GENERATOR; + + tls_dh_param.prime.data = tls_dh_prime_data; + tls_dh_param.prime.len = DH1024_KEY_LEN; + tls_dh_param.base.data = dh_generator_data; + tls_dh_param.base.len = 1; + + circuit_dh_param.prime.data = circuit_dh_prime_data; + circuit_dh_param.prime.len = DH1024_KEY_LEN; + circuit_dh_param.base.data = dh_generator_data; + circuit_dh_param.base.len = 1; + + dh_initialized = 1; +} + +void +crypto_dh_free_all_nss(void) +{ + dh_initialized = 0; +} + +struct crypto_dh_t { + int dh_type; // XXXX let's remove this later on. + SECKEYPrivateKey *seckey; + SECKEYPublicKey *pubkey; +}; + +crypto_dh_t * +crypto_dh_new(int dh_type) +{ + crypto_dh_t *r = tor_malloc_zero(sizeof(crypto_dh_t)); + r->dh_type = dh_type; + return r; +} + +crypto_dh_t * +crypto_dh_dup(const crypto_dh_t *dh) +{ + tor_assert(dh); + crypto_dh_t *r = crypto_dh_new(dh->dh_type); + if (dh->seckey) + r->seckey = SECKEY_CopyPrivateKey(dh->seckey); + if (dh->pubkey) + r->pubkey = SECKEY_CopyPublicKey(dh->pubkey); + return r; +} + +int +crypto_dh_get_bytes(crypto_dh_t *dh) +{ + (void)dh; + return DH1024_KEY_LEN; +} + +int +crypto_dh_generate_public(crypto_dh_t *dh) +{ + tor_assert(dh); + SECKEYDHParams *p; + if (dh->dh_type == DH_TYPE_TLS) + p = &tls_dh_param; + else + p = &circuit_dh_param; + + dh->seckey = SECKEY_CreateDHPrivateKey(p, &dh->pubkey, NULL); + if (!dh->seckey || !dh->pubkey) + return -1; + else + return 0; +} +int +crypto_dh_get_public(crypto_dh_t *dh, char *pubkey_out, + size_t pubkey_out_len) +{ + tor_assert(dh); + tor_assert(pubkey_out); + if (!dh->pubkey) { + if (crypto_dh_generate_public(dh) < 0) + return -1; + } + + const SECItem *item = &dh->pubkey->u.dh.publicValue; + + if (item->len > pubkey_out_len) + return -1; + + /* Left-pad the result with 0s. */ + memset(pubkey_out, 0, pubkey_out_len); + memcpy(pubkey_out + pubkey_out_len - item->len, + item->data, + item->len); + + return 0; +} + +void +crypto_dh_free_(crypto_dh_t *dh) +{ + if (!dh) + return; + if (dh->seckey) + SECKEY_DestroyPrivateKey(dh->seckey); + if (dh->pubkey) + SECKEY_DestroyPublicKey(dh->pubkey); + tor_free(dh); +} + +ssize_t +crypto_dh_handshake(int severity, crypto_dh_t *dh, + const char *pubkey, size_t pubkey_len, + unsigned char *secret_out, + size_t secret_bytes_out) +{ + tor_assert(dh); + if (pubkey_len > DH1024_KEY_LEN) + return -1; + if (!dh->pubkey || !dh->seckey) + return -1; + if (secret_bytes_out < DH1024_KEY_LEN) + return -1; + + SECKEYPublicKey peer_key; + memset(&peer_key, 0, sizeof(peer_key)); + peer_key.keyType = dhKey; + peer_key.pkcs11ID = CK_INVALID_HANDLE; + + if (dh->dh_type == DH_TYPE_TLS) + peer_key.u.dh.prime.data = tls_dh_prime_data; // should never use this code + else + peer_key.u.dh.prime.data = circuit_dh_prime_data; + peer_key.u.dh.prime.len = DH1024_KEY_LEN; + peer_key.u.dh.base.data = dh_generator_data; + peer_key.u.dh.base.len = 1; + peer_key.u.dh.publicValue.data = (unsigned char *)pubkey; + peer_key.u.dh.publicValue.len = (int) pubkey_len; + + PK11SymKey *sym = PK11_PubDerive(dh->seckey, &peer_key, + PR_FALSE, NULL, NULL, CKM_DH_PKCS_DERIVE, + CKM_GENERIC_SECRET_KEY_GEN /* ??? */, + CKA_DERIVE, 0, NULL); + if (! sym) { + crypto_nss_log_errors(severity, "deriving a DH shared secret"); + return -1; + } + + SECStatus s = PK11_ExtractKeyValue(sym); + if (s != SECSuccess) { + crypto_nss_log_errors(severity, "extracting a DH shared secret"); + PK11_FreeSymKey(sym); + return -1; + } + + SECItem *result = PK11_GetKeyData(sym); + tor_assert(result); // This cannot fail. + if (BUG(result->len > secret_bytes_out)) { + PK11_FreeSymKey(sym); + return -1; + } + + ssize_t len = result->len; + memcpy(secret_out, result->data, len); + PK11_FreeSymKey(sym); + + return len; +} diff --git a/src/lib/crypt_ops/crypto_dh_openssl.c b/src/lib/crypt_ops/crypto_dh_openssl.c new file mode 100644 index 0000000000..8c6388fd5d --- /dev/null +++ b/src/lib/crypt_ops/crypto_dh_openssl.c @@ -0,0 +1,477 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_dh_openssl.c + * \brief Implement Tor's Z_p diffie-hellman stuff for OpenSSL. + **/ + +#include "lib/crypt_ops/compat_openssl.h" +#include "lib/crypt_ops/crypto_dh.h" +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/crypt_ops/crypto_hkdf.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +DISABLE_GCC_WARNING(redundant-decls) + +#include <openssl/dh.h> + +ENABLE_GCC_WARNING(redundant-decls) + +#include <openssl/bn.h> +#include <string.h> + +#ifndef ENABLE_NSS +static int tor_check_dh_key(int severity, const BIGNUM *bn); + +/** A structure to hold the first half (x, g^x) of a Diffie-Hellman handshake + * while we're waiting for the second.*/ +struct crypto_dh_t { + DH *dh; /**< The openssl DH object */ +}; +#endif + +static DH *new_openssl_dh_from_params(BIGNUM *p, BIGNUM *g); + +/** Shared P parameter for our circuit-crypto DH key exchanges. */ +static BIGNUM *dh_param_p = NULL; +/** Shared P parameter for our TLS DH key exchanges. */ +static BIGNUM *dh_param_p_tls = NULL; +/** Shared G parameter for our DH key exchanges. */ +static BIGNUM *dh_param_g = NULL; + +/* This function is disabled unless we change the DH parameters. */ +#if 0 +/** Validate a given set of Diffie-Hellman parameters. This is moderately + * computationally expensive (milliseconds), so should only be called when + * the DH parameters change. Returns 0 on success, * -1 on failure. + */ +static int +crypto_validate_dh_params(const BIGNUM *p, const BIGNUM *g) +{ + DH *dh = NULL; + int ret = -1; + + /* Copy into a temporary DH object, just so that DH_check() can be called. */ + if (!(dh = DH_new())) + goto out; +#ifdef OPENSSL_1_1_API + BIGNUM *dh_p, *dh_g; + if (!(dh_p = BN_dup(p))) + goto out; + if (!(dh_g = BN_dup(g))) + goto out; + if (!DH_set0_pqg(dh, dh_p, NULL, dh_g)) + goto out; +#else /* !(defined(OPENSSL_1_1_API)) */ + if (!(dh->p = BN_dup(p))) + goto out; + if (!(dh->g = BN_dup(g))) + goto out; +#endif /* defined(OPENSSL_1_1_API) */ + + /* Perform the validation. */ + int codes = 0; + if (!DH_check(dh, &codes)) + goto out; + if (BN_is_word(g, DH_GENERATOR_2)) { + /* Per https://wiki.openssl.org/index.php/Diffie-Hellman_parameters + * + * OpenSSL checks the prime is congruent to 11 when g = 2; while the + * IETF's primes are congruent to 23 when g = 2. + */ + BN_ULONG residue = BN_mod_word(p, 24); + if (residue == 11 || residue == 23) + codes &= ~DH_NOT_SUITABLE_GENERATOR; + } + if (codes != 0) /* Specifics on why the params suck is irrelevant. */ + goto out; + + /* Things are probably not evil. */ + ret = 0; + + out: + if (dh) + DH_free(dh); + return ret; +} +#endif + +/** + * Helper: convert <b>hex<b> to a bignum, and return it. Assert that the + * operation was successful. + */ +static BIGNUM * +bignum_from_hex(const char *hex) +{ + BIGNUM *result = BN_new(); + tor_assert(result); + + int r = BN_hex2bn(&result, hex); + tor_assert(r); + tor_assert(result); + return result; +} + +/** Set the global Diffie-Hellman generator, used for both TLS and internal + * DH stuff. + */ +static void +crypto_set_dh_generator(void) +{ + BIGNUM *generator; + int r; + + if (dh_param_g) + return; + + generator = BN_new(); + tor_assert(generator); + + r = BN_set_word(generator, DH_GENERATOR); + tor_assert(r); + + dh_param_g = generator; +} + +/** Initialize our DH parameters. Idempotent. */ +void +crypto_dh_init_openssl(void) +{ + if (dh_param_p && dh_param_g && dh_param_p_tls) + return; + + tor_assert(dh_param_g == NULL); + tor_assert(dh_param_p == NULL); + tor_assert(dh_param_p_tls == NULL); + + crypto_set_dh_generator(); + dh_param_p = bignum_from_hex(OAKLEY_PRIME_2); + dh_param_p_tls = bignum_from_hex(TLS_DH_PRIME); + + /* Checks below are disabled unless we change the hardcoded DH parameters. */ +#if 0 + tor_assert(0 == crypto_validate_dh_params(dh_param_p, dh_param_g)); + tor_assert(0 == crypto_validate_dh_params(dh_param_p_tls, dh_param_g)); +#endif +} + +/** Number of bits to use when choosing the x or y value in a Diffie-Hellman + * handshake. Since we exponentiate by this value, choosing a smaller one + * lets our handhake go faster. + */ +#define DH_PRIVATE_KEY_BITS 320 + +/** Used by tortls.c: Get the DH* for use with TLS. + */ +DH * +crypto_dh_new_openssl_tls(void) +{ + return new_openssl_dh_from_params(dh_param_p_tls, dh_param_g); +} + +#ifndef ENABLE_NSS +/** Allocate and return a new DH object for a key exchange. Returns NULL on + * failure. + */ +crypto_dh_t * +crypto_dh_new(int dh_type) +{ + crypto_dh_t *res = tor_malloc_zero(sizeof(crypto_dh_t)); + + tor_assert(dh_type == DH_TYPE_CIRCUIT || dh_type == DH_TYPE_TLS || + dh_type == DH_TYPE_REND); + + if (!dh_param_p) + crypto_dh_init(); + + BIGNUM *dh_p = NULL; + if (dh_type == DH_TYPE_TLS) { + dh_p = dh_param_p_tls; + } else { + dh_p = dh_param_p; + } + + res->dh = new_openssl_dh_from_params(dh_p, dh_param_g); + if (res->dh == NULL) + tor_free(res); // sets res to NULL. + return res; +} +#endif + +/** Create and return a new openssl DH from a given prime and generator. */ +static DH * +new_openssl_dh_from_params(BIGNUM *p, BIGNUM *g) +{ + DH *res_dh; + if (!(res_dh = DH_new())) + goto err; + + BIGNUM *dh_p = NULL, *dh_g = NULL; + dh_p = BN_dup(p); + if (!dh_p) + goto err; + + dh_g = BN_dup(g); + if (!dh_g) { + BN_free(dh_p); + goto err; + } + +#ifdef OPENSSL_1_1_API + + if (!DH_set0_pqg(res_dh, dh_p, NULL, dh_g)) { + goto err; + } + + if (!DH_set_length(res_dh, DH_PRIVATE_KEY_BITS)) + goto err; +#else /* !(defined(OPENSSL_1_1_API)) */ + res_dh->p = dh_p; + res_dh->g = dh_g; + res_dh->length = DH_PRIVATE_KEY_BITS; +#endif /* defined(OPENSSL_1_1_API) */ + + return res_dh; + + /* LCOV_EXCL_START + * This error condition is only reached when an allocation fails */ + err: + crypto_openssl_log_errors(LOG_WARN, "creating DH object"); + if (res_dh) DH_free(res_dh); /* frees p and g too */ + return NULL; + /* LCOV_EXCL_STOP */ +} + +#ifndef ENABLE_NSS +/** Return a copy of <b>dh</b>, sharing its internal state. */ +crypto_dh_t * +crypto_dh_dup(const crypto_dh_t *dh) +{ + crypto_dh_t *dh_new = tor_malloc_zero(sizeof(crypto_dh_t)); + tor_assert(dh); + tor_assert(dh->dh); + dh_new->dh = dh->dh; + DH_up_ref(dh->dh); + return dh_new; +} + +/** Return the length of the DH key in <b>dh</b>, in bytes. + */ +int +crypto_dh_get_bytes(crypto_dh_t *dh) +{ + tor_assert(dh); + return DH_size(dh->dh); +} + +/** Generate \<x,g^x\> for our part of the key exchange. Return 0 on + * success, -1 on failure. + */ +int +crypto_dh_generate_public(crypto_dh_t *dh) +{ +#ifndef OPENSSL_1_1_API + again: +#endif + if (!DH_generate_key(dh->dh)) { + /* LCOV_EXCL_START + * To test this we would need some way to tell openssl to break DH. */ + crypto_openssl_log_errors(LOG_WARN, "generating DH key"); + return -1; + /* LCOV_EXCL_STOP */ + } +#ifdef OPENSSL_1_1_API + /* OpenSSL 1.1.x doesn't appear to let you regenerate a DH key, without + * recreating the DH object. I have no idea what sort of aliasing madness + * can occur here, so do the check, and just bail on failure. + */ + const BIGNUM *pub_key, *priv_key; + DH_get0_key(dh->dh, &pub_key, &priv_key); + if (tor_check_dh_key(LOG_WARN, pub_key)<0) { + log_warn(LD_CRYPTO, "Weird! Our own DH key was invalid. I guess once-in-" + "the-universe chances really do happen. Treating as a failure."); + return -1; + } +#else /* !(defined(OPENSSL_1_1_API)) */ + if (tor_check_dh_key(LOG_WARN, dh->dh->pub_key)<0) { + /* LCOV_EXCL_START + * If this happens, then openssl's DH implementation is busted. */ + log_warn(LD_CRYPTO, "Weird! Our own DH key was invalid. I guess once-in-" + "the-universe chances really do happen. Trying again."); + /* Free and clear the keys, so OpenSSL will actually try again. */ + BN_clear_free(dh->dh->pub_key); + BN_clear_free(dh->dh->priv_key); + dh->dh->pub_key = dh->dh->priv_key = NULL; + goto again; + /* LCOV_EXCL_STOP */ + } +#endif /* defined(OPENSSL_1_1_API) */ + return 0; +} + +/** Generate g^x as necessary, and write the g^x for the key exchange + * as a <b>pubkey_len</b>-byte value into <b>pubkey</b>. Return 0 on + * success, -1 on failure. <b>pubkey_len</b> must be \>= DH1024_KEY_LEN. + */ +int +crypto_dh_get_public(crypto_dh_t *dh, char *pubkey, size_t pubkey_len) +{ + int bytes; + tor_assert(dh); + + const BIGNUM *dh_pub; + +#ifdef OPENSSL_1_1_API + const BIGNUM *dh_priv; + DH_get0_key(dh->dh, &dh_pub, &dh_priv); +#else + dh_pub = dh->dh->pub_key; +#endif /* defined(OPENSSL_1_1_API) */ + + if (!dh_pub) { + if (crypto_dh_generate_public(dh)<0) + return -1; + else { +#ifdef OPENSSL_1_1_API + DH_get0_key(dh->dh, &dh_pub, &dh_priv); +#else + dh_pub = dh->dh->pub_key; +#endif + } + } + + tor_assert(dh_pub); + bytes = BN_num_bytes(dh_pub); + tor_assert(bytes >= 0); + if (pubkey_len < (size_t)bytes) { + log_warn(LD_CRYPTO, + "Weird! pubkey_len (%d) was smaller than DH1024_KEY_LEN (%d)", + (int) pubkey_len, bytes); + return -1; + } + + memset(pubkey, 0, pubkey_len); + BN_bn2bin(dh_pub, (unsigned char*)(pubkey+(pubkey_len-bytes))); + + return 0; +} + +/** Check for bad Diffie-Hellman public keys (g^x). Return 0 if the key is + * okay (in the subgroup [2,p-2]), or -1 if it's bad. + * See http://www.cl.cam.ac.uk/ftp/users/rja14/psandqs.ps.gz for some tips. + */ +static int +tor_check_dh_key(int severity, const BIGNUM *bn) +{ + BIGNUM *x; + char *s; + tor_assert(bn); + x = BN_new(); + tor_assert(x); + if (BUG(!dh_param_p)) + crypto_dh_init(); //LCOV_EXCL_LINE we already checked whether we did this. + BN_set_word(x, 1); + if (BN_cmp(bn,x)<=0) { + log_fn(severity, LD_CRYPTO, "DH key must be at least 2."); + goto err; + } + BN_copy(x,dh_param_p); + BN_sub_word(x, 1); + if (BN_cmp(bn,x)>=0) { + log_fn(severity, LD_CRYPTO, "DH key must be at most p-2."); + goto err; + } + BN_clear_free(x); + return 0; + err: + BN_clear_free(x); + s = BN_bn2hex(bn); + log_fn(severity, LD_CRYPTO, "Rejecting insecure DH key [%s]", s); + OPENSSL_free(s); + return -1; +} + +/** Given a DH key exchange object, and our peer's value of g^y (as a + * <b>pubkey_len</b>-byte value in <b>pubkey</b>) generate + * g^xy as a big-endian integer in <b>secret_out</b>. + * Return the number of bytes generated on success, + * or -1 on failure. + * + * This function MUST validate that g^y is actually in the group. + */ +ssize_t +crypto_dh_handshake(int severity, crypto_dh_t *dh, + const char *pubkey, size_t pubkey_len, + unsigned char *secret_out, size_t secret_bytes_out) +{ + BIGNUM *pubkey_bn = NULL; + size_t secret_len=0; + int result=0; + + tor_assert(dh); + tor_assert(secret_bytes_out/DIGEST_LEN <= 255); + tor_assert(pubkey_len < INT_MAX); + + if (BUG(crypto_dh_get_bytes(dh) > (int)secret_bytes_out)) { + goto error; + } + + if (!(pubkey_bn = BN_bin2bn((const unsigned char*)pubkey, + (int)pubkey_len, NULL))) + goto error; + if (tor_check_dh_key(severity, pubkey_bn)<0) { + /* Check for invalid public keys. */ + log_fn(severity, LD_CRYPTO,"Rejected invalid g^x"); + goto error; + } + result = DH_compute_key(secret_out, pubkey_bn, dh->dh); + if (result < 0) { + log_warn(LD_CRYPTO,"DH_compute_key() failed."); + goto error; + } + secret_len = result; + + goto done; + error: + result = -1; + done: + crypto_openssl_log_errors(LOG_WARN, "completing DH handshake"); + if (pubkey_bn) + BN_clear_free(pubkey_bn); + if (result < 0) + return result; + else + return secret_len; +} + +/** Free a DH key exchange object. + */ +void +crypto_dh_free_(crypto_dh_t *dh) +{ + if (!dh) + return; + tor_assert(dh->dh); + DH_free(dh->dh); + tor_free(dh); +} +#endif + +void +crypto_dh_free_all_openssl(void) +{ + if (dh_param_p) + BN_clear_free(dh_param_p); + if (dh_param_p_tls) + BN_clear_free(dh_param_p_tls); + if (dh_param_g) + BN_clear_free(dh_param_g); + + dh_param_p = dh_param_p_tls = dh_param_g = NULL; +} diff --git a/src/lib/crypt_ops/crypto_digest.c b/src/lib/crypt_ops/crypto_digest.c new file mode 100644 index 0000000000..26f06c6c79 --- /dev/null +++ b/src/lib/crypt_ops/crypto_digest.c @@ -0,0 +1,828 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_digest.c + * \brief Block of functions related with digest and xof utilities and + * operations. + **/ + +#include "lib/container/smartlist.h" +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +#include "keccak-tiny/keccak-tiny.h" + +#include <stdlib.h> +#include <string.h> + +#include "lib/arch/bytes.h" + +#ifdef ENABLE_NSS +DISABLE_GCC_WARNING(strict-prototypes) +#include <pk11pub.h> +ENABLE_GCC_WARNING(strict-prototypes) +#else + +#include "lib/crypt_ops/crypto_openssl_mgt.h" + +DISABLE_GCC_WARNING(redundant-decls) + +#include <openssl/hmac.h> +#include <openssl/sha.h> + +ENABLE_GCC_WARNING(redundant-decls) +#endif + +#ifdef ENABLE_NSS +/** + * Convert a digest_algorithm_t (used by tor) to a HashType (used by NSS). + * On failure, return SEC_OID_UNKNOWN. */ +static SECOidTag +digest_alg_to_nss_oid(digest_algorithm_t alg) +{ + switch (alg) { + case DIGEST_SHA1: return SEC_OID_SHA1; + case DIGEST_SHA256: return SEC_OID_SHA256; + case DIGEST_SHA512: return SEC_OID_SHA512; + case DIGEST_SHA3_256: /* Fall through */ + case DIGEST_SHA3_512: /* Fall through */ + default: + return SEC_OID_UNKNOWN; + } +} + +/* Helper: get an unkeyed digest via pk11wrap */ +static int +digest_nss_internal(SECOidTag alg, + char *digest, unsigned len_out, + const char *msg, size_t msg_len) +{ + if (alg == SEC_OID_UNKNOWN) + return -1; + tor_assert(msg_len <= UINT_MAX); + + int rv = -1; + SECStatus s; + PK11Context *ctx = PK11_CreateDigestContext(alg); + if (!ctx) + return -1; + + s = PK11_DigestBegin(ctx); + if (s != SECSuccess) + goto done; + + s = PK11_DigestOp(ctx, (const unsigned char *)msg, (unsigned int)msg_len); + if (s != SECSuccess) + goto done; + + unsigned int len = 0; + s = PK11_DigestFinal(ctx, (unsigned char *)digest, &len, len_out); + if (s != SECSuccess) + goto done; + + rv = 0; + done: + PK11_DestroyContext(ctx, PR_TRUE); + return rv; +} + +/** True iff alg is implemented in our crypto library, and we want to use that + * implementation */ +static bool +library_supports_digest(digest_algorithm_t alg) +{ + switch (alg) { + case DIGEST_SHA1: /* Fall through */ + case DIGEST_SHA256: /* Fall through */ + case DIGEST_SHA512: /* Fall through */ + return true; + case DIGEST_SHA3_256: /* Fall through */ + case DIGEST_SHA3_512: /* Fall through */ + default: + return false; + } +} +#endif + +/* Crypto digest functions */ + +/** Compute the SHA1 digest of the <b>len</b> bytes on data stored in + * <b>m</b>. Write the DIGEST_LEN byte result into <b>digest</b>. + * Return 0 on success, -1 on failure. + */ +MOCK_IMPL(int, +crypto_digest,(char *digest, const char *m, size_t len)) +{ + tor_assert(m); + tor_assert(digest); +#ifdef ENABLE_NSS + return digest_nss_internal(SEC_OID_SHA1, digest, DIGEST_LEN, m, len); +#else + if (SHA1((const unsigned char*)m,len,(unsigned char*)digest) == NULL) { + return -1; + } +#endif + return 0; +} + +/** Compute a 256-bit digest of <b>len</b> bytes in data stored in <b>m</b>, + * using the algorithm <b>algorithm</b>. Write the DIGEST_LEN256-byte result + * into <b>digest</b>. Return 0 on success, -1 on failure. */ +int +crypto_digest256(char *digest, const char *m, size_t len, + digest_algorithm_t algorithm) +{ + tor_assert(m); + tor_assert(digest); + tor_assert(algorithm == DIGEST_SHA256 || algorithm == DIGEST_SHA3_256); + + int ret = 0; + if (algorithm == DIGEST_SHA256) { +#ifdef ENABLE_NSS + return digest_nss_internal(SEC_OID_SHA256, digest, DIGEST256_LEN, m, len); +#else + ret = (SHA256((const uint8_t*)m,len,(uint8_t*)digest) != NULL); +#endif + } else { + ret = (sha3_256((uint8_t *)digest, DIGEST256_LEN,(const uint8_t *)m, len) + > -1); + } + + if (!ret) + return -1; + return 0; +} + +/** Compute a 512-bit digest of <b>len</b> bytes in data stored in <b>m</b>, + * using the algorithm <b>algorithm</b>. Write the DIGEST_LEN512-byte result + * into <b>digest</b>. Return 0 on success, -1 on failure. */ +int +crypto_digest512(char *digest, const char *m, size_t len, + digest_algorithm_t algorithm) +{ + tor_assert(m); + tor_assert(digest); + tor_assert(algorithm == DIGEST_SHA512 || algorithm == DIGEST_SHA3_512); + + int ret = 0; + if (algorithm == DIGEST_SHA512) { +#ifdef ENABLE_NSS + return digest_nss_internal(SEC_OID_SHA512, digest, DIGEST512_LEN, m, len); +#else + ret = (SHA512((const unsigned char*)m,len,(unsigned char*)digest) + != NULL); +#endif + } else { + ret = (sha3_512((uint8_t*)digest, DIGEST512_LEN, (const uint8_t*)m, len) + > -1); + } + + if (!ret) + return -1; + return 0; +} + +/** Set the common_digests_t in <b>ds_out</b> to contain every digest on the + * <b>len</b> bytes in <b>m</b> that we know how to compute. Return 0 on + * success, -1 on failure. */ +int +crypto_common_digests(common_digests_t *ds_out, const char *m, size_t len) +{ + tor_assert(ds_out); + memset(ds_out, 0, sizeof(*ds_out)); + if (crypto_digest(ds_out->d[DIGEST_SHA1], m, len) < 0) + return -1; + if (crypto_digest256(ds_out->d[DIGEST_SHA256], m, len, DIGEST_SHA256) < 0) + return -1; + + return 0; +} + +/** Return the name of an algorithm, as used in directory documents. */ +const char * +crypto_digest_algorithm_get_name(digest_algorithm_t alg) +{ + switch (alg) { + case DIGEST_SHA1: + return "sha1"; + case DIGEST_SHA256: + return "sha256"; + case DIGEST_SHA512: + return "sha512"; + case DIGEST_SHA3_256: + return "sha3-256"; + case DIGEST_SHA3_512: + return "sha3-512"; + // LCOV_EXCL_START + default: + tor_fragile_assert(); + return "??unknown_digest??"; + // LCOV_EXCL_STOP + } +} + +/** Given the name of a digest algorithm, return its integer value, or -1 if + * the name is not recognized. */ +int +crypto_digest_algorithm_parse_name(const char *name) +{ + if (!strcmp(name, "sha1")) + return DIGEST_SHA1; + else if (!strcmp(name, "sha256")) + return DIGEST_SHA256; + else if (!strcmp(name, "sha512")) + return DIGEST_SHA512; + else if (!strcmp(name, "sha3-256")) + return DIGEST_SHA3_256; + else if (!strcmp(name, "sha3-512")) + return DIGEST_SHA3_512; + else + return -1; +} + +/** Given an algorithm, return the digest length in bytes. */ +size_t +crypto_digest_algorithm_get_length(digest_algorithm_t alg) +{ + switch (alg) { + case DIGEST_SHA1: + return DIGEST_LEN; + case DIGEST_SHA256: + return DIGEST256_LEN; + case DIGEST_SHA512: + return DIGEST512_LEN; + case DIGEST_SHA3_256: + return DIGEST256_LEN; + case DIGEST_SHA3_512: + return DIGEST512_LEN; + default: + tor_assert(0); // LCOV_EXCL_LINE + return 0; /* Unreachable */ // LCOV_EXCL_LINE + } +} + +/** Intermediate information about the digest of a stream of data. */ +struct crypto_digest_t { + digest_algorithm_t algorithm; /**< Which algorithm is in use? */ + /** State for the digest we're using. Only one member of the + * union is usable, depending on the value of <b>algorithm</b>. Note also + * that space for other members might not even be allocated! + */ + union { +#ifdef ENABLE_NSS + PK11Context *ctx; +#else + SHA_CTX sha1; /**< state for SHA1 */ + SHA256_CTX sha2; /**< state for SHA256 */ + SHA512_CTX sha512; /**< state for SHA512 */ +#endif + keccak_state sha3; /**< state for SHA3-[256,512] */ + } d; +}; + +#ifdef TOR_UNIT_TESTS + +digest_algorithm_t +crypto_digest_get_algorithm(crypto_digest_t *digest) +{ + tor_assert(digest); + + return digest->algorithm; +} + +#endif /* defined(TOR_UNIT_TESTS) */ + +/** + * Return the number of bytes we need to malloc in order to get a + * crypto_digest_t for <b>alg</b>, or the number of bytes we need to wipe + * when we free one. + */ +static size_t +crypto_digest_alloc_bytes(digest_algorithm_t alg) +{ + /* Helper: returns the number of bytes in the 'f' field of 'st' */ +#define STRUCT_FIELD_SIZE(st, f) (sizeof( ((st*)0)->f )) + /* Gives the length of crypto_digest_t through the end of the field 'd' */ +#define END_OF_FIELD(f) (offsetof(crypto_digest_t, f) + \ + STRUCT_FIELD_SIZE(crypto_digest_t, f)) + switch (alg) { +#ifdef ENABLE_NSS + case DIGEST_SHA1: /* Fall through */ + case DIGEST_SHA256: /* Fall through */ + case DIGEST_SHA512: + return END_OF_FIELD(d.ctx); +#else + case DIGEST_SHA1: + return END_OF_FIELD(d.sha1); + case DIGEST_SHA256: + return END_OF_FIELD(d.sha2); + case DIGEST_SHA512: + return END_OF_FIELD(d.sha512); +#endif + case DIGEST_SHA3_256: + case DIGEST_SHA3_512: + return END_OF_FIELD(d.sha3); + default: + tor_assert(0); // LCOV_EXCL_LINE + return 0; // LCOV_EXCL_LINE + } +#undef END_OF_FIELD +#undef STRUCT_FIELD_SIZE +} + +/** + * Internal function: create and return a new digest object for 'algorithm'. + * Does not typecheck the algorithm. + */ +static crypto_digest_t * +crypto_digest_new_internal(digest_algorithm_t algorithm) +{ + crypto_digest_t *r = tor_malloc(crypto_digest_alloc_bytes(algorithm)); + r->algorithm = algorithm; + + switch (algorithm) + { +#ifdef ENABLE_NSS + case DIGEST_SHA1: /* fall through */ + case DIGEST_SHA256: /* fall through */ + case DIGEST_SHA512: + r->d.ctx = PK11_CreateDigestContext(digest_alg_to_nss_oid(algorithm)); + if (BUG(!r->d.ctx)) { + tor_free(r); + return NULL; + } + if (BUG(SECSuccess != PK11_DigestBegin(r->d.ctx))) { + crypto_digest_free(r); + return NULL; + } + break; +#else + case DIGEST_SHA1: + SHA1_Init(&r->d.sha1); + break; + case DIGEST_SHA256: + SHA256_Init(&r->d.sha2); + break; + case DIGEST_SHA512: + SHA512_Init(&r->d.sha512); + break; +#endif + case DIGEST_SHA3_256: + keccak_digest_init(&r->d.sha3, 256); + break; + case DIGEST_SHA3_512: + keccak_digest_init(&r->d.sha3, 512); + break; + default: + tor_assert_unreached(); + } + + return r; +} + +/** Allocate and return a new digest object to compute SHA1 digests. + */ +crypto_digest_t * +crypto_digest_new(void) +{ + return crypto_digest_new_internal(DIGEST_SHA1); +} + +/** Allocate and return a new digest object to compute 256-bit digests + * using <b>algorithm</b>. + * + * C_RUST_COUPLED: `external::crypto_digest::crypto_digest256_new` + * C_RUST_COUPLED: `crypto::digest::Sha256::default` + */ +crypto_digest_t * +crypto_digest256_new(digest_algorithm_t algorithm) +{ + tor_assert(algorithm == DIGEST_SHA256 || algorithm == DIGEST_SHA3_256); + return crypto_digest_new_internal(algorithm); +} + +/** Allocate and return a new digest object to compute 512-bit digests + * using <b>algorithm</b>. */ +crypto_digest_t * +crypto_digest512_new(digest_algorithm_t algorithm) +{ + tor_assert(algorithm == DIGEST_SHA512 || algorithm == DIGEST_SHA3_512); + return crypto_digest_new_internal(algorithm); +} + +/** Deallocate a digest object. + */ +void +crypto_digest_free_(crypto_digest_t *digest) +{ + if (!digest) + return; +#ifdef ENABLE_NSS + if (library_supports_digest(digest->algorithm)) { + PK11_DestroyContext(digest->d.ctx, PR_TRUE); + } +#endif + size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); + memwipe(digest, 0, bytes); + tor_free(digest); +} + +/** Add <b>len</b> bytes from <b>data</b> to the digest object. + * + * C_RUST_COUPLED: `external::crypto_digest::crypto_digest_add_bytess` + * C_RUST_COUPLED: `crypto::digest::Sha256::process` + */ +void +crypto_digest_add_bytes(crypto_digest_t *digest, const char *data, + size_t len) +{ + tor_assert(digest); + tor_assert(data); + /* Using the SHA*_*() calls directly means we don't support doing + * SHA in hardware. But so far the delay of getting the question + * to the hardware, and hearing the answer, is likely higher than + * just doing it ourselves. Hashes are fast. + */ + switch (digest->algorithm) { +#ifdef ENABLE_NSS + case DIGEST_SHA1: /* fall through */ + case DIGEST_SHA256: /* fall through */ + case DIGEST_SHA512: + tor_assert(len <= UINT_MAX); + SECStatus s = PK11_DigestOp(digest->d.ctx, + (const unsigned char *)data, + (unsigned int)len); + tor_assert(s == SECSuccess); + break; +#else + case DIGEST_SHA1: + SHA1_Update(&digest->d.sha1, (void*)data, len); + break; + case DIGEST_SHA256: + SHA256_Update(&digest->d.sha2, (void*)data, len); + break; + case DIGEST_SHA512: + SHA512_Update(&digest->d.sha512, (void*)data, len); + break; +#endif + case DIGEST_SHA3_256: /* FALLSTHROUGH */ + case DIGEST_SHA3_512: + keccak_digest_update(&digest->d.sha3, (const uint8_t *)data, len); + break; + default: + /* LCOV_EXCL_START */ + tor_fragile_assert(); + break; + /* LCOV_EXCL_STOP */ + } +} + +/** Compute the hash of the data that has been passed to the digest + * object; write the first out_len bytes of the result to <b>out</b>. + * <b>out_len</b> must be \<= DIGEST512_LEN. + * + * C_RUST_COUPLED: `external::crypto_digest::crypto_digest_get_digest` + * C_RUST_COUPLED: `impl digest::FixedOutput for Sha256` + */ +void +crypto_digest_get_digest(crypto_digest_t *digest, + char *out, size_t out_len) +{ + unsigned char r[DIGEST512_LEN]; + tor_assert(digest); + tor_assert(out); + tor_assert(out_len <= crypto_digest_algorithm_get_length(digest->algorithm)); + + /* The SHA-3 code handles copying into a temporary ctx, and also can handle + * short output buffers by truncating appropriately. */ + if (digest->algorithm == DIGEST_SHA3_256 || + digest->algorithm == DIGEST_SHA3_512) { + keccak_digest_sum(&digest->d.sha3, (uint8_t *)out, out_len); + return; + } + +#ifdef ENABLE_NSS + /* Copy into a temporary buffer since DigestFinal (alters) the context */ + unsigned char buf[1024]; + unsigned int saved_len = 0; + unsigned rlen; + unsigned char *saved = PK11_SaveContextAlloc(digest->d.ctx, + buf, sizeof(buf), + &saved_len); + tor_assert(saved); + SECStatus s = PK11_DigestFinal(digest->d.ctx, r, &rlen, sizeof(r)); + tor_assert(s == SECSuccess); + tor_assert(rlen >= out_len); + s = PK11_RestoreContext(digest->d.ctx, saved, saved_len); + tor_assert(s == SECSuccess); + if (saved != buf) { + PORT_ZFree(saved, saved_len); + } +#else + const size_t alloc_bytes = crypto_digest_alloc_bytes(digest->algorithm); + crypto_digest_t tmpenv; + /* memcpy into a temporary ctx, since SHA*_Final clears the context */ + memcpy(&tmpenv, digest, alloc_bytes); + switch (digest->algorithm) { + case DIGEST_SHA1: + SHA1_Final(r, &tmpenv.d.sha1); + break; + case DIGEST_SHA256: + SHA256_Final(r, &tmpenv.d.sha2); + break; + case DIGEST_SHA512: + SHA512_Final(r, &tmpenv.d.sha512); + break; +//LCOV_EXCL_START + case DIGEST_SHA3_256: /* FALLSTHROUGH */ + case DIGEST_SHA3_512: + default: + log_warn(LD_BUG, "Handling unexpected algorithm %d", digest->algorithm); + /* This is fatal, because it should never happen. */ + tor_assert_unreached(); + break; +//LCOV_EXCL_STOP + } +#endif + memcpy(out, r, out_len); + memwipe(r, 0, sizeof(r)); +} + +/** Allocate and return a new digest object with the same state as + * <b>digest</b> + * + * C_RUST_COUPLED: `external::crypto_digest::crypto_digest_dup` + * C_RUST_COUPLED: `impl Clone for crypto::digest::Sha256` + */ +crypto_digest_t * +crypto_digest_dup(const crypto_digest_t *digest) +{ + tor_assert(digest); + const size_t alloc_bytes = crypto_digest_alloc_bytes(digest->algorithm); + crypto_digest_t *result = tor_memdup(digest, alloc_bytes); +#ifdef ENABLE_NSS + if (library_supports_digest(digest->algorithm)) { + result->d.ctx = PK11_CloneContext(digest->d.ctx); + } +#endif + return result; +} + +/** Temporarily save the state of <b>digest</b> in <b>checkpoint</b>. + * Asserts that <b>digest</b> is a SHA1 digest object. + */ +void +crypto_digest_checkpoint(crypto_digest_checkpoint_t *checkpoint, + const crypto_digest_t *digest) +{ + const size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); + tor_assert(bytes <= sizeof(checkpoint->mem)); +#ifdef ENABLE_NSS + if (library_supports_digest(digest->algorithm)) { + unsigned char *allocated; + allocated = PK11_SaveContextAlloc(digest->d.ctx, + (unsigned char *)checkpoint->mem, + sizeof(checkpoint->mem), + &checkpoint->bytes_used); + /* No allocation is allowed here. */ + tor_assert(allocated == checkpoint->mem); + return; + } +#endif + memcpy(checkpoint->mem, digest, bytes); +} + +/** Restore the state of <b>digest</b> from <b>checkpoint</b>. + * Asserts that <b>digest</b> is a SHA1 digest object. Requires that the + * state was previously stored with crypto_digest_checkpoint() */ +void +crypto_digest_restore(crypto_digest_t *digest, + const crypto_digest_checkpoint_t *checkpoint) +{ + const size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); +#ifdef ENABLE_NSS + if (library_supports_digest(digest->algorithm)) { + SECStatus s = PK11_RestoreContext(digest->d.ctx, + (unsigned char *)checkpoint->mem, + checkpoint->bytes_used); + tor_assert(s == SECSuccess); + return; + } +#endif + memcpy(digest, checkpoint->mem, bytes); +} + +/** Replace the state of the digest object <b>into</b> with the state + * of the digest object <b>from</b>. Requires that 'into' and 'from' + * have the same digest type. + */ +void +crypto_digest_assign(crypto_digest_t *into, + const crypto_digest_t *from) +{ + tor_assert(into); + tor_assert(from); + tor_assert(into->algorithm == from->algorithm); + const size_t alloc_bytes = crypto_digest_alloc_bytes(from->algorithm); +#ifdef ENABLE_NSS + if (library_supports_digest(from->algorithm)) { + PK11_DestroyContext(into->d.ctx, PR_TRUE); + into->d.ctx = PK11_CloneContext(from->d.ctx); + return; + } +#endif + memcpy(into,from,alloc_bytes); +} + +/** Given a list of strings in <b>lst</b>, set the <b>len_out</b>-byte digest + * at <b>digest_out</b> to the hash of the concatenation of those strings, + * plus the optional string <b>append</b>, computed with the algorithm + * <b>alg</b>. + * <b>out_len</b> must be \<= DIGEST512_LEN. */ +void +crypto_digest_smartlist(char *digest_out, size_t len_out, + const smartlist_t *lst, + const char *append, + digest_algorithm_t alg) +{ + crypto_digest_smartlist_prefix(digest_out, len_out, NULL, lst, append, alg); +} + +/** Given a list of strings in <b>lst</b>, set the <b>len_out</b>-byte digest + * at <b>digest_out</b> to the hash of the concatenation of: the + * optional string <b>prepend</b>, those strings, + * and the optional string <b>append</b>, computed with the algorithm + * <b>alg</b>. + * <b>len_out</b> must be \<= DIGEST512_LEN. */ +void +crypto_digest_smartlist_prefix(char *digest_out, size_t len_out, + const char *prepend, + const smartlist_t *lst, + const char *append, + digest_algorithm_t alg) +{ + crypto_digest_t *d = crypto_digest_new_internal(alg); + if (prepend) + crypto_digest_add_bytes(d, prepend, strlen(prepend)); + SMARTLIST_FOREACH(lst, const char *, cp, + crypto_digest_add_bytes(d, cp, strlen(cp))); + if (append) + crypto_digest_add_bytes(d, append, strlen(append)); + crypto_digest_get_digest(d, digest_out, len_out); + crypto_digest_free(d); +} + +/** Compute the HMAC-SHA-256 of the <b>msg_len</b> bytes in <b>msg</b>, using + * the <b>key</b> of length <b>key_len</b>. Store the DIGEST256_LEN-byte + * result in <b>hmac_out</b>. Asserts on failure. + */ +void +crypto_hmac_sha256(char *hmac_out, + const char *key, size_t key_len, + const char *msg, size_t msg_len) +{ + /* If we've got OpenSSL >=0.9.8 we can use its hmac implementation. */ + tor_assert(key_len < INT_MAX); + tor_assert(msg_len < INT_MAX); + tor_assert(hmac_out); +#ifdef ENABLE_NSS + PK11SlotInfo *slot = NULL; + PK11SymKey *symKey = NULL; + PK11Context *hmac = NULL; + + int ok = 0; + SECStatus s; + SECItem keyItem, paramItem; + keyItem.data = (unsigned char *)key; + keyItem.len = (unsigned)key_len; + paramItem.type = siBuffer; + paramItem.data = NULL; + paramItem.len = 0; + + slot = PK11_GetBestSlot(CKM_SHA256_HMAC, NULL); + if (!slot) + goto done; + symKey = PK11_ImportSymKey(slot, CKM_SHA256_HMAC, + PK11_OriginUnwrap, CKA_SIGN, &keyItem, NULL); + if (!symKey) + goto done; + + hmac = PK11_CreateContextBySymKey(CKM_SHA256_HMAC, CKA_SIGN, symKey, + ¶mItem); + if (!hmac) + goto done; + s = PK11_DigestBegin(hmac); + if (s != SECSuccess) + goto done; + s = PK11_DigestOp(hmac, (const unsigned char *)msg, (unsigned int)msg_len); + if (s != SECSuccess) + goto done; + unsigned int len=0; + s = PK11_DigestFinal(hmac, (unsigned char *)hmac_out, &len, DIGEST256_LEN); + if (s != SECSuccess || len != DIGEST256_LEN) + goto done; + ok = 1; + + done: + if (hmac) + PK11_DestroyContext(hmac, PR_TRUE); + if (symKey) + PK11_FreeSymKey(symKey); + if (slot) + PK11_FreeSlot(slot); + + tor_assert(ok); +#else + unsigned char *rv = NULL; + rv = HMAC(EVP_sha256(), key, (int)key_len, (unsigned char*)msg, (int)msg_len, + (unsigned char*)hmac_out, NULL); + tor_assert(rv); +#endif +} + +/** Compute a MAC using SHA3-256 of <b>msg_len</b> bytes in <b>msg</b> using a + * <b>key</b> of length <b>key_len</b> and a <b>salt</b> of length + * <b>salt_len</b>. Store the result of <b>len_out</b> bytes in in + * <b>mac_out</b>. This function can't fail. */ +void +crypto_mac_sha3_256(uint8_t *mac_out, size_t len_out, + const uint8_t *key, size_t key_len, + const uint8_t *msg, size_t msg_len) +{ + crypto_digest_t *digest; + + const uint64_t key_len_netorder = tor_htonll(key_len); + + tor_assert(mac_out); + tor_assert(key); + tor_assert(msg); + + digest = crypto_digest256_new(DIGEST_SHA3_256); + + /* Order matters here that is any subsystem using this function should + * expect this very precise ordering in the MAC construction. */ + crypto_digest_add_bytes(digest, (const char *) &key_len_netorder, + sizeof(key_len_netorder)); + crypto_digest_add_bytes(digest, (const char *) key, key_len); + crypto_digest_add_bytes(digest, (const char *) msg, msg_len); + crypto_digest_get_digest(digest, (char *) mac_out, len_out); + crypto_digest_free(digest); +} + +/* xof functions */ + +/** Internal state for a eXtendable-Output Function (XOF). */ +struct crypto_xof_t { + keccak_state s; +}; + +/** Allocate a new XOF object backed by SHAKE-256. The security level + * provided is a function of the length of the output used. Read and + * understand FIPS-202 A.2 "Additional Consideration for Extendable-Output + * Functions" before using this construct. + */ +crypto_xof_t * +crypto_xof_new(void) +{ + crypto_xof_t *xof; + xof = tor_malloc(sizeof(crypto_xof_t)); + keccak_xof_init(&xof->s, 256); + return xof; +} + +/** Absorb bytes into a XOF object. Must not be called after a call to + * crypto_xof_squeeze_bytes() for the same instance, and will assert + * if attempted. + */ +void +crypto_xof_add_bytes(crypto_xof_t *xof, const uint8_t *data, size_t len) +{ + int i = keccak_xof_absorb(&xof->s, data, len); + tor_assert(i == 0); +} + +/** Squeeze bytes out of a XOF object. Calling this routine will render + * the XOF instance ineligible to absorb further data. + */ +void +crypto_xof_squeeze_bytes(crypto_xof_t *xof, uint8_t *out, size_t len) +{ + int i = keccak_xof_squeeze(&xof->s, out, len); + tor_assert(i == 0); +} + +/** Cleanse and deallocate a XOF object. */ +void +crypto_xof_free_(crypto_xof_t *xof) +{ + if (!xof) + return; + memwipe(xof, 0, sizeof(crypto_xof_t)); + tor_free(xof); +} diff --git a/src/lib/crypt_ops/crypto_digest.h b/src/lib/crypt_ops/crypto_digest.h new file mode 100644 index 0000000000..47e60ce617 --- /dev/null +++ b/src/lib/crypt_ops/crypto_digest.h @@ -0,0 +1,132 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_digest.h + * + * \brief Headers for crypto_digest.c + **/ + +#ifndef TOR_CRYPTO_DIGEST_H +#define TOR_CRYPTO_DIGEST_H + +#include "lib/cc/torint.h" +#include "lib/defs/digest_sizes.h" +#include "lib/malloc/malloc.h" +#include "lib/testsupport/testsupport.h" + +/** Length of a sha1 message digest when encoded in base32 with trailing = + * signs removed. */ +#define BASE32_DIGEST_LEN 32 +/** Length of a sha1 message digest when encoded in base64 with trailing = + * signs removed. */ +#define BASE64_DIGEST_LEN 27 +/** Length of a sha256 message digest when encoded in base64 with trailing = + * signs removed. */ +#define BASE64_DIGEST256_LEN 43 +/** Length of a sha512 message digest when encoded in base64 with trailing = + * signs removed. */ +#define BASE64_DIGEST512_LEN 86 + +/** Length of hex encoding of SHA1 digest, not including final NUL. */ +#define HEX_DIGEST_LEN 40 +/** Length of hex encoding of SHA256 digest, not including final NUL. */ +#define HEX_DIGEST256_LEN 64 +/** Length of hex encoding of SHA512 digest, not including final NUL. */ +#define HEX_DIGEST512_LEN 128 + +typedef enum { + DIGEST_SHA1 = 0, + DIGEST_SHA256 = 1, + DIGEST_SHA512 = 2, + DIGEST_SHA3_256 = 3, + DIGEST_SHA3_512 = 4, +} digest_algorithm_t; +#define N_DIGEST_ALGORITHMS (DIGEST_SHA3_512+1) +#define N_COMMON_DIGEST_ALGORITHMS (DIGEST_SHA256+1) + +#define DIGEST_CHECKPOINT_BYTES (SIZEOF_VOID_P + 512) +/** Structure used to temporarily save the a digest object. Only implemented + * for SHA1 digest for now. */ +typedef struct crypto_digest_checkpoint_t { +#ifdef ENABLE_NSS + unsigned int bytes_used; +#endif + uint8_t mem[DIGEST_CHECKPOINT_BYTES]; +} crypto_digest_checkpoint_t; + +/** A set of all the digests we commonly compute, taken on a single + * string. Any digests that are shorter than 512 bits are right-padded + * with 0 bits. + * + * Note that this representation wastes 44 bytes for the SHA1 case, so + * don't use it for anything where we need to allocate a whole bunch at + * once. + **/ +typedef struct { + char d[N_COMMON_DIGEST_ALGORITHMS][DIGEST256_LEN]; +} common_digests_t; + +typedef struct crypto_digest_t crypto_digest_t; +typedef struct crypto_xof_t crypto_xof_t; + +struct smartlist_t; + +/* SHA-1 and other digests */ +MOCK_DECL(int, crypto_digest,(char *digest, const char *m, size_t len)); +int crypto_digest256(char *digest, const char *m, size_t len, + digest_algorithm_t algorithm); +int crypto_digest512(char *digest, const char *m, size_t len, + digest_algorithm_t algorithm); +int crypto_common_digests(common_digests_t *ds_out, const char *m, size_t len); +void crypto_digest_smartlist_prefix(char *digest_out, size_t len_out, + const char *prepend, + const struct smartlist_t *lst, + const char *append, + digest_algorithm_t alg); +void crypto_digest_smartlist(char *digest_out, size_t len_out, + const struct smartlist_t *lst, const char *append, + digest_algorithm_t alg); +const char *crypto_digest_algorithm_get_name(digest_algorithm_t alg); +size_t crypto_digest_algorithm_get_length(digest_algorithm_t alg); +int crypto_digest_algorithm_parse_name(const char *name); +crypto_digest_t *crypto_digest_new(void); +crypto_digest_t *crypto_digest256_new(digest_algorithm_t algorithm); +crypto_digest_t *crypto_digest512_new(digest_algorithm_t algorithm); +void crypto_digest_free_(crypto_digest_t *digest); +#define crypto_digest_free(d) \ + FREE_AND_NULL(crypto_digest_t, crypto_digest_free_, (d)) +void crypto_digest_add_bytes(crypto_digest_t *digest, const char *data, + size_t len); +void crypto_digest_get_digest(crypto_digest_t *digest, + char *out, size_t out_len); +crypto_digest_t *crypto_digest_dup(const crypto_digest_t *digest); +void crypto_digest_checkpoint(crypto_digest_checkpoint_t *checkpoint, + const crypto_digest_t *digest); +void crypto_digest_restore(crypto_digest_t *digest, + const crypto_digest_checkpoint_t *checkpoint); +void crypto_digest_assign(crypto_digest_t *into, + const crypto_digest_t *from); +void crypto_hmac_sha256(char *hmac_out, + const char *key, size_t key_len, + const char *msg, size_t msg_len); +void crypto_mac_sha3_256(uint8_t *mac_out, size_t len_out, + const uint8_t *key, size_t key_len, + const uint8_t *msg, size_t msg_len); + +/* xof functions*/ +crypto_xof_t *crypto_xof_new(void); +void crypto_xof_add_bytes(crypto_xof_t *xof, const uint8_t *data, size_t len); +void crypto_xof_squeeze_bytes(crypto_xof_t *xof, uint8_t *out, size_t len); +void crypto_xof_free_(crypto_xof_t *xof); +#define crypto_xof_free(xof) \ + FREE_AND_NULL(crypto_xof_t, crypto_xof_free_, (xof)) + +#ifdef TOR_UNIT_TESTS +digest_algorithm_t crypto_digest_get_algorithm(crypto_digest_t *digest); +#endif + +#endif /* !defined(TOR_CRYPTO_DIGEST_H) */ diff --git a/src/lib/crypt_ops/crypto_ed25519.c b/src/lib/crypt_ops/crypto_ed25519.c new file mode 100644 index 0000000000..400f963898 --- /dev/null +++ b/src/lib/crypt_ops/crypto_ed25519.c @@ -0,0 +1,821 @@ +/* Copyright (c) 2013-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_ed25519.c + * + * \brief Wrapper code for an ed25519 implementation. + * + * Ed25519 is a Schnorr signature on a Twisted Edwards curve, defined + * by Dan Bernstein. For more information, see https://ed25519.cr.yp.to/ + * + * This module wraps our choice of Ed25519 backend, and provides a few + * convenience functions for checking and generating signatures. It also + * provides Tor-specific tools for key blinding and for converting Ed25519 + * keys to and from the corresponding Curve25519 keys. + */ + +#define CRYPTO_ED25519_PRIVATE +#include "orconfig.h" +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#include "lib/ctime/di_ops.h" +#include "lib/crypt_ops/crypto_curve25519.h" +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/crypt_ops/crypto_ed25519.h" +#include "lib/crypt_ops/crypto_format.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/encoding/binascii.h" +#include "lib/string/util_string.h" + +#include "ed25519/ref10/ed25519_ref10.h" +#include "ed25519/donna/ed25519_donna_tor.h" + +#include <string.h> +#include <errno.h> + +static void pick_ed25519_impl(void); + +/** An Ed25519 implementation, as a set of function pointers. */ +typedef struct { + int (*selftest)(void); + + int (*seckey)(unsigned char *); + int (*seckey_expand)(unsigned char *, const unsigned char *); + int (*pubkey)(unsigned char *, const unsigned char *); + int (*keygen)(unsigned char *, unsigned char *); + + int (*open)(const unsigned char *, const unsigned char *, size_t, const + unsigned char *); + int (*sign)(unsigned char *, const unsigned char *, size_t, + const unsigned char *, const unsigned char *); + int (*open_batch)(const unsigned char **, size_t *, const unsigned char **, + const unsigned char **, size_t, int *); + + int (*blind_secret_key)(unsigned char *, const unsigned char *, + const unsigned char *); + int (*blind_public_key)(unsigned char *, const unsigned char *, + const unsigned char *); + + int (*pubkey_from_curve25519_pubkey)(unsigned char *, const unsigned char *, + int); + + int (*ed25519_scalarmult_with_group_order)(unsigned char *, + const unsigned char *); +} ed25519_impl_t; + +/** The Ref10 Ed25519 implementation. This one is pure C and lightly + * optimized. */ +static const ed25519_impl_t impl_ref10 = { + NULL, + + ed25519_ref10_seckey, + ed25519_ref10_seckey_expand, + ed25519_ref10_pubkey, + ed25519_ref10_keygen, + + ed25519_ref10_open, + ed25519_ref10_sign, + NULL, + + ed25519_ref10_blind_secret_key, + ed25519_ref10_blind_public_key, + + ed25519_ref10_pubkey_from_curve25519_pubkey, + ed25519_ref10_scalarmult_with_group_order, +}; + +/** The Ref10 Ed25519 implementation. This one is heavily optimized, but still + * mostly C. The C still tends to be heavily platform-specific. */ +static const ed25519_impl_t impl_donna = { + ed25519_donna_selftest, + + ed25519_donna_seckey, + ed25519_donna_seckey_expand, + ed25519_donna_pubkey, + ed25519_donna_keygen, + + ed25519_donna_open, + ed25519_donna_sign, + ed25519_sign_open_batch_donna, + + ed25519_donna_blind_secret_key, + ed25519_donna_blind_public_key, + + ed25519_donna_pubkey_from_curve25519_pubkey, + ed25519_donna_scalarmult_with_group_order, +}; + +/** Which Ed25519 implementation are we using? NULL if we haven't decided + * yet. */ +static const ed25519_impl_t *ed25519_impl = NULL; + +/** Helper: Return our chosen Ed25519 implementation. + * + * This should only be called after we've picked an implementation, but + * it _does_ recover if you forget this. + **/ +static inline const ed25519_impl_t * +get_ed_impl(void) +{ + if (BUG(ed25519_impl == NULL)) { + pick_ed25519_impl(); // LCOV_EXCL_LINE - We always call ed25519_init(). + } + return ed25519_impl; +} + +#ifdef TOR_UNIT_TESTS +/** For testing: used to remember our actual choice of Ed25519 + * implementation */ +static const ed25519_impl_t *saved_ed25519_impl = NULL; +/** For testing: Use the Ed25519 implementation called <b>name</b> until + * crypto_ed25519_testing_restore_impl is called. Recognized names are + * "donna" and "ref10". */ +void +crypto_ed25519_testing_force_impl(const char *name) +{ + tor_assert(saved_ed25519_impl == NULL); + saved_ed25519_impl = ed25519_impl; + if (! strcmp(name, "donna")) { + ed25519_impl = &impl_donna; + } else { + tor_assert(!strcmp(name, "ref10")); + ed25519_impl = &impl_ref10; + } +} +/** For testing: go back to whatever Ed25519 implementation we had picked + * before crypto_ed25519_testing_force_impl was called. + */ +void +crypto_ed25519_testing_restore_impl(void) +{ + ed25519_impl = saved_ed25519_impl; + saved_ed25519_impl = NULL; +} +#endif /* defined(TOR_UNIT_TESTS) */ + +/** + * Initialize a new ed25519 secret key in <b>seckey_out</b>. If + * <b>extra_strong</b>, take the RNG inputs directly from the operating + * system. Return 0 on success, -1 on failure. + */ +int +ed25519_secret_key_generate(ed25519_secret_key_t *seckey_out, + int extra_strong) +{ + int r; + uint8_t seed[32]; + if (extra_strong) + crypto_strongest_rand(seed, sizeof(seed)); + else + crypto_rand((char*)seed, sizeof(seed)); + + r = get_ed_impl()->seckey_expand(seckey_out->seckey, seed); + memwipe(seed, 0, sizeof(seed)); + + return r < 0 ? -1 : 0; +} + +/** + * Given a 32-byte random seed in <b>seed</b>, expand it into an ed25519 + * secret key in <b>seckey_out</b>. Return 0 on success, -1 on failure. + */ +int +ed25519_secret_key_from_seed(ed25519_secret_key_t *seckey_out, + const uint8_t *seed) +{ + if (get_ed_impl()->seckey_expand(seckey_out->seckey, seed) < 0) + return -1; + return 0; +} + +/** + * Given a secret key in <b>seckey</b>, expand it into an + * ed25519 public key. Return 0 on success, -1 on failure. + */ +int +ed25519_public_key_generate(ed25519_public_key_t *pubkey_out, + const ed25519_secret_key_t *seckey) +{ + if (get_ed_impl()->pubkey(pubkey_out->pubkey, seckey->seckey) < 0) + return -1; + return 0; +} + +/** Generate a new ed25519 keypair in <b>keypair_out</b>. If + * <b>extra_strong</b> is set, try to mix some system entropy into the key + * generation process. Return 0 on success, -1 on failure. */ +int +ed25519_keypair_generate(ed25519_keypair_t *keypair_out, int extra_strong) +{ + if (ed25519_secret_key_generate(&keypair_out->seckey, extra_strong) < 0) + return -1; + if (ed25519_public_key_generate(&keypair_out->pubkey, + &keypair_out->seckey)<0) + return -1; + return 0; +} + +/** Return true iff 'pubkey' is set to zero (eg to indicate that it is not + * set). */ +int +ed25519_public_key_is_zero(const ed25519_public_key_t *pubkey) +{ + return tor_mem_is_zero((char*)pubkey->pubkey, ED25519_PUBKEY_LEN); +} + +/* Return a heap-allocated array that contains <b>msg</b> prefixed by the + * string <b>prefix_str</b>. Set <b>final_msg_len_out</b> to the size of the + * final array. If an error occurred, return NULL. It's the responsibility of + * the caller to free the returned array. */ +static uint8_t * +get_prefixed_msg(const uint8_t *msg, size_t msg_len, + const char *prefix_str, + size_t *final_msg_len_out) +{ + size_t prefixed_msg_len, prefix_len; + uint8_t *prefixed_msg; + + tor_assert(prefix_str); + tor_assert(final_msg_len_out); + + prefix_len = strlen(prefix_str); + + /* msg_len + strlen(prefix_str) must not overflow. */ + if (msg_len > SIZE_T_CEILING - prefix_len) { + return NULL; + } + + prefixed_msg_len = msg_len + prefix_len; + prefixed_msg = tor_malloc_zero(prefixed_msg_len); + + memcpy(prefixed_msg, prefix_str, prefix_len); + memcpy(prefixed_msg + prefix_len, msg, msg_len); + + *final_msg_len_out = prefixed_msg_len; + return prefixed_msg; +} + +/** + * Set <b>signature_out</b> to a signature of the <b>len</b>-byte message + * <b>msg</b>, using the secret and public key in <b>keypair</b>. + * + * Return 0 if we successfully signed the message, otherwise return -1. + */ +int +ed25519_sign(ed25519_signature_t *signature_out, + const uint8_t *msg, size_t len, + const ed25519_keypair_t *keypair) +{ + if (get_ed_impl()->sign(signature_out->sig, msg, len, + keypair->seckey.seckey, + keypair->pubkey.pubkey) < 0) { + return -1; + } + + return 0; +} + +/** + * Like ed25519_sign(), but also prefix <b>msg</b> with <b>prefix_str</b> + * before signing. <b>prefix_str</b> must be a NUL-terminated string. + */ +MOCK_IMPL(int, +ed25519_sign_prefixed,(ed25519_signature_t *signature_out, + const uint8_t *msg, size_t msg_len, + const char *prefix_str, + const ed25519_keypair_t *keypair)) +{ + int retval; + size_t prefixed_msg_len; + uint8_t *prefixed_msg; + + tor_assert(prefix_str); + + prefixed_msg = get_prefixed_msg(msg, msg_len, prefix_str, + &prefixed_msg_len); + if (BUG(!prefixed_msg)) { + /* LCOV_EXCL_START -- only possible when the message and prefix are + * ridiculously huge */ + log_warn(LD_GENERAL, "Failed to get prefixed msg."); + return -1; + /* LCOV_EXCL_STOP */ + } + + retval = ed25519_sign(signature_out, + prefixed_msg, prefixed_msg_len, + keypair); + tor_free(prefixed_msg); + + return retval; +} + +/** + * Check whether if <b>signature</b> is a valid signature for the + * <b>len</b>-byte message in <b>msg</b> made with the key <b>pubkey</b>. + * + * Return 0 if the signature is valid; -1 if it isn't. + */ +MOCK_IMPL(int, +ed25519_checksig,(const ed25519_signature_t *signature, + const uint8_t *msg, size_t len, + const ed25519_public_key_t *pubkey)) +{ + return + get_ed_impl()->open(signature->sig, msg, len, pubkey->pubkey) < 0 ? -1 : 0; +} + +/** + * Like ed2519_checksig(), but also prefix <b>msg</b> with <b>prefix_str</b> + * before verifying signature. <b>prefix_str</b> must be a NUL-terminated + * string. + */ +int +ed25519_checksig_prefixed(const ed25519_signature_t *signature, + const uint8_t *msg, size_t msg_len, + const char *prefix_str, + const ed25519_public_key_t *pubkey) +{ + int retval; + size_t prefixed_msg_len; + uint8_t *prefixed_msg; + + prefixed_msg = get_prefixed_msg(msg, msg_len, prefix_str, + &prefixed_msg_len); + if (BUG(!prefixed_msg)) { + /* LCOV_EXCL_START -- only possible when the message and prefix are + * ridiculously huge */ + log_warn(LD_GENERAL, "Failed to get prefixed msg."); + return -1; + /* LCOV_EXCL_STOP */ + } + + retval = ed25519_checksig(signature, + prefixed_msg, prefixed_msg_len, + pubkey); + tor_free(prefixed_msg); + + return retval; +} + +/** Validate every signature among those in <b>checkable</b>, which contains + * exactly <b>n_checkable</b> elements. If <b>okay_out</b> is non-NULL, set + * the i'th element of <b>okay_out</b> to 1 if the i'th element of + * <b>checkable</b> is valid, and to 0 otherwise. Return 0 if every signature + * was valid. Otherwise return -N, where N is the number of invalid + * signatures. + */ +MOCK_IMPL(int, +ed25519_checksig_batch,(int *okay_out, + const ed25519_checkable_t *checkable, + int n_checkable)) +{ + int i, res; + const ed25519_impl_t *impl = get_ed_impl(); + + if (impl->open_batch == NULL) { + /* No batch verification implementation available, fake it by checking the + * each signature individually. + */ + res = 0; + for (i = 0; i < n_checkable; ++i) { + const ed25519_checkable_t *ch = &checkable[i]; + int r = ed25519_checksig(&ch->signature, ch->msg, ch->len, ch->pubkey); + if (r < 0) + --res; + if (okay_out) + okay_out[i] = (r == 0); + } + } else { + /* ed25519-donna style batch verification available. + * + * Theoretically, this should only be called if n_checkable >= 3, since + * that's the threshold where the batch verification actually kicks in, + * but the only difference is a few mallocs/frees. + */ + const uint8_t **ms; + size_t *lens; + const uint8_t **pks; + const uint8_t **sigs; + int *oks; + int all_ok; + + ms = tor_calloc(n_checkable, sizeof(uint8_t*)); + lens = tor_calloc(n_checkable, sizeof(size_t)); + pks = tor_calloc(n_checkable, sizeof(uint8_t*)); + sigs = tor_calloc(n_checkable, sizeof(uint8_t*)); + oks = okay_out ? okay_out : tor_calloc(n_checkable, sizeof(int)); + + for (i = 0; i < n_checkable; ++i) { + ms[i] = checkable[i].msg; + lens[i] = checkable[i].len; + pks[i] = checkable[i].pubkey->pubkey; + sigs[i] = checkable[i].signature.sig; + oks[i] = 0; + } + + res = 0; + all_ok = impl->open_batch(ms, lens, pks, sigs, n_checkable, oks); + for (i = 0; i < n_checkable; ++i) { + if (!oks[i]) + --res; + } + /* XXX: For now sanity check oks with the return value. Once we have + * more confidence in the code, if `all_ok == 0` we can skip iterating + * over oks since all the signatures were found to be valid. + */ + tor_assert(((res == 0) && !all_ok) || ((res < 0) && all_ok)); + + tor_free(ms); + tor_free(lens); + tor_free(pks); + tor_free(sigs); + if (! okay_out) + tor_free(oks); + } + + return res; +} + +/** + * Given a curve25519 keypair in <b>inp</b>, generate a corresponding + * ed25519 keypair in <b>out</b>, and set <b>signbit_out</b> to the + * sign bit of the X coordinate of the ed25519 key. + * + * NOTE THAT IT IS PROBABLY NOT SAFE TO USE THE GENERATED KEY FOR ANYTHING + * OUTSIDE OF WHAT'S PRESENTED IN PROPOSAL 228. In particular, it's probably + * not a great idea to use it to sign attacker-supplied anything. + */ +int +ed25519_keypair_from_curve25519_keypair(ed25519_keypair_t *out, + int *signbit_out, + const curve25519_keypair_t *inp) +{ + const char string[] = "Derive high part of ed25519 key from curve25519 key"; + ed25519_public_key_t pubkey_check; + crypto_digest_t *ctx; + uint8_t sha512_output[DIGEST512_LEN]; + + memcpy(out->seckey.seckey, inp->seckey.secret_key, 32); + + ctx = crypto_digest512_new(DIGEST_SHA512); + crypto_digest_add_bytes(ctx, (const char*)out->seckey.seckey, 32); + crypto_digest_add_bytes(ctx, (const char*)string, sizeof(string)); + crypto_digest_get_digest(ctx, (char *)sha512_output, sizeof(sha512_output)); + crypto_digest_free(ctx); + memcpy(out->seckey.seckey + 32, sha512_output, 32); + + ed25519_public_key_generate(&out->pubkey, &out->seckey); + + *signbit_out = out->pubkey.pubkey[31] >> 7; + + ed25519_public_key_from_curve25519_public_key(&pubkey_check, &inp->pubkey, + *signbit_out); + + tor_assert(fast_memeq(pubkey_check.pubkey, out->pubkey.pubkey, 32)); + + memwipe(&pubkey_check, 0, sizeof(pubkey_check)); + memwipe(sha512_output, 0, sizeof(sha512_output)); + + return 0; +} + +/** + * Given a curve25519 public key and sign bit of X coordinate of the ed25519 + * public key, generate the corresponding ed25519 public key. + */ +int +ed25519_public_key_from_curve25519_public_key(ed25519_public_key_t *pubkey, + const curve25519_public_key_t *pubkey_in, + int signbit) +{ + return get_ed_impl()->pubkey_from_curve25519_pubkey(pubkey->pubkey, + pubkey_in->public_key, + signbit); +} + +/** + * Given an ed25519 keypair in <b>inp</b>, generate a corresponding + * ed25519 keypair in <b>out</b>, blinded by the corresponding 32-byte input + * in 'param'. + * + * Tor uses key blinding for the "next-generation" hidden services design: + * service descriptors are encrypted with a key derived from the service's + * long-term public key, and then signed with (and stored at a position + * indexed by) a short-term key derived by blinding the long-term keys. + * + * Return 0 if blinding was successful, else return -1. */ +int +ed25519_keypair_blind(ed25519_keypair_t *out, + const ed25519_keypair_t *inp, + const uint8_t *param) +{ + ed25519_public_key_t pubkey_check; + + get_ed_impl()->blind_secret_key(out->seckey.seckey, + inp->seckey.seckey, param); + + if (ed25519_public_blind(&pubkey_check, &inp->pubkey, param) < 0) { + return -1; + } + ed25519_public_key_generate(&out->pubkey, &out->seckey); + + tor_assert(fast_memeq(pubkey_check.pubkey, out->pubkey.pubkey, 32)); + + memwipe(&pubkey_check, 0, sizeof(pubkey_check)); + + return 0; +} + +/** + * Given an ed25519 public key in <b>inp</b>, generate a corresponding blinded + * public key in <b>out</b>, blinded with the 32-byte parameter in + * <b>param</b>. Return 0 on success, -1 on railure. + */ +int +ed25519_public_blind(ed25519_public_key_t *out, + const ed25519_public_key_t *inp, + const uint8_t *param) +{ + return get_ed_impl()->blind_public_key(out->pubkey, inp->pubkey, param); +} + +/** + * Store seckey unencrypted to <b>filename</b>, marking it with <b>tag</b>. + * Return 0 on success, -1 on failure. + */ +int +ed25519_seckey_write_to_file(const ed25519_secret_key_t *seckey, + const char *filename, + const char *tag) +{ + return crypto_write_tagged_contents_to_file(filename, + "ed25519v1-secret", + tag, + seckey->seckey, + sizeof(seckey->seckey)); +} + +/** + * Read seckey unencrypted from <b>filename</b>, storing it into + * <b>seckey_out</b>. Set *<b>tag_out</b> to the tag it was marked with. + * Return 0 on success, -1 on failure. + */ +int +ed25519_seckey_read_from_file(ed25519_secret_key_t *seckey_out, + char **tag_out, + const char *filename) +{ + ssize_t len; + + len = crypto_read_tagged_contents_from_file(filename, "ed25519v1-secret", + tag_out, seckey_out->seckey, + sizeof(seckey_out->seckey)); + if (len == sizeof(seckey_out->seckey)) { + return 0; + } else if (len >= 0) { + errno = EINVAL; + } + + tor_free(*tag_out); + return -1; +} + +/** + * Store pubkey unencrypted to <b>filename</b>, marking it with <b>tag</b>. + * Return 0 on success, -1 on failure. + */ +int +ed25519_pubkey_write_to_file(const ed25519_public_key_t *pubkey, + const char *filename, + const char *tag) +{ + return crypto_write_tagged_contents_to_file(filename, + "ed25519v1-public", + tag, + pubkey->pubkey, + sizeof(pubkey->pubkey)); +} + +/** + * Store pubkey unencrypted to <b>filename</b>, marking it with <b>tag</b>. + * Return 0 on success, -1 on failure. + */ +int +ed25519_pubkey_read_from_file(ed25519_public_key_t *pubkey_out, + char **tag_out, + const char *filename) +{ + ssize_t len; + + len = crypto_read_tagged_contents_from_file(filename, "ed25519v1-public", + tag_out, pubkey_out->pubkey, + sizeof(pubkey_out->pubkey)); + if (len == sizeof(pubkey_out->pubkey)) { + return 0; + } else if (len >= 0) { + errno = EINVAL; + } + + tor_free(*tag_out); + return -1; +} + +/** Release all storage held for <b>kp</b>. */ +void +ed25519_keypair_free_(ed25519_keypair_t *kp) +{ + if (! kp) + return; + + memwipe(kp, 0, sizeof(*kp)); + tor_free(kp); +} + +/** Return true iff <b>key1</b> and <b>key2</b> are the same public key. */ +int +ed25519_pubkey_eq(const ed25519_public_key_t *key1, + const ed25519_public_key_t *key2) +{ + tor_assert(key1); + tor_assert(key2); + return tor_memeq(key1->pubkey, key2->pubkey, ED25519_PUBKEY_LEN); +} + +/** + * Set <b>dest</b> to contain the same key as <b>src</b>. + */ +void +ed25519_pubkey_copy(ed25519_public_key_t *dest, + const ed25519_public_key_t *src) +{ + tor_assert(dest); + tor_assert(src); + memcpy(dest, src, sizeof(ed25519_public_key_t)); +} + +/** Check whether the given Ed25519 implementation seems to be working. + * If so, return 0; otherwise return -1. */ +MOCK_IMPL(STATIC int, +ed25519_impl_spot_check,(void)) +{ + static const uint8_t alicesk[32] = { + 0xc5,0xaa,0x8d,0xf4,0x3f,0x9f,0x83,0x7b, + 0xed,0xb7,0x44,0x2f,0x31,0xdc,0xb7,0xb1, + 0x66,0xd3,0x85,0x35,0x07,0x6f,0x09,0x4b, + 0x85,0xce,0x3a,0x2e,0x0b,0x44,0x58,0xf7 + }; + static const uint8_t alicepk[32] = { + 0xfc,0x51,0xcd,0x8e,0x62,0x18,0xa1,0xa3, + 0x8d,0xa4,0x7e,0xd0,0x02,0x30,0xf0,0x58, + 0x08,0x16,0xed,0x13,0xba,0x33,0x03,0xac, + 0x5d,0xeb,0x91,0x15,0x48,0x90,0x80,0x25 + }; + static const uint8_t alicemsg[2] = { 0xaf, 0x82 }; + static const uint8_t alicesig[64] = { + 0x62,0x91,0xd6,0x57,0xde,0xec,0x24,0x02, + 0x48,0x27,0xe6,0x9c,0x3a,0xbe,0x01,0xa3, + 0x0c,0xe5,0x48,0xa2,0x84,0x74,0x3a,0x44, + 0x5e,0x36,0x80,0xd7,0xdb,0x5a,0xc3,0xac, + 0x18,0xff,0x9b,0x53,0x8d,0x16,0xf2,0x90, + 0xae,0x67,0xf7,0x60,0x98,0x4d,0xc6,0x59, + 0x4a,0x7c,0x15,0xe9,0x71,0x6e,0xd2,0x8d, + 0xc0,0x27,0xbe,0xce,0xea,0x1e,0xc4,0x0a + }; + const ed25519_impl_t *impl = get_ed_impl(); + uint8_t sk[ED25519_SECKEY_LEN]; + uint8_t pk[ED25519_PUBKEY_LEN]; + uint8_t sig[ED25519_SIG_LEN]; + int r = 0; + + /* Some implementations (eg: The modified Ed25519-donna) have handy self-test + * code that sanity-checks the internals. If present, use that to screen out + * catastrophic errors like massive compiler failure. + */ + if (impl->selftest && impl->selftest() != 0) + goto fail; + + /* Validate results versus known answer tests. People really should be + * running "make test" instead of relying on this, but it's better than + * nothing. + * + * Test vectors taken from "EdDSA & Ed25519 - 6. Test Vectors for Ed25519 + * (TEST3)" (draft-josefsson-eddsa-ed25519-03). + */ + + /* Key expansion, public key derivation. */ + if (impl->seckey_expand(sk, alicesk) < 0) + goto fail; + if (impl->pubkey(pk, sk) < 0) + goto fail; + if (fast_memneq(pk, alicepk, ED25519_PUBKEY_LEN)) + goto fail; + + /* Signing, verification. */ + if (impl->sign(sig, alicemsg, sizeof(alicemsg), sk, pk) < 0) + return -1; + if (fast_memneq(sig, alicesig, ED25519_SIG_LEN)) + return -1; + if (impl->open(sig, alicemsg, sizeof(alicemsg), pk) < 0) + return -1; + + /* XXX/yawning: Someone that's more paranoid than I am, can write "Assume + * ref0 is canonical, and fuzz impl against it" if they want, but I doubt + * that will catch anything that the known answer tests won't. + */ + goto end; + + // LCOV_EXCL_START -- We can only reach this if our ed25519 implementation is + // broken. + fail: + r = -1; + // LCOV_EXCL_STOP + end: + return r; +} + +/** Force the Ed25519 implementation to a given one, without sanity checking + * the output. Used for testing. + */ +void +ed25519_set_impl_params(int use_donna) +{ + if (use_donna) + ed25519_impl = &impl_donna; + else + ed25519_impl = &impl_ref10; +} + +/** Choose whether to use the Ed25519-donna implementation. */ +static void +pick_ed25519_impl(void) +{ + ed25519_impl = &impl_donna; + + if (ed25519_impl_spot_check() == 0) + return; + + /* LCOV_EXCL_START + * unreachable unless ed25519_donna is broken */ + log_warn(LD_CRYPTO, "The Ed25519-donna implementation seems broken; using " + "the ref10 implementation."); + ed25519_impl = &impl_ref10; + /* LCOV_EXCL_STOP */ +} + +/* Initialize the Ed25519 implementation. This is necessary if you're + * going to use them in a multithreaded setting, and not otherwise. */ +void +ed25519_init(void) +{ + pick_ed25519_impl(); +} + +/* Return true if <b>point</b> is the identity element of the ed25519 group. */ +static int +ed25519_point_is_identity_element(const uint8_t *point) +{ + /* The identity element in ed25159 is the point with coordinates (0,1). */ + static const uint8_t ed25519_identity[32] = { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + tor_assert(sizeof(ed25519_identity) == ED25519_PUBKEY_LEN); + return tor_memeq(point, ed25519_identity, sizeof(ed25519_identity)); +} + +/** Validate <b>pubkey</b> to ensure that it has no torsion component. + * Return 0 if <b>pubkey</b> is valid, else return -1. */ +int +ed25519_validate_pubkey(const ed25519_public_key_t *pubkey) +{ + uint8_t result[32] = {9}; + + /* First check that we were not given the identity element */ + if (ed25519_point_is_identity_element(pubkey->pubkey)) { + log_warn(LD_CRYPTO, "ed25519 pubkey is the identity"); + return -1; + } + + /* For any point on the curve, doing l*point should give the identity element + * (where l is the group order). Do the computation and check that the + * identity element is returned. */ + if (get_ed_impl()->ed25519_scalarmult_with_group_order(result, + pubkey->pubkey) < 0) { + log_warn(LD_CRYPTO, "ed25519 group order scalarmult failed"); + return -1; + } + + if (!ed25519_point_is_identity_element(result)) { + log_warn(LD_CRYPTO, "ed25519 validation failed"); + return -1; + } + + return 0; +} diff --git a/src/lib/crypt_ops/crypto_ed25519.h b/src/lib/crypt_ops/crypto_ed25519.h new file mode 100644 index 0000000000..325b28244d --- /dev/null +++ b/src/lib/crypt_ops/crypto_ed25519.h @@ -0,0 +1,144 @@ +/* Copyright (c) 2012-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_ed25519.h + * \brief Header for crypto_ed25519.c + **/ + +#ifndef TOR_CRYPTO_ED25519_H +#define TOR_CRYPTO_ED25519_H + +#include "lib/testsupport/testsupport.h" +#include "lib/cc/torint.h" +#include "lib/crypt_ops/crypto_curve25519.h" +#include "lib/defs/x25519_sizes.h" + +/** An Ed25519 signature. */ +typedef struct ed25519_signature_t { + uint8_t sig[ED25519_SIG_LEN]; +} ed25519_signature_t; + +/** An Ed25519 public key */ +typedef struct ed25519_public_key_t { + uint8_t pubkey[ED25519_PUBKEY_LEN]; +} ed25519_public_key_t; + +/** An Ed25519 secret key */ +typedef struct ed25519_secret_key_t { + /** Note that we store secret keys in an expanded format that doesn't match + * the format from standard ed25519. Ed25519 stores a 32-byte value k and + * expands it into a 64-byte H(k), using the first 32 bytes for a multiplier + * of the base point, and second 32 bytes as an input to a hash function + * for deriving r. But because we implement key blinding, we need to store + * keys in the 64-byte expanded form. */ + uint8_t seckey[ED25519_SECKEY_LEN]; +} ed25519_secret_key_t; + +/** An Ed25519 keypair. */ +typedef struct ed25519_keypair_t { + ed25519_public_key_t pubkey; + ed25519_secret_key_t seckey; +} ed25519_keypair_t; + +int ed25519_secret_key_generate(ed25519_secret_key_t *seckey_out, + int extra_strong); +int ed25519_secret_key_from_seed(ed25519_secret_key_t *seckey_out, + const uint8_t *seed); + +int ed25519_public_key_generate(ed25519_public_key_t *pubkey_out, + const ed25519_secret_key_t *seckey); +int ed25519_keypair_generate(ed25519_keypair_t *keypair_out, int extra_strong); +int ed25519_sign(ed25519_signature_t *signature_out, + const uint8_t *msg, size_t len, + const ed25519_keypair_t *key); +MOCK_DECL(int,ed25519_checksig,(const ed25519_signature_t *signature, + const uint8_t *msg, size_t len, + const ed25519_public_key_t *pubkey)); + +MOCK_DECL(int, +ed25519_sign_prefixed,(ed25519_signature_t *signature_out, + const uint8_t *msg, size_t len, + const char *prefix_str, + const ed25519_keypair_t *keypair)); + +int +ed25519_checksig_prefixed(const ed25519_signature_t *signature, + const uint8_t *msg, size_t len, + const char *prefix_str, + const ed25519_public_key_t *pubkey); + +int ed25519_public_key_is_zero(const ed25519_public_key_t *pubkey); + +/** + * A collection of information necessary to check an Ed25519 signature. Used + * for batch verification. + */ +typedef struct { + /** The public key that supposedly generated the signature. */ + const ed25519_public_key_t *pubkey; + /** The signature to check. */ + ed25519_signature_t signature; + /** The message that the signature is supposed to have been applied to. */ + const uint8_t *msg; + /** The length of the message. */ + size_t len; +} ed25519_checkable_t; + +MOCK_DECL(int, ed25519_checksig_batch,(int *okay_out, + const ed25519_checkable_t *checkable, + int n_checkable)); + +int ed25519_keypair_from_curve25519_keypair(ed25519_keypair_t *out, + int *signbit_out, + const curve25519_keypair_t *inp); + +int ed25519_public_key_from_curve25519_public_key(ed25519_public_key_t *pubkey, + const curve25519_public_key_t *pubkey_in, + int signbit); +int ed25519_keypair_blind(ed25519_keypair_t *out, + const ed25519_keypair_t *inp, + const uint8_t *param); +int ed25519_public_blind(ed25519_public_key_t *out, + const ed25519_public_key_t *inp, + const uint8_t *param); + +/* XXXX read encrypted, write encrypted. */ + +int ed25519_seckey_write_to_file(const ed25519_secret_key_t *seckey, + const char *filename, + const char *tag); +int ed25519_seckey_read_from_file(ed25519_secret_key_t *seckey_out, + char **tag_out, + const char *filename); +int ed25519_pubkey_write_to_file(const ed25519_public_key_t *pubkey, + const char *filename, + const char *tag); +int ed25519_pubkey_read_from_file(ed25519_public_key_t *pubkey_out, + char **tag_out, + const char *filename); + +void ed25519_keypair_free_(ed25519_keypair_t *kp); +#define ed25519_keypair_free(kp) \ + FREE_AND_NULL(ed25519_keypair_t, ed25519_keypair_free_, (kp)) + +int ed25519_pubkey_eq(const ed25519_public_key_t *key1, + const ed25519_public_key_t *key2); +void ed25519_pubkey_copy(ed25519_public_key_t *dest, + const ed25519_public_key_t *src); + +void ed25519_set_impl_params(int use_donna); +void ed25519_init(void); + +int ed25519_validate_pubkey(const ed25519_public_key_t *pubkey); + +#ifdef TOR_UNIT_TESTS +void crypto_ed25519_testing_force_impl(const char *name); +void crypto_ed25519_testing_restore_impl(void); +#endif + +#ifdef CRYPTO_ED25519_PRIVATE +MOCK_DECL(STATIC int, ed25519_impl_spot_check, (void)); +#endif + +#endif /* !defined(TOR_CRYPTO_ED25519_H) */ diff --git a/src/lib/crypt_ops/crypto_format.c b/src/lib/crypt_ops/crypto_format.c new file mode 100644 index 0000000000..84f73e5272 --- /dev/null +++ b/src/lib/crypt_ops/crypto_format.c @@ -0,0 +1,305 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_format.c + * + * \brief Formatting and parsing code for crypto-related data structures. + */ + +#include "orconfig.h" +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#include "lib/container/smartlist.h" +#include "lib/crypt_ops/crypto_curve25519.h" +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/crypt_ops/crypto_ed25519.h" +#include "lib/crypt_ops/crypto_format.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/string/compat_string.h" +#include "lib/string/util_string.h" +#include "lib/string/printf.h" +#include "lib/encoding/binascii.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/fs/files.h" + +#include <string.h> +#include <errno.h> + +/** Write the <b>datalen</b> bytes from <b>data</b> to the file named + * <b>fname</b> in the tagged-data format. This format contains a + * 32-byte header, followed by the data itself. The header is the + * NUL-padded string "== <b>typestring</b>: <b>tag</b> ==". The length + * of <b>typestring</b> and <b>tag</b> must therefore be no more than + * 24. + **/ +int +crypto_write_tagged_contents_to_file(const char *fname, + const char *typestring, + const char *tag, + const uint8_t *data, + size_t datalen) +{ + char header[32]; + smartlist_t *chunks = smartlist_new(); + sized_chunk_t ch0, ch1; + int r = -1; + + memset(header, 0, sizeof(header)); + if (tor_snprintf(header, sizeof(header), + "== %s: %s ==", typestring, tag) < 0) + goto end; + ch0.bytes = header; + ch0.len = 32; + ch1.bytes = (const char*) data; + ch1.len = datalen; + smartlist_add(chunks, &ch0); + smartlist_add(chunks, &ch1); + + r = write_chunks_to_file(fname, chunks, 1, 0); + + end: + smartlist_free(chunks); + return r; +} + +/** Read a tagged-data file from <b>fname</b> into the + * <b>data_out_len</b>-byte buffer in <b>data_out</b>. Check that the + * typestring matches <b>typestring</b>; store the tag into a newly allocated + * string in <b>tag_out</b>. Return -1 on failure, and the number of bytes of + * data on success. Preserves the errno from reading the file. */ +ssize_t +crypto_read_tagged_contents_from_file(const char *fname, + const char *typestring, + char **tag_out, + uint8_t *data_out, + ssize_t data_out_len) +{ + char prefix[33]; + char *content = NULL; + struct stat st; + ssize_t r = -1; + size_t st_size = 0; + int saved_errno = 0; + + *tag_out = NULL; + st.st_size = 0; + content = read_file_to_str(fname, RFTS_BIN|RFTS_IGNORE_MISSING, &st); + if (! content) { + saved_errno = errno; + goto end; + } + if (st.st_size < 32 || st.st_size > 32 + data_out_len) { + saved_errno = EINVAL; + goto end; + } + st_size = (size_t)st.st_size; + + memcpy(prefix, content, 32); + prefix[32] = 0; + /* Check type, extract tag. */ + if (strcmpstart(prefix, "== ") || strcmpend(prefix, " ==") || + ! tor_mem_is_zero(prefix+strlen(prefix), 32-strlen(prefix))) { + saved_errno = EINVAL; + goto end; + } + + if (strcmpstart(prefix+3, typestring) || + 3+strlen(typestring) >= 32 || + strcmpstart(prefix+3+strlen(typestring), ": ")) { + saved_errno = EINVAL; + goto end; + } + + *tag_out = tor_strndup(prefix+5+strlen(typestring), + strlen(prefix)-8-strlen(typestring)); + + memcpy(data_out, content+32, st_size-32); + r = st_size - 32; + + end: + if (content) + memwipe(content, 0, st_size); + tor_free(content); + if (saved_errno) + errno = saved_errno; + return r; +} + +/** Encode <b>pkey</b> as a base64-encoded string, without trailing "=" + * characters, in the buffer <b>output</b>, which must have at least + * CURVE25519_BASE64_PADDED_LEN+1 bytes available. Return 0 on success, -1 on + * failure. */ +int +curve25519_public_to_base64(char *output, + const curve25519_public_key_t *pkey) +{ + char buf[128]; + base64_encode(buf, sizeof(buf), + (const char*)pkey->public_key, CURVE25519_PUBKEY_LEN, 0); + buf[CURVE25519_BASE64_PADDED_LEN] = '\0'; + memcpy(output, buf, CURVE25519_BASE64_PADDED_LEN+1); + return 0; +} + +/** Try to decode a base64-encoded curve25519 public key from <b>input</b> + * into the object at <b>pkey</b>. Return 0 on success, -1 on failure. + * Accepts keys with or without a trailing "=". */ +int +curve25519_public_from_base64(curve25519_public_key_t *pkey, + const char *input) +{ + size_t len = strlen(input); + if (len == CURVE25519_BASE64_PADDED_LEN - 1) { + /* not padded */ + return digest256_from_base64((char*)pkey->public_key, input); + } else if (len == CURVE25519_BASE64_PADDED_LEN) { + char buf[128]; + if (base64_decode(buf, sizeof(buf), input, len) != CURVE25519_PUBKEY_LEN) + return -1; + memcpy(pkey->public_key, buf, CURVE25519_PUBKEY_LEN); + return 0; + } else { + return -1; + } +} + +/** For logging convenience: Convert <b>pkey</b> to a statically allocated + * base64 string and return it. Not threadsafe. Format not meant to be + * computer-readable; it may change in the future. Subsequent calls invalidate + * previous returns. */ +const char * +ed25519_fmt(const ed25519_public_key_t *pkey) +{ + static char formatted[ED25519_BASE64_LEN+1]; + if (pkey) { + if (ed25519_public_key_is_zero(pkey)) { + strlcpy(formatted, "<unset>", sizeof(formatted)); + } else { + int r = ed25519_public_to_base64(formatted, pkey); + tor_assert(!r); + } + } else { + strlcpy(formatted, "<null>", sizeof(formatted)); + } + return formatted; +} + +/** Try to decode the string <b>input</b> into an ed25519 public key. On + * success, store the value in <b>pkey</b> and return 0. Otherwise return + * -1. */ +int +ed25519_public_from_base64(ed25519_public_key_t *pkey, + const char *input) +{ + return digest256_from_base64((char*)pkey->pubkey, input); +} + +/** Encode the public key <b>pkey</b> into the buffer at <b>output</b>, + * which must have space for ED25519_BASE64_LEN bytes of encoded key, + * plus one byte for a terminating NUL. Return 0 on success, -1 on failure. + */ +int +ed25519_public_to_base64(char *output, + const ed25519_public_key_t *pkey) +{ + return digest256_to_base64(output, (const char *)pkey->pubkey); +} + +/** Encode the signature <b>sig</b> into the buffer at <b>output</b>, + * which must have space for ED25519_SIG_BASE64_LEN bytes of encoded signature, + * plus one byte for a terminating NUL. Return 0 on success, -1 on failure. + */ +int +ed25519_signature_to_base64(char *output, + const ed25519_signature_t *sig) +{ + char buf[256]; + int n = base64_encode_nopad(buf, sizeof(buf), sig->sig, ED25519_SIG_LEN); + tor_assert(n == ED25519_SIG_BASE64_LEN); + memcpy(output, buf, ED25519_SIG_BASE64_LEN+1); + return 0; +} + +/** Try to decode the string <b>input</b> into an ed25519 signature. On + * success, store the value in <b>sig</b> and return 0. Otherwise return + * -1. */ +int +ed25519_signature_from_base64(ed25519_signature_t *sig, + const char *input) +{ + + if (strlen(input) != ED25519_SIG_BASE64_LEN) + return -1; + char buf[ED25519_SIG_BASE64_LEN+3]; + memcpy(buf, input, ED25519_SIG_BASE64_LEN); + buf[ED25519_SIG_BASE64_LEN+0] = '='; + buf[ED25519_SIG_BASE64_LEN+1] = '='; + buf[ED25519_SIG_BASE64_LEN+2] = 0; + char decoded[128]; + int n = base64_decode(decoded, sizeof(decoded), buf, strlen(buf)); + if (n < 0 || n != ED25519_SIG_LEN) + return -1; + memcpy(sig->sig, decoded, ED25519_SIG_LEN); + + return 0; +} + +/** Base64 encode DIGEST_LINE bytes from <b>digest</b>, remove the trailing = + * characters, and store the nul-terminated result in the first + * BASE64_DIGEST_LEN+1 bytes of <b>d64</b>. */ +/* XXXX unify with crypto_format.c code */ +int +digest_to_base64(char *d64, const char *digest) +{ + char buf[256]; + base64_encode(buf, sizeof(buf), digest, DIGEST_LEN, 0); + buf[BASE64_DIGEST_LEN] = '\0'; + memcpy(d64, buf, BASE64_DIGEST_LEN+1); + return 0; +} + +/** Given a base64 encoded, nul-terminated digest in <b>d64</b> (without + * trailing newline or = characters), decode it and store the result in the + * first DIGEST_LEN bytes at <b>digest</b>. */ +/* XXXX unify with crypto_format.c code */ +int +digest_from_base64(char *digest, const char *d64) +{ + if (base64_decode(digest, DIGEST_LEN, d64, strlen(d64)) == DIGEST_LEN) + return 0; + else + return -1; +} + +/** Base64 encode DIGEST256_LINE bytes from <b>digest</b>, remove the + * trailing = characters, and store the nul-terminated result in the first + * BASE64_DIGEST256_LEN+1 bytes of <b>d64</b>. */ + /* XXXX unify with crypto_format.c code */ +int +digest256_to_base64(char *d64, const char *digest) +{ + char buf[256]; + base64_encode(buf, sizeof(buf), digest, DIGEST256_LEN, 0); + buf[BASE64_DIGEST256_LEN] = '\0'; + memcpy(d64, buf, BASE64_DIGEST256_LEN+1); + return 0; +} + +/** Given a base64 encoded, nul-terminated digest in <b>d64</b> (without + * trailing newline or = characters), decode it and store the result in the + * first DIGEST256_LEN bytes at <b>digest</b>. */ +/* XXXX unify with crypto_format.c code */ +int +digest256_from_base64(char *digest, const char *d64) +{ + if (base64_decode(digest, DIGEST256_LEN, d64, strlen(d64)) == DIGEST256_LEN) + return 0; + else + return -1; +} diff --git a/src/lib/crypt_ops/crypto_format.h b/src/lib/crypt_ops/crypto_format.h new file mode 100644 index 0000000000..fe852e6a61 --- /dev/null +++ b/src/lib/crypt_ops/crypto_format.h @@ -0,0 +1,50 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_format.h + * \brief Header for crypto_format.c + **/ + +#ifndef TOR_CRYPTO_FORMAT_H +#define TOR_CRYPTO_FORMAT_H + +#include "lib/testsupport/testsupport.h" +#include "lib/cc/torint.h" +#include "lib/defs/x25519_sizes.h" + +struct ed25519_public_key_t; +struct ed25519_signature_t; + +int crypto_write_tagged_contents_to_file(const char *fname, + const char *typestring, + const char *tag, + const uint8_t *data, + size_t datalen); + +ssize_t crypto_read_tagged_contents_from_file(const char *fname, + const char *typestring, + char **tag_out, + uint8_t *data_out, + ssize_t data_out_len); + +int ed25519_public_from_base64(struct ed25519_public_key_t *pkey, + const char *input); +int ed25519_public_to_base64(char *output, + const struct ed25519_public_key_t *pkey); +const char *ed25519_fmt(const struct ed25519_public_key_t *pkey); + +int ed25519_signature_from_base64(struct ed25519_signature_t *sig, + const char *input); +int ed25519_signature_to_base64(char *output, + const struct ed25519_signature_t *sig); + +int digest_to_base64(char *d64, const char *digest); +int digest_from_base64(char *digest, const char *d64); +int digest256_to_base64(char *d64, const char *digest); +int digest256_from_base64(char *digest, const char *d64); + +#endif /* !defined(TOR_CRYPTO_FORMAT_H) */ diff --git a/src/lib/crypt_ops/crypto_hkdf.c b/src/lib/crypt_ops/crypto_hkdf.c new file mode 100644 index 0000000000..fd2e701651 --- /dev/null +++ b/src/lib/crypt_ops/crypto_hkdf.c @@ -0,0 +1,201 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_hkdf.c + * \brief Block of functions related with HKDF utilities and operations. + **/ + +#include "lib/crypt_ops/crypto_hkdf.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/crypt_ops/crypto_digest.h" + +#include "lib/crypt_ops/crypto_openssl_mgt.h" +#include "lib/intmath/cmp.h" +#include "lib/log/util_bug.h" + +#ifdef ENABLE_OPENSSL +#include <openssl/evp.h> +#include <openssl/opensslv.h> + +#if defined(HAVE_ERR_LOAD_KDF_STRINGS) +#include <openssl/kdf.h> +#define HAVE_OPENSSL_HKDF 1 +#endif +#endif + +#include <string.h> + +/** Given <b>key_in_len</b> bytes of negotiated randomness in <b>key_in</b> + * ("K"), expand it into <b>key_out_len</b> bytes of negotiated key material in + * <b>key_out</b> by taking the first <b>key_out_len</b> bytes of + * H(K | [00]) | H(K | [01]) | .... + * + * This is the key expansion algorithm used in the "TAP" circuit extension + * mechanism; it shouldn't be used for new protocols. + * + * Return 0 on success, -1 on failure. + */ +int +crypto_expand_key_material_TAP(const uint8_t *key_in, size_t key_in_len, + uint8_t *key_out, size_t key_out_len) +{ + int i, r = -1; + uint8_t *cp, *tmp = tor_malloc(key_in_len+1); + uint8_t digest[DIGEST_LEN]; + + /* If we try to get more than this amount of key data, we'll repeat blocks.*/ + tor_assert(key_out_len <= DIGEST_LEN*256); + + memcpy(tmp, key_in, key_in_len); + for (cp = key_out, i=0; cp < key_out+key_out_len; + ++i, cp += DIGEST_LEN) { + tmp[key_in_len] = i; + if (crypto_digest((char*)digest, (const char *)tmp, key_in_len+1) < 0) + goto exit; + memcpy(cp, digest, MIN(DIGEST_LEN, key_out_len-(cp-key_out))); + } + + r = 0; + exit: + memwipe(tmp, 0, key_in_len+1); + tor_free(tmp); + memwipe(digest, 0, sizeof(digest)); + return r; +} + +#ifdef HAVE_OPENSSL_HKDF +/** + * Perform RFC5869 HKDF computation using OpenSSL (only to be called from + * crypto_expand_key_material_rfc5869_sha256_openssl). Note that OpenSSL + * requires input key to be nonempty and salt length to be equal or less + * than 1024. + */ +static int +crypto_expand_key_material_rfc5869_sha256_openssl( + const uint8_t *key_in, size_t key_in_len, + const uint8_t *salt_in, size_t salt_in_len, + const uint8_t *info_in, size_t info_in_len, + uint8_t *key_out, size_t key_out_len) +{ + int r; + EVP_PKEY_CTX *evp_pkey_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + tor_assert(evp_pkey_ctx); + tor_assert(key_in_len != 0); + tor_assert(salt_in_len <= 1024); + + r = EVP_PKEY_derive_init(evp_pkey_ctx); + tor_assert(r == 1); + + r = EVP_PKEY_CTX_set_hkdf_md(evp_pkey_ctx, EVP_sha256()); + tor_assert(r == 1); + + r = EVP_PKEY_CTX_set1_hkdf_salt(evp_pkey_ctx, salt_in, (int)salt_in_len); + tor_assert(r == 1); + + r = EVP_PKEY_CTX_set1_hkdf_key(evp_pkey_ctx, key_in, (int)key_in_len); + tor_assert(r == 1); + + r = EVP_PKEY_CTX_add1_hkdf_info(evp_pkey_ctx, info_in, (int)info_in_len); + tor_assert(r == 1); + + r = EVP_PKEY_derive(evp_pkey_ctx, key_out, &key_out_len); + tor_assert(r == 1); + + EVP_PKEY_CTX_free(evp_pkey_ctx); + return 0; +} + +#else + +/** + * Perform RFC5869 HKDF computation using our own legacy implementation. + * Only to be called from crypto_expand_key_material_rfc5869_sha256_openssl. + */ +static int +crypto_expand_key_material_rfc5869_sha256_legacy( + const uint8_t *key_in, size_t key_in_len, + const uint8_t *salt_in, size_t salt_in_len, + const uint8_t *info_in, size_t info_in_len, + uint8_t *key_out, size_t key_out_len) +{ + uint8_t prk[DIGEST256_LEN]; + uint8_t tmp[DIGEST256_LEN + 128 + 1]; + uint8_t mac[DIGEST256_LEN]; + int i; + uint8_t *outp; + size_t tmp_len; + + crypto_hmac_sha256((char*)prk, + (const char*)salt_in, salt_in_len, + (const char*)key_in, key_in_len); + + /* If we try to get more than this amount of key data, we'll repeat blocks.*/ + tor_assert(key_out_len <= DIGEST256_LEN * 256); + tor_assert(info_in_len <= 128); + memset(tmp, 0, sizeof(tmp)); + outp = key_out; + i = 1; + + while (key_out_len) { + size_t n; + if (i > 1) { + memcpy(tmp, mac, DIGEST256_LEN); + memcpy(tmp+DIGEST256_LEN, info_in, info_in_len); + tmp[DIGEST256_LEN+info_in_len] = i; + tmp_len = DIGEST256_LEN + info_in_len + 1; + } else { + memcpy(tmp, info_in, info_in_len); + tmp[info_in_len] = i; + tmp_len = info_in_len + 1; + } + crypto_hmac_sha256((char*)mac, + (const char*)prk, DIGEST256_LEN, + (const char*)tmp, tmp_len); + n = key_out_len < DIGEST256_LEN ? key_out_len : DIGEST256_LEN; + memcpy(outp, mac, n); + key_out_len -= n; + outp += n; + ++i; + } + + memwipe(tmp, 0, sizeof(tmp)); + memwipe(mac, 0, sizeof(mac)); + return 0; +} +#endif + +/** Expand some secret key material according to RFC5869, using SHA256 as the + * underlying hash. The <b>key_in_len</b> bytes at <b>key_in</b> are the + * secret key material; the <b>salt_in_len</b> bytes at <b>salt_in</b> and the + * <b>info_in_len</b> bytes in <b>info_in_len</b> are the algorithm's "salt" + * and "info" parameters respectively. On success, write <b>key_out_len</b> + * bytes to <b>key_out</b> and return 0. Assert on failure. + */ +int +crypto_expand_key_material_rfc5869_sha256( + const uint8_t *key_in, size_t key_in_len, + const uint8_t *salt_in, size_t salt_in_len, + const uint8_t *info_in, size_t info_in_len, + uint8_t *key_out, size_t key_out_len) +{ + tor_assert(key_in); + tor_assert(key_in_len > 0); + +#ifdef HAVE_OPENSSL_HKDF + return crypto_expand_key_material_rfc5869_sha256_openssl(key_in, + key_in_len, salt_in, + salt_in_len, info_in, + info_in_len, + key_out, key_out_len); +#else + return crypto_expand_key_material_rfc5869_sha256_legacy(key_in, + key_in_len, salt_in, + salt_in_len, info_in, + info_in_len, + key_out, key_out_len); +#endif +} diff --git a/src/lib/crypt_ops/crypto_hkdf.h b/src/lib/crypt_ops/crypto_hkdf.h new file mode 100644 index 0000000000..2994d18e3d --- /dev/null +++ b/src/lib/crypt_ops/crypto_hkdf.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_hkdf.h + * + * \brief Headers for crypto_hkdf.h + **/ + +#ifndef TOR_CRYPTO_HKDF_H +#define TOR_CRYPTO_HKDF_H + +#include "lib/cc/torint.h" + +int crypto_expand_key_material_TAP(const uint8_t *key_in, + size_t key_in_len, + uint8_t *key_out, size_t key_out_len); +int crypto_expand_key_material_rfc5869_sha256( + const uint8_t *key_in, size_t key_in_len, + const uint8_t *salt_in, size_t salt_in_len, + const uint8_t *info_in, size_t info_in_len, + uint8_t *key_out, size_t key_out_len); + +#endif /* !defined(TOR_CRYPTO_HKDF_H) */ diff --git a/src/lib/crypt_ops/crypto_init.c b/src/lib/crypt_ops/crypto_init.c new file mode 100644 index 0000000000..329c264af6 --- /dev/null +++ b/src/lib/crypt_ops/crypto_init.c @@ -0,0 +1,204 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_init.c + * + * \brief Initialize and shut down Tor's crypto library and subsystem. + **/ + +#include "orconfig.h" + +#include "lib/crypt_ops/crypto_init.h" + +#include "lib/crypt_ops/crypto_curve25519.h" +#include "lib/crypt_ops/crypto_dh.h" +#include "lib/crypt_ops/crypto_ed25519.h" +#include "lib/crypt_ops/crypto_openssl_mgt.h" +#include "lib/crypt_ops/crypto_nss_mgt.h" +#include "lib/crypt_ops/crypto_rand.h" + +#include "siphash.h" + +/** Boolean: has our crypto library been initialized? (early phase) */ +static int crypto_early_initialized_ = 0; + +/** Boolean: has our crypto library been initialized? (late phase) */ +static int crypto_global_initialized_ = 0; + +static int have_seeded_siphash = 0; + +/** Set up the siphash key if we haven't already done so. */ +int +crypto_init_siphash_key(void) +{ + struct sipkey key; + if (have_seeded_siphash) + return 0; + + crypto_rand((char*) &key, sizeof(key)); + siphash_set_global_key(&key); + have_seeded_siphash = 1; + return 0; +} + +/** Initialize the crypto library. Return 0 on success, -1 on failure. + */ +int +crypto_early_init(void) +{ + if (!crypto_early_initialized_) { + + crypto_early_initialized_ = 1; + +#ifdef ENABLE_OPENSSL + crypto_openssl_early_init(); +#endif +#ifdef ENABLE_NSS + crypto_nss_early_init(0); +#endif + + if (crypto_seed_rng() < 0) + return -1; + if (crypto_init_siphash_key() < 0) + return -1; + + curve25519_init(); + ed25519_init(); + } + return 0; +} + +/** Initialize the crypto library. Return 0 on success, -1 on failure. + */ +int +crypto_global_init(int useAccel, const char *accelName, const char *accelDir) +{ + if (!crypto_global_initialized_) { + if (crypto_early_init() < 0) + return -1; + + crypto_global_initialized_ = 1; + + crypto_dh_init(); + +#ifdef ENABLE_OPENSSL + if (crypto_openssl_late_init(useAccel, accelName, accelDir) < 0) + return -1; +#else + (void)useAccel; + (void)accelName; + (void)accelDir; +#endif +#ifdef ENABLE_NSS + if (crypto_nss_late_init() < 0) + return -1; +#endif + } + return 0; +} + +/** Free crypto resources held by this thread. */ +void +crypto_thread_cleanup(void) +{ +#ifdef ENABLE_OPENSSL + crypto_openssl_thread_cleanup(); +#endif +} + +/** + * Uninitialize the crypto library. Return 0 on success. Does not detect + * failure. + */ +int +crypto_global_cleanup(void) +{ + crypto_dh_free_all(); + +#ifdef ENABLE_OPENSSL + crypto_openssl_global_cleanup(); +#endif +#ifdef ENABLE_NSS + crypto_nss_global_cleanup(); +#endif + + crypto_early_initialized_ = 0; + crypto_global_initialized_ = 0; + have_seeded_siphash = 0; + siphash_unset_global_key(); + + return 0; +} + +/** Run operations that the crypto library requires to be happy again + * after forking. */ +void +crypto_prefork(void) +{ +#ifdef ENABLE_NSS + crypto_nss_prefork(); +#endif +} + +/** Run operations that the crypto library requires to be happy again + * after forking. */ +void +crypto_postfork(void) +{ +#ifdef ENABLE_NSS + crypto_nss_postfork(); +#endif +} + +/** Return the name of the crypto library we're using. */ +const char * +crypto_get_library_name(void) +{ +#ifdef ENABLE_OPENSSL + return "OpenSSL"; +#endif +#ifdef ENABLE_NSS + return "NSS"; +#endif +} + +/** Return the version of the crypto library we are using, as given in the + * library. */ +const char * +crypto_get_library_version_string(void) +{ +#ifdef ENABLE_OPENSSL + return crypto_openssl_get_version_str(); +#endif +#ifdef ENABLE_NSS + return crypto_nss_get_version_str(); +#endif +} + +/** Return the version of the crypto library we're using, as given in the + * headers. */ +const char * +crypto_get_header_version_string(void) +{ +#ifdef ENABLE_OPENSSL + return crypto_openssl_get_header_version_str(); +#endif +#ifdef ENABLE_NSS + return crypto_nss_get_header_version_str(); +#endif +} + +/** Return true iff Tor is using the NSS library. */ +int +tor_is_using_nss(void) +{ +#ifdef ENABLE_NSS + return 1; +#else + return 0; +#endif +} diff --git a/src/lib/crypt_ops/crypto_init.h b/src/lib/crypt_ops/crypto_init.h new file mode 100644 index 0000000000..540d08eb56 --- /dev/null +++ b/src/lib/crypt_ops/crypto_init.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_init.h + * + * \brief Headers for crypto_init.c + **/ + +#ifndef TOR_CRYPTO_INIT_H +#define TOR_CRYPTO_INIT_H + +#include "orconfig.h" +#include "lib/cc/compat_compiler.h" + +int crypto_init_siphash_key(void); +int crypto_early_init(void) ATTR_WUR; +int crypto_global_init(int hardwareAccel, + const char *accelName, + const char *accelPath) ATTR_WUR; + +void crypto_thread_cleanup(void); +int crypto_global_cleanup(void); +void crypto_prefork(void); +void crypto_postfork(void); + +const char *crypto_get_library_name(void); +const char *crypto_get_library_version_string(void); +const char *crypto_get_header_version_string(void); + +int tor_is_using_nss(void); + +#endif /* !defined(TOR_CRYPTO_H) */ diff --git a/src/lib/crypt_ops/crypto_nss_mgt.c b/src/lib/crypt_ops/crypto_nss_mgt.c new file mode 100644 index 0000000000..0179126e38 --- /dev/null +++ b/src/lib/crypt_ops/crypto_nss_mgt.c @@ -0,0 +1,132 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_nss_mgt.c + * + * \brief Manage the NSS library (if used) + **/ + +#include "lib/crypt_ops/crypto_nss_mgt.h" + +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/string/printf.h" + +DISABLE_GCC_WARNING(strict-prototypes) +#include <nss.h> +#include <pk11func.h> +#include <ssl.h> + +#include <prerror.h> +#include <prtypes.h> +#include <prinit.h> +ENABLE_GCC_WARNING(strict-prototypes) + +const char * +crypto_nss_get_version_str(void) +{ + return NSS_GetVersion(); +} +const char * +crypto_nss_get_header_version_str(void) +{ + return NSS_VERSION; +} + +/** A password function that always returns NULL. */ +static char * +nss_password_func_always_fail(PK11SlotInfo *slot, + PRBool retry, + void *arg) +{ + (void) slot; + (void) retry; + (void) arg; + return NULL; +} + +void +crypto_nss_early_init(int nss_only) +{ + if (! nss_only) { + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); + PK11_SetPasswordFunc(nss_password_func_always_fail); + } + + /* Eventually we should use NSS_Init() instead -- but that wants a + directory. The documentation says that we can't use this if we want + to use OpenSSL. */ + if (NSS_NoDB_Init(NULL) == SECFailure) { + log_err(LD_CRYPTO, "Unable to initialize NSS."); + crypto_nss_log_errors(LOG_ERR, "initializing NSS"); + tor_assert_unreached(); + } + + if (NSS_SetDomesticPolicy() == SECFailure) { + log_err(LD_CRYPTO, "Unable to set NSS cipher policy."); + crypto_nss_log_errors(LOG_ERR, "setting cipher policy"); + tor_assert_unreached(); + } + + /* We need to override the default here, or NSS will reject all the + * legacy Tor certificates. */ + SECStatus rv = NSS_OptionSet(NSS_RSA_MIN_KEY_SIZE, 1024); + if (rv != SECSuccess) { + log_err(LD_CRYPTO, "Unable to set NSS min RSA key size"); + crypto_nss_log_errors(LOG_ERR, "setting cipher option."); + tor_assert_unreached(); + } +} + +void +crypto_nss_log_errors(int severity, const char *doing) +{ + PRErrorCode code = PR_GetError(); + const char *string = PORT_ErrorToString(code); + const char *name = PORT_ErrorToName(code); + char buf[16]; + if (!string) + string = "<unrecognized>"; + if (!name) { + tor_snprintf(buf, sizeof(buf), "%d", code); + name = buf; + } + if (doing) { + tor_log(severity, LD_CRYPTO, "NSS error %s while %s: %s", + name, doing, string); + } else { + tor_log(severity, LD_CRYPTO, "NSS error %s: %s", name, string); + } +} + +int +crypto_nss_late_init(void) +{ + /* Possibly, SSL_OptionSetDefault? */ + + return 0; +} + +void +crypto_nss_global_cleanup(void) +{ + NSS_Shutdown(); + PL_ArenaFinish(); + PR_Cleanup(); +} + +void +crypto_nss_prefork(void) +{ + NSS_Shutdown(); +} + +void +crypto_nss_postfork(void) +{ + crypto_nss_early_init(1); +} diff --git a/src/lib/crypt_ops/crypto_nss_mgt.h b/src/lib/crypt_ops/crypto_nss_mgt.h new file mode 100644 index 0000000000..72fd2a1229 --- /dev/null +++ b/src/lib/crypt_ops/crypto_nss_mgt.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_nss_mgt.h + * + * \brief Headers for crypto_nss_mgt.c + **/ + +#ifndef TOR_CRYPTO_NSS_MGT_H +#define TOR_CRYPTO_NSS_MGT_H + +#include "orconfig.h" + +#ifdef ENABLE_NSS +/* global nss state */ +const char *crypto_nss_get_version_str(void); +const char *crypto_nss_get_header_version_str(void); + +void crypto_nss_log_errors(int severity, const char *doing); + +void crypto_nss_early_init(int nss_only); +int crypto_nss_late_init(void); + +void crypto_nss_global_cleanup(void); + +void crypto_nss_prefork(void); +void crypto_nss_postfork(void); +#endif + +#endif /* !defined(TOR_CRYPTO_NSS_H) */ diff --git a/src/lib/crypt_ops/crypto_ope.c b/src/lib/crypt_ops/crypto_ope.c new file mode 100644 index 0000000000..2186d2a939 --- /dev/null +++ b/src/lib/crypt_ops/crypto_ope.c @@ -0,0 +1,185 @@ +/* Copyright (c) 2018-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * A rudimentary order-preserving encryption scheme. + * + * To compute the encryption of N, this scheme uses an AES-CTR stream to + * generate M-byte values, and adds the first N of them together. (+1 each to + * insure that the ciphertexts are strictly decreasing.) + * + * We use this for generating onion service revision counters based on the + * current time, without leaking the amount of skew in our view of the current + * time. MUCH more analysis would be needed before using it for anything + * else! + */ + +#include "orconfig.h" + +#define CRYPTO_OPE_PRIVATE +#include "lib/crypt_ops/crypto_ope.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/crypt_ops/crypto_cipher.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/arch/bytes.h" + +#include <string.h> + +/** + * How infrequent should the precomputed values be for this encryption? + * The choice of this value creates a space/time tradeoff. + * + * Note that this value must be a multiple of 16; see + * ope_get_cipher() + */ +#define SAMPLE_INTERVAL 1024 +/** Number of precomputed samples to make for each OPE key. */ +#define N_SAMPLES (OPE_INPUT_MAX / SAMPLE_INTERVAL) + +struct crypto_ope_t { + /** An AES key for use with this object. */ + uint8_t key[OPE_KEY_LEN]; + /** Cached intermediate encryption values at SAMPLE_INTERVAL, + * SAMPLE_INTERVAL*2,...SAMPLE_INTERVAL*N_SAMPLES */ + uint64_t samples[N_SAMPLES]; +}; + +/** The type to add up in order to produce our OPE ciphertexts */ +typedef uint16_t ope_val_t; + +#ifdef WORDS_BIGENDIAN +/** Convert an OPE value from little-endian. */ +static inline ope_val_t +ope_val_from_le(ope_val_t x) +{ + return + ((x) >> 8) | + (((x)&0xff) << 8); +} +#else +#define ope_val_from_le(x) (x) +#endif + +/** + * Return a new AES256-CTR stream cipher object for <b>ope</b>, ready to yield + * bytes from the stream at position <b>initial_idx</b>. + * + * Note that because the index is converted directly to an IV, it must be a + * multiple of the AES block size (16). + */ +STATIC crypto_cipher_t * +ope_get_cipher(const crypto_ope_t *ope, uint32_t initial_idx) +{ + uint8_t iv[CIPHER_IV_LEN]; + tor_assert((initial_idx & 0xf) == 0); + uint32_t n = tor_htonl(initial_idx >> 4); + memset(iv, 0, sizeof(iv)); + memcpy(iv + CIPHER_IV_LEN - sizeof(n), &n, sizeof(n)); + + return crypto_cipher_new_with_iv_and_bits(ope->key, + iv, + OPE_KEY_LEN * 8); +} + +/** + * Retrieve and add the next <b>n</b> values from the stream cipher <b>c</b>, + * and return their sum. + * + * Note that values are taken in little-endian order (for performance on + * prevalent hardware), and are mapped from range 0..2^n-1 to range 1..2^n (so + * that each input encrypts to a different output). + * + * NOTE: this function is not constant-time. + */ +STATIC uint64_t +sum_values_from_cipher(crypto_cipher_t *c, size_t n) +{ +#define BUFSZ 256 + ope_val_t buf[BUFSZ]; + uint64_t total = 0; + unsigned i; + while (n >= BUFSZ) { + memset(buf, 0, sizeof(buf)); + crypto_cipher_crypt_inplace(c, (char*)buf, BUFSZ*sizeof(ope_val_t)); + + for (i = 0; i < BUFSZ; ++i) { + total += ope_val_from_le(buf[i]); + total += 1; + } + n -= BUFSZ; + } + + memset(buf, 0, n*sizeof(ope_val_t)); + crypto_cipher_crypt_inplace(c, (char*)buf, n*sizeof(ope_val_t)); + for (i = 0; i < n; ++i) { + total += ope_val_from_le(buf[i]); + total += 1; + } + + memset(buf, 0, sizeof(buf)); + return total; +} + +/** + * Return a new crypto_ope_t object, using the provided 256-bit key. + */ +crypto_ope_t * +crypto_ope_new(const uint8_t *key) +{ + crypto_ope_t *ope = tor_malloc_zero(sizeof(crypto_ope_t)); + memcpy(ope->key, key, OPE_KEY_LEN); + + crypto_cipher_t *cipher = ope_get_cipher(ope, 0); + + uint64_t v = 0; + int i; + for (i = 0; i < N_SAMPLES; ++i) { + v += sum_values_from_cipher(cipher, SAMPLE_INTERVAL); + ope->samples[i] = v; + } + + crypto_cipher_free(cipher); + return ope; +} + +/** Free all storage held in <>ope</b>. */ +void +crypto_ope_free_(crypto_ope_t *ope) +{ + if (!ope) + return; + memwipe(ope, 0, sizeof(*ope)); + tor_free(ope); +} + +/** + * Return the encrypted value corresponding to <b>input</b>. The input value + * must be in range 1..OPE_INPUT_MAX. Returns CRYPTO_OPE_ERROR on an invalid + * input. + * + * NOTE: this function is not constant-time. + */ +uint64_t +crypto_ope_encrypt(const crypto_ope_t *ope, int plaintext) +{ + if (plaintext <= 0 || plaintext > OPE_INPUT_MAX) + return CRYPTO_OPE_ERROR; + + const int sample_idx = (plaintext / SAMPLE_INTERVAL); + const int starting_iv = sample_idx * SAMPLE_INTERVAL; + const int remaining_values = plaintext - starting_iv; + uint64_t v; + if (sample_idx == 0) { + v = 0; + } else { + v = ope->samples[sample_idx - 1]; + } + crypto_cipher_t *cipher = ope_get_cipher(ope, starting_iv*sizeof(ope_val_t)); + + v += sum_values_from_cipher(cipher, remaining_values); + + crypto_cipher_free(cipher); + + return v; +} diff --git a/src/lib/crypt_ops/crypto_ope.h b/src/lib/crypt_ops/crypto_ope.h new file mode 100644 index 0000000000..610d956335 --- /dev/null +++ b/src/lib/crypt_ops/crypto_ope.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2018-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef CRYPTO_OPE_H +#define CRYPTO_OPE_H + +#include "orconfig.h" +#include "lib/cc/torint.h" +#include "lib/crypt_ops/crypto_ope.h" +#include "lib/testsupport/testsupport.h" + +/** Length of OPE key, in bytes. */ +#define OPE_KEY_LEN 32 + +/** Largest value that can be passed to crypto_ope_encrypt(). + * + * Expressed as 2^18 because the OPE system prefers powers of two. + * + * The current max value stands for about 70 hours. The rationale here is as + * follows: The rev counter is the time of seconds since the start of an SRV + * period. SRVs are useful for about 48 hours (that's how long they stick + * around on the consensus). Let's also add 12 hours of drift for clock skewed + * services that might be using an old consensus and we arrive to 60 + * hours. The max value should be beyond that. + */ +#define OPE_INPUT_MAX (1<<18) + +#define CRYPTO_OPE_ERROR UINT64_MAX + +typedef struct crypto_ope_t crypto_ope_t; + +crypto_ope_t *crypto_ope_new(const uint8_t *key); +void crypto_ope_free_(crypto_ope_t *ope); +#define crypto_ope_free(ope) \ + FREE_AND_NULL(crypto_ope_t, crypto_ope_free_, (ope)) + +uint64_t crypto_ope_encrypt(const crypto_ope_t *ope, int plaintext); + +#ifdef CRYPTO_OPE_PRIVATE +struct aes_cnt_cipher; +STATIC struct aes_cnt_cipher *ope_get_cipher(const crypto_ope_t *ope, + uint32_t initial_idx); +STATIC uint64_t sum_values_from_cipher(struct aes_cnt_cipher *c, size_t n); +#endif + +#endif diff --git a/src/lib/crypt_ops/crypto_openssl_mgt.c b/src/lib/crypt_ops/crypto_openssl_mgt.c new file mode 100644 index 0000000000..60e4ea795e --- /dev/null +++ b/src/lib/crypt_ops/crypto_openssl_mgt.c @@ -0,0 +1,390 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_openssl_mgt.c + * + * \brief Block of functions related to operations from OpenSSL. + **/ + +#include "lib/crypt_ops/compat_openssl.h" +#include "lib/crypt_ops/crypto_openssl_mgt.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/aes.h" +#include "lib/string/util_string.h" +#include "lib/lock/compat_mutex.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/testsupport/testsupport.h" +#include "lib/thread/threads.h" + +DISABLE_GCC_WARNING(redundant-decls) + +#include <openssl/err.h> +#include <openssl/rsa.h> +#include <openssl/pem.h> +#include <openssl/evp.h> +#include <openssl/engine.h> +#include <openssl/rand.h> +#include <openssl/bn.h> +#include <openssl/dh.h> +#include <openssl/conf.h> +#include <openssl/hmac.h> +#include <openssl/crypto.h> +#include <openssl/ssl.h> + +ENABLE_GCC_WARNING(redundant-decls) + +#include <string.h> + +#ifndef NEW_THREAD_API +/** A number of preallocated mutexes for use by OpenSSL. */ +static tor_mutex_t **openssl_mutexes_ = NULL; +/** How many mutexes have we allocated for use by OpenSSL? */ +static int n_openssl_mutexes_ = 0; +#endif /* !defined(NEW_THREAD_API) */ + +/** Declare STATIC functions */ +STATIC char * parse_openssl_version_str(const char *raw_version); +#ifndef NEW_THREAD_API +STATIC void openssl_locking_cb_(int mode, int n, const char *file, int line); +STATIC void tor_set_openssl_thread_id(CRYPTO_THREADID *threadid); +#endif + +/** Log all pending crypto errors at level <b>severity</b>. Use + * <b>doing</b> to describe our current activities. + */ +void +crypto_openssl_log_errors(int severity, const char *doing) +{ + unsigned long err; + const char *msg, *lib, *func; + while ((err = ERR_get_error()) != 0) { + msg = (const char*)ERR_reason_error_string(err); + lib = (const char*)ERR_lib_error_string(err); + func = (const char*)ERR_func_error_string(err); + if (!msg) msg = "(null)"; + if (!lib) lib = "(null)"; + if (!func) func = "(null)"; + if (BUG(!doing)) doing = "(null)"; + tor_log(severity, LD_CRYPTO, "crypto error while %s: %s (in %s:%s)", + doing, msg, lib, func); + } +} + +/* Returns a trimmed and human-readable version of an openssl version string +* <b>raw_version</b>. They are usually in the form of 'OpenSSL 1.0.0b 10 +* May 2012' and this will parse them into a form similar to '1.0.0b' */ +STATIC char * +parse_openssl_version_str(const char *raw_version) +{ + const char *end_of_version = NULL; + /* The output should be something like "OpenSSL 1.0.0b 10 May 2012. Let's + trim that down. */ + if (!strcmpstart(raw_version, "OpenSSL ")) { + raw_version += strlen("OpenSSL "); + end_of_version = strchr(raw_version, ' '); + } + + if (end_of_version) + return tor_strndup(raw_version, + end_of_version-raw_version); + else + return tor_strdup(raw_version); +} + +static char *crypto_openssl_version_str = NULL; +/* Return a human-readable version of the run-time openssl version number. */ +const char * +crypto_openssl_get_version_str(void) +{ + if (crypto_openssl_version_str == NULL) { + const char *raw_version = OpenSSL_version(OPENSSL_VERSION); + crypto_openssl_version_str = parse_openssl_version_str(raw_version); + } + return crypto_openssl_version_str; +} + +static char *crypto_openssl_header_version_str = NULL; +/* Return a human-readable version of the compile-time openssl version +* number. */ +const char * +crypto_openssl_get_header_version_str(void) +{ + if (crypto_openssl_header_version_str == NULL) { + crypto_openssl_header_version_str = + parse_openssl_version_str(OPENSSL_VERSION_TEXT); + } + return crypto_openssl_header_version_str; +} + +#ifndef OPENSSL_THREADS +#error OpenSSL has been built without thread support. Tor requires an \ + OpenSSL library with thread support enabled. +#endif + +#ifndef NEW_THREAD_API +/** Helper: OpenSSL uses this callback to manipulate mutexes. */ +STATIC void +openssl_locking_cb_(int mode, int n, const char *file, int line) +{ + (void)file; + (void)line; + if (!openssl_mutexes_) + /* This is not a really good fix for the + * "release-freed-lock-from-separate-thread-on-shutdown" problem, but + * it can't hurt. */ + return; + if (mode & CRYPTO_LOCK) + tor_mutex_acquire(openssl_mutexes_[n]); + else + tor_mutex_release(openssl_mutexes_[n]); +} + +STATIC void +tor_set_openssl_thread_id(CRYPTO_THREADID *threadid) +{ + CRYPTO_THREADID_set_numeric(threadid, tor_get_thread_id()); +} +#endif /* !defined(NEW_THREAD_API) */ + +/** Helper: Construct mutexes, and set callbacks to help OpenSSL handle being + * multithreaded. Returns 0. */ +static int +setup_openssl_threading(void) +{ +#ifndef NEW_THREAD_API + int i; + int n = CRYPTO_num_locks(); + n_openssl_mutexes_ = n; + openssl_mutexes_ = tor_calloc(n, sizeof(tor_mutex_t *)); + for (i=0; i < n; ++i) + openssl_mutexes_[i] = tor_mutex_new(); + CRYPTO_set_locking_callback(openssl_locking_cb_); + CRYPTO_THREADID_set_callback(tor_set_openssl_thread_id); +#endif /* !defined(NEW_THREAD_API) */ + return 0; +} + +/** free OpenSSL variables */ +static void +crypto_openssl_free_all(void) +{ + tor_free(crypto_openssl_version_str); + tor_free(crypto_openssl_header_version_str); + +#ifndef NEW_THREAD_API + if (n_openssl_mutexes_) { + int n = n_openssl_mutexes_; + tor_mutex_t **ms = openssl_mutexes_; + int i; + openssl_mutexes_ = NULL; + n_openssl_mutexes_ = 0; + for (i=0;i<n;++i) { + tor_mutex_free(ms[i]); + } + tor_free(ms); + } +#endif /* !defined(NEW_THREAD_API) */ +} + +/** Perform early (pre-configuration) initialization tasks for OpenSSL. */ +void +crypto_openssl_early_init(void) +{ +#ifdef OPENSSL_1_1_API + OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | + OPENSSL_INIT_LOAD_CRYPTO_STRINGS | + OPENSSL_INIT_ADD_ALL_CIPHERS | + OPENSSL_INIT_ADD_ALL_DIGESTS, NULL); +#else + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); +#endif + + setup_openssl_threading(); + + unsigned long version_num = OpenSSL_version_num(); + const char *version_str = OpenSSL_version(OPENSSL_VERSION); + if (version_num == OPENSSL_VERSION_NUMBER && + !strcmp(version_str, OPENSSL_VERSION_TEXT)) { + log_info(LD_CRYPTO, "OpenSSL version matches version from headers " + "(%lx: %s).", version_num, version_str); + } else { + log_warn(LD_CRYPTO, "OpenSSL version from headers does not match the " + "version we're running with. If you get weird crashes, that " + "might be why. (Compiled with %lx: %s; running with %lx: %s).", + (unsigned long)OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_TEXT, + version_num, version_str); + } + + crypto_force_rand_ssleay(); +} + +#ifndef DISABLE_ENGINES +/** Try to load an engine in a shared library via fully qualified path. + */ +static ENGINE * +try_load_engine(const char *path, const char *engine) +{ + ENGINE *e = ENGINE_by_id("dynamic"); + if (e) { + if (!ENGINE_ctrl_cmd_string(e, "ID", engine, 0) || + !ENGINE_ctrl_cmd_string(e, "DIR_LOAD", "2", 0) || + !ENGINE_ctrl_cmd_string(e, "DIR_ADD", path, 0) || + !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) { + ENGINE_free(e); + e = NULL; + } + } + return e; +} +#endif /* !defined(DISABLE_ENGINES) */ + +#ifndef DISABLE_ENGINES +/** Log any OpenSSL engines we're using at NOTICE. */ +static void +log_engine(const char *fn, ENGINE *e) +{ + if (e) { + const char *name, *id; + name = ENGINE_get_name(e); + id = ENGINE_get_id(e); + log_notice(LD_CRYPTO, "Default OpenSSL engine for %s is %s [%s]", + fn, name?name:"?", id?id:"?"); + } else { + log_info(LD_CRYPTO, "Using default implementation for %s", fn); + } +} +#endif /* !defined(DISABLE_ENGINES) */ + +/** Initialize engines for openssl (if enabled). */ +static void +crypto_openssl_init_engines(const char *accelName, + const char *accelDir) +{ +#ifdef DISABLE_ENGINES + (void)accelName; + (void)accelDir; + log_warn(LD_CRYPTO, "No OpenSSL hardware acceleration support enabled."); +#else + ENGINE *e = NULL; + + log_info(LD_CRYPTO, "Initializing OpenSSL engine support."); + ENGINE_load_builtin_engines(); + ENGINE_register_all_complete(); + + if (accelName) { + if (accelDir) { + log_info(LD_CRYPTO, "Trying to load dynamic OpenSSL engine \"%s\"" + " via path \"%s\".", accelName, accelDir); + e = try_load_engine(accelName, accelDir); + } else { + log_info(LD_CRYPTO, "Initializing dynamic OpenSSL engine \"%s\"" + " acceleration support.", accelName); + e = ENGINE_by_id(accelName); + } + if (!e) { + log_warn(LD_CRYPTO, "Unable to load dynamic OpenSSL engine \"%s\".", + accelName); + } else { + log_info(LD_CRYPTO, "Loaded dynamic OpenSSL engine \"%s\".", + accelName); + } + } + if (e) { + log_info(LD_CRYPTO, "Loaded OpenSSL hardware acceleration engine," + " setting default ciphers."); + ENGINE_set_default(e, ENGINE_METHOD_ALL); + } + /* Log, if available, the intersection of the set of algorithms + used by Tor and the set of algorithms available in the engine */ + log_engine("RSA", ENGINE_get_default_RSA()); + log_engine("DH", ENGINE_get_default_DH()); +#ifdef OPENSSL_1_1_API + log_engine("EC", ENGINE_get_default_EC()); +#else + log_engine("ECDH", ENGINE_get_default_ECDH()); + log_engine("ECDSA", ENGINE_get_default_ECDSA()); +#endif /* defined(OPENSSL_1_1_API) */ + log_engine("RAND", ENGINE_get_default_RAND()); + log_engine("RAND (which we will not use)", ENGINE_get_default_RAND()); + log_engine("SHA1", ENGINE_get_digest_engine(NID_sha1)); + log_engine("3DES-CBC", ENGINE_get_cipher_engine(NID_des_ede3_cbc)); + log_engine("AES-128-ECB", ENGINE_get_cipher_engine(NID_aes_128_ecb)); + log_engine("AES-128-CBC", ENGINE_get_cipher_engine(NID_aes_128_cbc)); +#ifdef NID_aes_128_ctr + log_engine("AES-128-CTR", ENGINE_get_cipher_engine(NID_aes_128_ctr)); +#endif +#ifdef NID_aes_128_gcm + log_engine("AES-128-GCM", ENGINE_get_cipher_engine(NID_aes_128_gcm)); +#endif + log_engine("AES-256-CBC", ENGINE_get_cipher_engine(NID_aes_256_cbc)); +#ifdef NID_aes_256_gcm + log_engine("AES-256-GCM", ENGINE_get_cipher_engine(NID_aes_256_gcm)); +#endif + +#endif /* defined(DISABLE_ENGINES) */ +} + +/** Perform late (post-init) initialization tasks for OpenSSL */ +int +crypto_openssl_late_init(int useAccel, const char *accelName, + const char *accelDir) +{ + if (useAccel > 0) { + crypto_openssl_init_engines(accelName, accelDir); + } else { + log_info(LD_CRYPTO, "NOT using OpenSSL engine support."); + } + + if (crypto_force_rand_ssleay()) { + if (crypto_seed_rng() < 0) + return -1; + } + + evaluate_evp_for_aes(-1); + evaluate_ctr_for_aes(); + + return 0; +} + +/** Free crypto resources held by this thread. */ +void +crypto_openssl_thread_cleanup(void) +{ +#ifndef NEW_THREAD_API + ERR_remove_thread_state(NULL); +#endif +} + +/** Clean up global resources held by openssl. */ +void +crypto_openssl_global_cleanup(void) +{ + #ifndef OPENSSL_1_1_API + EVP_cleanup(); +#endif +#ifndef NEW_THREAD_API + ERR_remove_thread_state(NULL); +#endif +#ifndef OPENSSL_1_1_API + ERR_free_strings(); +#endif + +#ifndef DISABLE_ENGINES +#ifndef OPENSSL_1_1_API + ENGINE_cleanup(); +#endif +#endif + + CONF_modules_unload(1); +#ifndef OPENSSL_1_1_API + CRYPTO_cleanup_all_ex_data(); +#endif + + crypto_openssl_free_all(); +} diff --git a/src/lib/crypt_ops/crypto_openssl_mgt.h b/src/lib/crypt_ops/crypto_openssl_mgt.h new file mode 100644 index 0000000000..a3dd03aa04 --- /dev/null +++ b/src/lib/crypt_ops/crypto_openssl_mgt.h @@ -0,0 +1,89 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_openssl_mgt.h + * + * \brief Headers for crypto_openssl_mgt.c + **/ + +#ifndef TOR_CRYPTO_OPENSSL_H +#define TOR_CRYPTO_OPENSSL_H + +#include "orconfig.h" + +#ifdef ENABLE_OPENSSL +#include <openssl/engine.h> + +/* + Macro to create an arbitrary OpenSSL version number as used by + OPENSSL_VERSION_NUMBER or SSLeay(), since the actual numbers are a bit hard + to read. + + Don't use this directly, instead use one of the other OPENSSL_V macros + below. + + The format is: 4 bits major, 8 bits minor, 8 bits fix, 8 bits patch, 4 bit + status. + */ +#define OPENSSL_VER(a,b,c,d,e) \ + (((a)<<28) | \ + ((b)<<20) | \ + ((c)<<12) | \ + ((d)<< 4) | \ + (e)) +/** An openssl release number. For example, OPENSSL_V(0,9,8,'j') is the + * version for the released version of 0.9.8j */ +#define OPENSSL_V(a,b,c,d) \ + OPENSSL_VER((a),(b),(c),(d)-'a'+1,0xf) +/** An openssl release number for the first release in the series. For + * example, OPENSSL_V_NOPATCH(1,0,0) is the first released version of OpenSSL + * 1.0.0. */ +#define OPENSSL_V_NOPATCH(a,b,c) \ + OPENSSL_VER((a),(b),(c),0,0xf) +/** The first version that would occur for any alpha or beta in an openssl + * series. For example, OPENSSL_V_SERIES(0,9,8) is greater than any released + * 0.9.7, and less than any released 0.9.8. */ +#define OPENSSL_V_SERIES(a,b,c) \ + OPENSSL_VER((a),(b),(c),0,0) + +#ifdef OPENSSL_NO_ENGINE +/* Android's OpenSSL seems to have removed all of its Engine support. */ +#define DISABLE_ENGINES +#endif + +#if OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5) && \ + !defined(LIBRESSL_VERSION_NUMBER) +/* OpenSSL as of 1.1.0pre4 has an "new" thread API, which doesn't require + * seting up various callbacks. + * + * OpenSSL 1.1.0pre4 has a messed up `ERR_remove_thread_state()` prototype, + * while the previous one was restored in pre5, and the function made a no-op + * (along with a deprecated annotation, which produces a compiler warning). + * + * While it is possible to support all three versions of the thread API, + * a version that existed only for one snapshot pre-release is kind of + * pointless, so let's not. + */ +#define NEW_THREAD_API +#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5) && ... */ + +void crypto_openssl_log_errors(int severity, const char *doing); + +/* global openssl state */ +const char * crypto_openssl_get_version_str(void); +const char * crypto_openssl_get_header_version_str(void); + +void crypto_openssl_early_init(void); +int crypto_openssl_late_init(int useAccel, const char *accelName, + const char *accelDir); + +void crypto_openssl_thread_cleanup(void); +void crypto_openssl_global_cleanup(void); + +#endif /* ENABLE_OPENSSL */ + +#endif /* !defined(TOR_CRYPTO_OPENSSL_H) */ diff --git a/src/lib/crypt_ops/crypto_pwbox.c b/src/lib/crypt_ops/crypto_pwbox.c new file mode 100644 index 0000000000..a8db08f7b7 --- /dev/null +++ b/src/lib/crypt_ops/crypto_pwbox.c @@ -0,0 +1,219 @@ +/* Copyright (c) 2014-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_pwbox.c + * + * \brief Code for encrypting secrets in a password-protected form and saving + * them to disk. + */ + +#include <string.h> + +#include "lib/arch/bytes.h" +#include "lib/crypt_ops/crypto_cipher.h" +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/crypt_ops/crypto_pwbox.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_s2k.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/ctime/di_ops.h" +#include "lib/intmath/muldiv.h" +#include "trunnel/pwbox.h" +#include "lib/log/util_bug.h" + +/* 8 bytes "TORBOX00" + 1 byte: header len (H) + H bytes: header, denoting secret key algorithm. + 16 bytes: IV + Round up to multiple of 128 bytes, then encrypt: + 4 bytes: data len + data + zeros + 32 bytes: HMAC-SHA256 of all previous bytes. +*/ + +#define MAX_OVERHEAD (S2K_MAXLEN + 8 + 1 + 32 + CIPHER_IV_LEN) + +/** + * Make an authenticated passphrase-encrypted blob to encode the + * <b>input_len</b> bytes in <b>input</b> using the passphrase + * <b>secret</b> of <b>secret_len</b> bytes. Allocate a new chunk of memory + * to hold the encrypted data, and store a pointer to that memory in + * *<b>out</b>, and its size in <b>outlen_out</b>. Use <b>s2k_flags</b> as an + * argument to the passphrase-hashing function. + */ +int +crypto_pwbox(uint8_t **out, size_t *outlen_out, + const uint8_t *input, size_t input_len, + const char *secret, size_t secret_len, + unsigned s2k_flags) +{ + uint8_t *result = NULL, *encrypted_portion; + size_t encrypted_len = 128 * CEIL_DIV(input_len+4, 128); + ssize_t result_len; + int spec_len; + uint8_t keys[CIPHER_KEY_LEN + DIGEST256_LEN]; + pwbox_encoded_t *enc = NULL; + ssize_t enc_len; + + crypto_cipher_t *cipher; + int rv; + + enc = pwbox_encoded_new(); + tor_assert(enc); + + pwbox_encoded_setlen_skey_header(enc, S2K_MAXLEN); + + spec_len = secret_to_key_make_specifier( + pwbox_encoded_getarray_skey_header(enc), + S2K_MAXLEN, + s2k_flags); + if (BUG(spec_len < 0 || spec_len > S2K_MAXLEN)) + goto err; + pwbox_encoded_setlen_skey_header(enc, spec_len); + enc->header_len = spec_len; + + crypto_rand((char*)enc->iv, sizeof(enc->iv)); + + pwbox_encoded_setlen_data(enc, encrypted_len); + encrypted_portion = pwbox_encoded_getarray_data(enc); + + set_uint32(encrypted_portion, tor_htonl((uint32_t)input_len)); + memcpy(encrypted_portion+4, input, input_len); + + /* Now that all the data is in position, derive some keys, encrypt, and + * digest */ + const int s2k_rv = secret_to_key_derivekey(keys, sizeof(keys), + pwbox_encoded_getarray_skey_header(enc), + spec_len, + secret, secret_len); + if (BUG(s2k_rv < 0)) + goto err; + + cipher = crypto_cipher_new_with_iv((char*)keys, (char*)enc->iv); + crypto_cipher_crypt_inplace(cipher, (char*)encrypted_portion, encrypted_len); + crypto_cipher_free(cipher); + + result_len = pwbox_encoded_encoded_len(enc); + if (BUG(result_len < 0)) + goto err; + result = tor_malloc(result_len); + enc_len = pwbox_encoded_encode(result, result_len, enc); + if (BUG(enc_len < 0)) + goto err; + tor_assert(enc_len == result_len); + + crypto_hmac_sha256((char*) result + result_len - 32, + (const char*)keys + CIPHER_KEY_LEN, + DIGEST256_LEN, + (const char*)result, + result_len - 32); + + *out = result; + *outlen_out = result_len; + rv = 0; + goto out; + + /* LCOV_EXCL_START + + This error case is often unreachable if we're correctly coded, unless + somebody adds a new error case somewhere, or unless you're building + without scrypto support. + + - make_specifier can't fail, unless S2K_MAX_LEN is too short. + - secret_to_key_derivekey can't really fail unless we're missing + scrypt, or the underlying function fails, or we pass it a bogus + algorithm or parameters. + - pwbox_encoded_encoded_len can't fail unless we're using trunnel + incorrectly. + - pwbox_encoded_encode can't fail unless we're using trunnel wrong, + or it's buggy. + */ + err: + tor_free(result); + rv = -1; + /* LCOV_EXCL_STOP */ + out: + pwbox_encoded_free(enc); + memwipe(keys, 0, sizeof(keys)); + return rv; +} + +/** + * Try to decrypt the passphrase-encrypted blob of <b>input_len</b> bytes in + * <b>input</b> using the passphrase <b>secret</b> of <b>secret_len</b> bytes. + * On success, return 0 and allocate a new chunk of memory to hold the + * decrypted data, and store a pointer to that memory in *<b>out</b>, and its + * size in <b>outlen_out</b>. On failure, return UNPWBOX_BAD_SECRET if + * the passphrase might have been wrong, and UNPWBOX_CORRUPT if the object is + * definitely corrupt. + */ +int +crypto_unpwbox(uint8_t **out, size_t *outlen_out, + const uint8_t *inp, size_t input_len, + const char *secret, size_t secret_len) +{ + uint8_t *result = NULL; + const uint8_t *encrypted; + uint8_t keys[CIPHER_KEY_LEN + DIGEST256_LEN]; + uint8_t hmac[DIGEST256_LEN]; + uint32_t result_len; + size_t encrypted_len; + crypto_cipher_t *cipher = NULL; + int rv = UNPWBOX_CORRUPTED; + ssize_t got_len; + + pwbox_encoded_t *enc = NULL; + + got_len = pwbox_encoded_parse(&enc, inp, input_len); + if (got_len < 0 || (size_t)got_len != input_len) + goto err; + + /* Now derive the keys and check the hmac. */ + if (secret_to_key_derivekey(keys, sizeof(keys), + pwbox_encoded_getarray_skey_header(enc), + pwbox_encoded_getlen_skey_header(enc), + secret, secret_len) < 0) + goto err; + + crypto_hmac_sha256((char *)hmac, + (const char*)keys + CIPHER_KEY_LEN, DIGEST256_LEN, + (const char*)inp, input_len - DIGEST256_LEN); + + if (tor_memneq(hmac, enc->hmac, DIGEST256_LEN)) { + rv = UNPWBOX_BAD_SECRET; + goto err; + } + + /* How long is the plaintext? */ + encrypted = pwbox_encoded_getarray_data(enc); + encrypted_len = pwbox_encoded_getlen_data(enc); + if (encrypted_len < 4) + goto err; + + cipher = crypto_cipher_new_with_iv((char*)keys, (char*)enc->iv); + crypto_cipher_decrypt(cipher, (char*)&result_len, (char*)encrypted, 4); + result_len = tor_ntohl(result_len); + if (encrypted_len < result_len + 4) + goto err; + + /* Allocate a buffer and decrypt */ + result = tor_malloc_zero(result_len); + crypto_cipher_decrypt(cipher, (char*)result, (char*)encrypted+4, result_len); + + *out = result; + *outlen_out = result_len; + + rv = UNPWBOX_OKAY; + goto out; + + err: + tor_free(result); + + out: + crypto_cipher_free(cipher); + pwbox_encoded_free(enc); + memwipe(keys, 0, sizeof(keys)); + return rv; +} diff --git a/src/lib/crypt_ops/crypto_pwbox.h b/src/lib/crypt_ops/crypto_pwbox.h new file mode 100644 index 0000000000..5a26889fb2 --- /dev/null +++ b/src/lib/crypt_ops/crypto_pwbox.h @@ -0,0 +1,28 @@ +/* Copyright (c) 2014-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_pwbox.h + * + * \brief Header for crypto_pwbox.c + **/ + +#ifndef CRYPTO_PWBOX_H_INCLUDED_ +#define CRYPTO_PWBOX_H_INCLUDED_ + +#include "lib/cc/torint.h" + +#define UNPWBOX_OKAY 0 +#define UNPWBOX_BAD_SECRET -1 +#define UNPWBOX_CORRUPTED -2 + +int crypto_pwbox(uint8_t **out, size_t *outlen_out, + const uint8_t *inp, size_t input_len, + const char *secret, size_t secret_len, + unsigned s2k_flags); + +int crypto_unpwbox(uint8_t **out, size_t *outlen_out, + const uint8_t *inp, size_t input_len, + const char *secret, size_t secret_len); + +#endif /* !defined(CRYPTO_PWBOX_H_INCLUDED_) */ diff --git a/src/lib/crypt_ops/crypto_rand.c b/src/lib/crypt_ops/crypto_rand.c new file mode 100644 index 0000000000..915fe0870d --- /dev/null +++ b/src/lib/crypt_ops/crypto_rand.c @@ -0,0 +1,731 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_rand.c + * + * \brief Functions for initialising and seeding (pseudo-)random + * number generators, and working with randomness. + **/ + +#ifndef CRYPTO_RAND_PRIVATE +#define CRYPTO_RAND_PRIVATE + +#include "lib/crypt_ops/crypto_rand.h" + +#ifdef _WIN32 +#include <windows.h> +#include <wincrypt.h> +#endif /* defined(_WIN32) */ + +#include "lib/container/smartlist.h" +#include "lib/crypt_ops/compat_openssl.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/encoding/binascii.h" +#include "lib/intmath/weakrng.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/sandbox/sandbox.h" +#include "lib/string/compat_string.h" +#include "lib/string/util_string.h" +#include "lib/testsupport/testsupport.h" +#include "lib/fs/files.h" + +#include "lib/defs/digest_sizes.h" +#include "lib/crypt_ops/crypto_digest.h" + +#ifdef ENABLE_NSS +#include "lib/crypt_ops/crypto_nss_mgt.h" +#endif + +#ifdef ENABLE_OPENSSL +DISABLE_GCC_WARNING(redundant-decls) +#include <openssl/rand.h> +#include <openssl/sha.h> +ENABLE_GCC_WARNING(redundant-decls) +#endif + +#ifdef ENABLE_NSS +#include <pk11pub.h> +#include <secerr.h> +#include <prerror.h> +#endif + +#if __GNUC__ && GCC_VERSION >= 402 +#if GCC_VERSION >= 406 +#pragma GCC diagnostic pop +#else +#pragma GCC diagnostic warning "-Wredundant-decls" +#endif +#endif /* __GNUC__ && GCC_VERSION >= 402 */ + +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_SYS_FCNTL_H +#include <sys/fcntl.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_SYSCALL_H +#include <sys/syscall.h> +#endif +#ifdef HAVE_SYS_RANDOM_H +#include <sys/random.h> +#endif + +#include <string.h> +#include <errno.h> + +/** + * How many bytes of entropy we add at once. + * + * This is how much entropy OpenSSL likes to add right now, so maybe it will + * work for us too. + **/ +#define ADD_ENTROPY 32 + +/** + * Longest recognized DNS query. + **/ +#define MAX_DNS_LABEL_SIZE 63 + +/** + * Largest strong entropy request permitted. + **/ +#define MAX_STRONGEST_RAND_SIZE 256 + +/** + * Set the seed of the weak RNG to a random value. + **/ +void +crypto_seed_weak_rng(tor_weak_rng_t *rng) +{ + unsigned seed; + crypto_rand((void*)&seed, sizeof(seed)); + tor_init_weak_random(rng, seed); +} + +#ifdef TOR_UNIT_TESTS +int break_strongest_rng_syscall = 0; +int break_strongest_rng_fallback = 0; +#endif + +/** + * Try to get <b>out_len</b> bytes of the strongest entropy we can generate, + * via system calls, storing it into <b>out</b>. Return 0 on success, -1 on + * failure. A maximum request size of 256 bytes is imposed. + **/ +static int +crypto_strongest_rand_syscall(uint8_t *out, size_t out_len) +{ + tor_assert(out_len <= MAX_STRONGEST_RAND_SIZE); + + /* We only log at notice-level here because in the case that this function + * fails the crypto_strongest_rand_raw() caller will log with a warning-level + * message and let crypto_strongest_rand() error out and finally terminating + * Tor with an assertion error. + */ + +#ifdef TOR_UNIT_TESTS + if (break_strongest_rng_syscall) + return -1; +#endif + +#if defined(_WIN32) + static int provider_set = 0; + static HCRYPTPROV provider; + + if (!provider_set) { + if (!CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + log_notice(LD_CRYPTO, "Unable to set Windows CryptoAPI provider [1]."); + return -1; + } + provider_set = 1; + } + if (!CryptGenRandom(provider, out_len, out)) { + log_notice(LD_CRYPTO, "Unable get entropy from the Windows CryptoAPI."); + return -1; + } + + return 0; +#elif defined(__linux__) && defined(SYS_getrandom) + static int getrandom_works = 1; /* Be optimistic about our chances... */ + + /* getrandom() isn't as straightforward as getentropy(), and has + * no glibc wrapper. + * + * As far as I can tell from getrandom(2) and the source code, the + * requests we issue will always succeed (though it will block on the + * call if /dev/urandom isn't seeded yet), since we are NOT specifying + * GRND_NONBLOCK and the request is <= 256 bytes. + * + * The manpage is unclear on what happens if a signal interrupts the call + * while the request is blocked due to lack of entropy.... + * + * We optimistically assume that getrandom() is available and functional + * because it is the way of the future, and 2 branch mispredicts pale in + * comparison to the overheads involved with failing to open + * /dev/srandom followed by opening and reading from /dev/urandom. + */ + if (PREDICT_LIKELY(getrandom_works)) { + long ret; + /* A flag of '0' here means to read from '/dev/urandom', and to + * block if insufficient entropy is available to service the + * request. + */ + const unsigned int flags = 0; + do { + ret = syscall(SYS_getrandom, out, out_len, flags); + } while (ret == -1 && ((errno == EINTR) ||(errno == EAGAIN))); + + if (PREDICT_UNLIKELY(ret == -1)) { + /* LCOV_EXCL_START we can't actually make the syscall fail in testing. */ + tor_assert(errno != EAGAIN); + tor_assert(errno != EINTR); + + /* Useful log message for errno. */ + if (errno == ENOSYS) { + log_notice(LD_CRYPTO, "Can't get entropy from getrandom()." + " You are running a version of Tor built to support" + " getrandom(), but the kernel doesn't implement this" + " function--probably because it is too old?" + " Trying fallback method instead."); + } else { + log_notice(LD_CRYPTO, "Can't get entropy from getrandom(): %s." + " Trying fallback method instead.", + strerror(errno)); + } + + getrandom_works = 0; /* Don't bother trying again. */ + return -1; + /* LCOV_EXCL_STOP */ + } + + tor_assert(ret == (long)out_len); + return 0; + } + + return -1; /* getrandom() previously failed unexpectedly. */ +#elif defined(HAVE_GETENTROPY) + /* getentropy() is what Linux's getrandom() wants to be when it grows up. + * the only gotcha is that requests are limited to 256 bytes. + */ + return getentropy(out, out_len); +#else + (void) out; +#endif /* defined(_WIN32) || ... */ + + /* This platform doesn't have a supported syscall based random. */ + return -1; +} + +/** + * Try to get <b>out_len</b> bytes of the strongest entropy we can generate, + * via the per-platform fallback mechanism, storing it into <b>out</b>. + * Return 0 on success, -1 on failure. A maximum request size of 256 bytes + * is imposed. + **/ +static int +crypto_strongest_rand_fallback(uint8_t *out, size_t out_len) +{ +#ifdef TOR_UNIT_TESTS + if (break_strongest_rng_fallback) + return -1; +#endif + +#ifdef _WIN32 + /* Windows exclusively uses crypto_strongest_rand_syscall(). */ + (void)out; + (void)out_len; + return -1; +#else /* !(defined(_WIN32)) */ + static const char *filenames[] = { + "/dev/srandom", "/dev/urandom", "/dev/random", NULL + }; + int fd, i; + size_t n; + + for (i = 0; filenames[i]; ++i) { + log_debug(LD_FS, "Considering %s as entropy source", filenames[i]); + fd = open(sandbox_intern_string(filenames[i]), O_RDONLY, 0); + if (fd<0) continue; + log_info(LD_CRYPTO, "Reading entropy from \"%s\"", filenames[i]); + n = read_all_from_fd(fd, (char*)out, out_len); + close(fd); + if (n != out_len) { + /* LCOV_EXCL_START + * We can't make /dev/foorandom actually fail. */ + log_notice(LD_CRYPTO, + "Error reading from entropy source %s (read only %lu bytes).", + filenames[i], + (unsigned long)n); + return -1; + /* LCOV_EXCL_STOP */ + } + + return 0; + } + + return -1; +#endif /* defined(_WIN32) */ +} + +/** + * Try to get <b>out_len</b> bytes of the strongest entropy we can generate, + * storing it into <b>out</b>. Return 0 on success, -1 on failure. A maximum + * request size of 256 bytes is imposed. + **/ +STATIC int +crypto_strongest_rand_raw(uint8_t *out, size_t out_len) +{ + static const size_t sanity_min_size = 16; + static const int max_attempts = 3; + tor_assert(out_len <= MAX_STRONGEST_RAND_SIZE); + + /* For buffers >= 16 bytes (128 bits), we sanity check the output by + * zero filling the buffer and ensuring that it actually was at least + * partially modified. + * + * Checking that any individual byte is non-zero seems like it would + * fail too often (p = out_len * 1/256) for comfort, but this is an + * "adjust according to taste" sort of check. + */ + memwipe(out, 0, out_len); + for (int i = 0; i < max_attempts; i++) { + /* Try to use the syscall/OS favored mechanism to get strong entropy. */ + if (crypto_strongest_rand_syscall(out, out_len) != 0) { + /* Try to use the less-favored mechanism to get strong entropy. */ + if (crypto_strongest_rand_fallback(out, out_len) != 0) { + /* Welp, we tried. Hopefully the calling code terminates the process + * since we're basically boned without good entropy. + */ + log_warn(LD_CRYPTO, + "Cannot get strong entropy: no entropy source found."); + return -1; + } + } + + if ((out_len < sanity_min_size) || !tor_mem_is_zero((char*)out, out_len)) + return 0; + } + + /* LCOV_EXCL_START + * + * We tried max_attempts times to fill a buffer >= 128 bits long, + * and each time it returned all '0's. Either the system entropy + * source is busted, or the user should go out and buy a ticket to + * every lottery on the planet. + */ + log_warn(LD_CRYPTO, "Strong OS entropy returned all zero buffer."); + + return -1; + /* LCOV_EXCL_STOP */ +} + +/** + * Try to get <b>out_len</b> bytes of the strongest entropy we can generate, + * storing it into <b>out</b>. + **/ +void +crypto_strongest_rand(uint8_t *out, size_t out_len) +{ + crypto_strongest_rand_(out, out_len); +} + +/** + * Try to get <b>out_len</b> bytes of the strongest entropy we can generate, + * storing it into <b>out</b>. (Mockable version.) + **/ +MOCK_IMPL(void, +crypto_strongest_rand_,(uint8_t *out, size_t out_len)) +{ +#define DLEN DIGEST512_LEN + + /* We're going to hash DLEN bytes from the system RNG together with some + * bytes from the PRNGs from our crypto librar(y/ies), in order to yield + * DLEN bytes. + */ + uint8_t inp[DLEN*3]; + uint8_t tmp[DLEN]; + tor_assert(out); + while (out_len) { + memset(inp, 0, sizeof(inp)); +#ifdef ENABLE_OPENSSL + RAND_bytes(inp, DLEN); +#endif +#ifdef ENABLE_NSS + PK11_GenerateRandom(inp+DLEN, DLEN); +#endif + if (crypto_strongest_rand_raw(inp+DLEN*2, DLEN) < 0) { + // LCOV_EXCL_START + log_err(LD_CRYPTO, "Failed to load strong entropy when generating an " + "important key. Exiting."); + /* Die with an assertion so we get a stack trace. */ + tor_assert(0); + // LCOV_EXCL_STOP + } + if (out_len >= DLEN) { + crypto_digest512((char*)out, (char*)inp, sizeof(inp), DIGEST_SHA512); + out += DLEN; + out_len -= DLEN; + } else { + crypto_digest512((char*)tmp, (char*)inp, sizeof(inp), DIGEST_SHA512); + memcpy(out, tmp, out_len); + break; + } + } + memwipe(tmp, 0, sizeof(tmp)); + memwipe(inp, 0, sizeof(inp)); +#undef DLEN +} + +#ifdef ENABLE_OPENSSL +/** + * Seed OpenSSL's random number generator with bytes from the operating + * system. Return 0 on success, -1 on failure. + **/ +static int +crypto_seed_openssl_rng(void) +{ + int rand_poll_ok = 0, load_entropy_ok = 0; + uint8_t buf[ADD_ENTROPY]; + + /* OpenSSL has a RAND_poll function that knows about more kinds of + * entropy than we do. We'll try calling that, *and* calling our own entropy + * functions. If one succeeds, we'll accept the RNG as seeded. */ + rand_poll_ok = RAND_poll(); + if (rand_poll_ok == 0) + log_warn(LD_CRYPTO, "RAND_poll() failed."); // LCOV_EXCL_LINE + + load_entropy_ok = !crypto_strongest_rand_raw(buf, sizeof(buf)); + if (load_entropy_ok) { + RAND_seed(buf, sizeof(buf)); + } + + memwipe(buf, 0, sizeof(buf)); + + if ((rand_poll_ok || load_entropy_ok) && RAND_status() == 1) + return 0; + else + return -1; +} +#endif + +#ifdef ENABLE_NSS +/** + * Seed OpenSSL's random number generator with bytes from the operating + * system. Return 0 on success, -1 on failure. + **/ +static int +crypto_seed_nss_rng(void) +{ + uint8_t buf[ADD_ENTROPY]; + + int load_entropy_ok = !crypto_strongest_rand_raw(buf, sizeof(buf)); + if (load_entropy_ok) { + if (PK11_RandomUpdate(buf, sizeof(buf)) != SECSuccess) { + load_entropy_ok = 0; + } + } + + memwipe(buf, 0, sizeof(buf)); + + return load_entropy_ok ? 0 : -1; +} +#endif + +/** + * Seed the RNG for any and all crypto libraries that we're using with bytes + * from the operating system. Return 0 on success, -1 on failure. + */ +int +crypto_seed_rng(void) +{ + int seeded = 0; +#ifdef ENABLE_NSS + if (crypto_seed_nss_rng() < 0) + return -1; + ++seeded; +#endif +#ifdef ENABLE_OPENSSL + if (crypto_seed_openssl_rng() < 0) + return -1; + ++seeded; +#endif + tor_assert(seeded); + return 0; +} + +/** + * Write <b>n</b> bytes of strong random data to <b>to</b>. Supports mocking + * for unit tests. + * + * This function is not allowed to fail; if it would fail to generate strong + * entropy, it must terminate the process instead. + **/ +MOCK_IMPL(void, +crypto_rand, (char *to, size_t n)) +{ + crypto_rand_unmocked(to, n); +} + +/** + * Write <b>n</b> bytes of strong random data to <b>to</b>. Most callers + * will want crypto_rand instead. + * + * This function is not allowed to fail; if it would fail to generate strong + * entropy, it must terminate the process instead. + **/ +void +crypto_rand_unmocked(char *to, size_t n) +{ + if (n == 0) + return; + + tor_assert(n < INT_MAX); + tor_assert(to); + +#ifdef ENABLE_NSS + SECStatus s = PK11_GenerateRandom((unsigned char*)to, (int)n); + if (s != SECSuccess) { + /* NSS rather sensibly might refuse to generate huge amounts of random + * data at once. Unfortunately, our unit test do this in a couple of + * places. To solve this issue, we use our XOF to stretch a shorter + * output when a longer one is needed. + * + * Yes, this is secure. */ + + /* This is longer than it needs to be; 1600 bits == 200 bytes is the + * state-size of SHA3. */ +#define BUFLEN 512 + tor_assert(PR_GetError() == SEC_ERROR_INVALID_ARGS && n > BUFLEN); + unsigned char buf[BUFLEN]; + s = PK11_GenerateRandom(buf, BUFLEN); + tor_assert(s == SECSuccess); + crypto_xof_t *xof = crypto_xof_new(); + crypto_xof_add_bytes(xof, buf, BUFLEN); + crypto_xof_squeeze_bytes(xof, (unsigned char *)to, n); + crypto_xof_free(xof); + memwipe(buf, 0, BUFLEN); + +#undef BUFLEN + } +#else + int r = RAND_bytes((unsigned char*)to, (int)n); + /* We consider a PRNG failure non-survivable. Let's assert so that we get a + * stack trace about where it happened. + */ + tor_assert(r >= 0); +#endif +} + +/** + * Return a pseudorandom integer, chosen uniformly from the values + * between 0 and <b>max</b>-1 inclusive. <b>max</b> must be between 1 and + * INT_MAX+1, inclusive. + */ +int +crypto_rand_int(unsigned int max) +{ + unsigned int val; + unsigned int cutoff; + tor_assert(max <= ((unsigned int)INT_MAX)+1); + tor_assert(max > 0); /* don't div by 0 */ + + /* We ignore any values that are >= 'cutoff,' to avoid biasing the + * distribution with clipping at the upper end of unsigned int's + * range. + */ + cutoff = UINT_MAX - (UINT_MAX%max); + while (1) { + crypto_rand((char*)&val, sizeof(val)); + if (val < cutoff) + return val % max; + } +} + +/** + * Return a pseudorandom integer, chosen uniformly from the values i such + * that min <= i < max. + * + * <b>min</b> MUST be in range [0, <b>max</b>). + * <b>max</b> MUST be in range (min, INT_MAX]. + **/ +int +crypto_rand_int_range(unsigned int min, unsigned int max) +{ + tor_assert(min < max); + tor_assert(max <= INT_MAX); + + /* The overflow is avoided here because crypto_rand_int() returns a value + * between 0 and (max - min) inclusive. */ + return min + crypto_rand_int(max - min); +} + +/** + * As crypto_rand_int_range, but supports uint64_t. + **/ +uint64_t +crypto_rand_uint64_range(uint64_t min, uint64_t max) +{ + tor_assert(min < max); + return min + crypto_rand_uint64(max - min); +} + +/** + * As crypto_rand_int_range, but supports time_t. + **/ +time_t +crypto_rand_time_range(time_t min, time_t max) +{ + tor_assert(min < max); + return min + (time_t)crypto_rand_uint64(max - min); +} + +/** + * Return a pseudorandom 64-bit integer, chosen uniformly from the values + * between 0 and <b>max</b>-1 inclusive. + **/ +uint64_t +crypto_rand_uint64(uint64_t max) +{ + uint64_t val; + uint64_t cutoff; + tor_assert(max < UINT64_MAX); + tor_assert(max > 0); /* don't div by 0 */ + + /* We ignore any values that are >= 'cutoff,' to avoid biasing the + * distribution with clipping at the upper end of unsigned int's + * range. + */ + cutoff = UINT64_MAX - (UINT64_MAX%max); + while (1) { + crypto_rand((char*)&val, sizeof(val)); + if (val < cutoff) + return val % max; + } +} + +/** + * Return a pseudorandom double d, chosen uniformly from the range + * 0.0 <= d < 1.0. + **/ +double +crypto_rand_double(void) +{ + /* We just use an unsigned int here; we don't really care about getting + * more than 32 bits of resolution */ + unsigned int u; + crypto_rand((char*)&u, sizeof(u)); +#if SIZEOF_INT == 4 +#define UINT_MAX_AS_DOUBLE 4294967296.0 +#elif SIZEOF_INT == 8 +#define UINT_MAX_AS_DOUBLE 1.8446744073709552e+19 +#else +#error SIZEOF_INT is neither 4 nor 8 +#endif /* SIZEOF_INT == 4 || ... */ + return ((double)u) / UINT_MAX_AS_DOUBLE; +} + +/** + * Generate and return a new random hostname starting with <b>prefix</b>, + * ending with <b>suffix</b>, and containing no fewer than + * <b>min_rand_len</b> and no more than <b>max_rand_len</b> random base32 + * characters. Does not check for failure. + * + * Clip <b>max_rand_len</b> to MAX_DNS_LABEL_SIZE. + **/ +char * +crypto_random_hostname(int min_rand_len, int max_rand_len, const char *prefix, + const char *suffix) +{ + char *result, *rand_bytes; + int randlen, rand_bytes_len; + size_t resultlen, prefixlen; + + if (max_rand_len > MAX_DNS_LABEL_SIZE) + max_rand_len = MAX_DNS_LABEL_SIZE; + if (min_rand_len > max_rand_len) + min_rand_len = max_rand_len; + + randlen = crypto_rand_int_range(min_rand_len, max_rand_len+1); + + prefixlen = strlen(prefix); + resultlen = prefixlen + strlen(suffix) + randlen + 16; + + rand_bytes_len = ((randlen*5)+7)/8; + if (rand_bytes_len % 5) + rand_bytes_len += 5 - (rand_bytes_len%5); + rand_bytes = tor_malloc(rand_bytes_len); + crypto_rand(rand_bytes, rand_bytes_len); + + result = tor_malloc(resultlen); + memcpy(result, prefix, prefixlen); + base32_encode(result+prefixlen, resultlen-prefixlen, + rand_bytes, rand_bytes_len); + tor_free(rand_bytes); + strlcpy(result+prefixlen+randlen, suffix, resultlen-(prefixlen+randlen)); + + return result; +} + +/** + * Return a randomly chosen element of <b>sl</b>; or NULL if <b>sl</b> + * is empty. + **/ +void * +smartlist_choose(const smartlist_t *sl) +{ + int len = smartlist_len(sl); + if (len) + return smartlist_get(sl,crypto_rand_int(len)); + return NULL; /* no elements to choose from */ +} + +/** + * Scramble the elements of <b>sl</b> into a random order. + **/ +void +smartlist_shuffle(smartlist_t *sl) +{ + int i; + /* From the end of the list to the front, choose at random from the + positions we haven't looked at yet, and swap that position into the + current position. Remember to give "no swap" the same probability as + any other swap. */ + for (i = smartlist_len(sl)-1; i > 0; --i) { + int j = crypto_rand_int(i+1); + smartlist_swap(sl, i, j); + } +} + +/** Make sure that openssl is using its default PRNG. Return 1 if we had to + * adjust it; 0 otherwise. */ +int +crypto_force_rand_ssleay(void) +{ +#ifdef ENABLE_OPENSSL + RAND_METHOD *default_method; + default_method = RAND_OpenSSL(); + if (RAND_get_rand_method() != default_method) { + log_notice(LD_CRYPTO, "It appears that one of our engines has provided " + "a replacement the OpenSSL RNG. Resetting it to the default " + "implementation."); + RAND_set_rand_method(default_method); + return 1; + } +#endif + return 0; +} + +#endif /* !defined(CRYPTO_RAND_PRIVATE) */ diff --git a/src/lib/crypt_ops/crypto_rand.h b/src/lib/crypt_ops/crypto_rand.h new file mode 100644 index 0000000000..86fa20faa3 --- /dev/null +++ b/src/lib/crypt_ops/crypto_rand.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_rand.h + * + * \brief Common functions for using (pseudo-)random number generators. + **/ + +#ifndef TOR_CRYPTO_RAND_H +#define TOR_CRYPTO_RAND_H + +#include "lib/cc/compat_compiler.h" +#include "lib/cc/torint.h" +#include "lib/testsupport/testsupport.h" + +/* random numbers */ +int crypto_seed_rng(void) ATTR_WUR; +MOCK_DECL(void,crypto_rand,(char *to, size_t n)); +void crypto_rand_unmocked(char *to, size_t n); +void crypto_strongest_rand(uint8_t *out, size_t out_len); +MOCK_DECL(void,crypto_strongest_rand_,(uint8_t *out, size_t out_len)); +int crypto_rand_int(unsigned int max); +int crypto_rand_int_range(unsigned int min, unsigned int max); +uint64_t crypto_rand_uint64_range(uint64_t min, uint64_t max); +time_t crypto_rand_time_range(time_t min, time_t max); +uint64_t crypto_rand_uint64(uint64_t max); +double crypto_rand_double(void); +struct tor_weak_rng_t; +void crypto_seed_weak_rng(struct tor_weak_rng_t *rng); + +char *crypto_random_hostname(int min_rand_len, int max_rand_len, + const char *prefix, const char *suffix); + +struct smartlist_t; +void *smartlist_choose(const struct smartlist_t *sl); +void smartlist_shuffle(struct smartlist_t *sl); +int crypto_force_rand_ssleay(void); + +#ifdef CRYPTO_RAND_PRIVATE + +STATIC int crypto_strongest_rand_raw(uint8_t *out, size_t out_len); + +#ifdef TOR_UNIT_TESTS +extern int break_strongest_rng_syscall; +extern int break_strongest_rng_fallback; +#endif +#endif /* defined(CRYPTO_RAND_PRIVATE) */ + +#endif /* !defined(TOR_CRYPTO_RAND_H) */ diff --git a/src/lib/crypt_ops/crypto_rsa.c b/src/lib/crypt_ops/crypto_rsa.c new file mode 100644 index 0000000000..c9189b0dfc --- /dev/null +++ b/src/lib/crypt_ops/crypto_rsa.c @@ -0,0 +1,672 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_rsa.c + * \brief Block of functions related with RSA utilities and operations. + **/ + +#include "lib/crypt_ops/crypto_cipher.h" +#include "lib/crypt_ops/crypto_curve25519.h" +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/crypt_ops/crypto_format.h" +#include "lib/crypt_ops/compat_openssl.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_rsa.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/ctime/di_ops.h" +#include "lib/log/util_bug.h" +#include "lib/fs/files.h" + +#include "lib/log/escape.h" +#include "lib/log/log.h" +#include "lib/encoding/binascii.h" +#include "lib/encoding/pem.h" + +#include <string.h> +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#ifdef ENABLE_OPENSSL +#include <openssl/rsa.h> +#endif + +/** Return the number of bytes added by padding method <b>padding</b>. + */ +int +crypto_get_rsa_padding_overhead(int padding) +{ + switch (padding) + { + case PK_PKCS1_OAEP_PADDING: return PKCS1_OAEP_PADDING_OVERHEAD; + default: tor_assert(0); return -1; // LCOV_EXCL_LINE + } +} + +#ifdef ENABLE_OPENSSL +/** Given a padding method <b>padding</b>, return the correct OpenSSL constant. + */ +int +crypto_get_rsa_padding(int padding) +{ + switch (padding) + { + case PK_PKCS1_OAEP_PADDING: return RSA_PKCS1_OAEP_PADDING; + default: tor_assert(0); return -1; // LCOV_EXCL_LINE + } +} +#endif + +/** Compare the public-key components of a and b. Return non-zero iff + * a==b. A NULL key is considered to be distinct from all non-NULL + * keys, and equal to itself. + * + * Note that this may leak information about the keys through timing. + */ +int +crypto_pk_eq_keys(const crypto_pk_t *a, const crypto_pk_t *b) +{ + return (crypto_pk_cmp_keys(a, b) == 0); +} + +/** Perform a hybrid (public/secret) encryption on <b>fromlen</b> + * bytes of data from <b>from</b>, with padding type 'padding', + * storing the results on <b>to</b>. + * + * Returns the number of bytes written on success, -1 on failure. + * + * The encrypted data consists of: + * - The source data, padded and encrypted with the public key, if the + * padded source data is no longer than the public key, and <b>force</b> + * is false, OR + * - The beginning of the source data prefixed with a 16-byte symmetric key, + * padded and encrypted with the public key; followed by the rest of + * the source data encrypted in AES-CTR mode with the symmetric key. + * + * NOTE that this format does not authenticate the symmetrically encrypted + * part of the data, and SHOULD NOT BE USED for new protocols. + */ +int +crypto_pk_obsolete_public_hybrid_encrypt(crypto_pk_t *env, + char *to, size_t tolen, + const char *from, + size_t fromlen, + int padding, int force) +{ + int overhead, outlen, r; + size_t pkeylen, symlen; + crypto_cipher_t *cipher = NULL; + char *buf = NULL; + + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(fromlen < SIZE_T_CEILING); + + overhead = crypto_get_rsa_padding_overhead(padding); + pkeylen = crypto_pk_keysize(env); + + if (!force && fromlen+overhead <= pkeylen) { + /* It all fits in a single encrypt. */ + return crypto_pk_public_encrypt(env,to, + tolen, + from,fromlen,padding); + } + tor_assert(tolen >= fromlen + overhead + CIPHER_KEY_LEN); + tor_assert(tolen >= pkeylen); + + char key[CIPHER_KEY_LEN]; + crypto_rand(key, sizeof(key)); /* generate a new key. */ + cipher = crypto_cipher_new(key); + + buf = tor_malloc(pkeylen+1); + memcpy(buf, key, CIPHER_KEY_LEN); + memcpy(buf+CIPHER_KEY_LEN, from, pkeylen-overhead-CIPHER_KEY_LEN); + + /* Length of symmetrically encrypted data. */ + symlen = fromlen-(pkeylen-overhead-CIPHER_KEY_LEN); + + outlen = crypto_pk_public_encrypt(env,to,tolen,buf,pkeylen-overhead,padding); + if (outlen!=(int)pkeylen) { + goto err; + } + r = crypto_cipher_encrypt(cipher, to+outlen, + from+pkeylen-overhead-CIPHER_KEY_LEN, symlen); + + if (r<0) goto err; + memwipe(buf, 0, pkeylen); + memwipe(key, 0, sizeof(key)); + tor_free(buf); + crypto_cipher_free(cipher); + tor_assert(outlen+symlen < INT_MAX); + return (int)(outlen + symlen); + err: + + memwipe(buf, 0, pkeylen); + memwipe(key, 0, sizeof(key)); + tor_free(buf); + crypto_cipher_free(cipher); + return -1; +} + +/** Invert crypto_pk_obsolete_public_hybrid_encrypt. Returns the number of + * bytes written on success, -1 on failure. + * + * NOTE that this format does not authenticate the symmetrically encrypted + * part of the data, and SHOULD NOT BE USED for new protocols. + */ +int +crypto_pk_obsolete_private_hybrid_decrypt(crypto_pk_t *env, + char *to, + size_t tolen, + const char *from, + size_t fromlen, + int padding, int warnOnFailure) +{ + int outlen, r; + size_t pkeylen; + crypto_cipher_t *cipher = NULL; + char *buf = NULL; + + tor_assert(fromlen < SIZE_T_CEILING); + pkeylen = crypto_pk_keysize(env); + + if (fromlen <= pkeylen) { + return crypto_pk_private_decrypt(env,to,tolen,from,fromlen,padding, + warnOnFailure); + } + + buf = tor_malloc(pkeylen); + outlen = crypto_pk_private_decrypt(env,buf,pkeylen,from,pkeylen,padding, + warnOnFailure); + if (outlen<0) { + log_fn(warnOnFailure?LOG_WARN:LOG_DEBUG, LD_CRYPTO, + "Error decrypting public-key data"); + goto err; + } + if (outlen < CIPHER_KEY_LEN) { + log_fn(warnOnFailure?LOG_WARN:LOG_INFO, LD_CRYPTO, + "No room for a symmetric key"); + goto err; + } + cipher = crypto_cipher_new(buf); + if (!cipher) { + goto err; + } + memcpy(to,buf+CIPHER_KEY_LEN,outlen-CIPHER_KEY_LEN); + outlen -= CIPHER_KEY_LEN; + tor_assert(tolen - outlen >= fromlen - pkeylen); + r = crypto_cipher_decrypt(cipher, to+outlen, from+pkeylen, fromlen-pkeylen); + if (r<0) + goto err; + memwipe(buf,0,pkeylen); + tor_free(buf); + crypto_cipher_free(cipher); + tor_assert(outlen + fromlen < INT_MAX); + return (int)(outlen + (fromlen-pkeylen)); + err: + memwipe(buf,0,pkeylen); + tor_free(buf); + crypto_cipher_free(cipher); + return -1; +} + +/** Given a private or public key <b>pk</b>, put a fingerprint of the + * public key into <b>fp_out</b> (must have at least FINGERPRINT_LEN+1 bytes of + * space). Return 0 on success, -1 on failure. + * + * Fingerprints are computed as the SHA1 digest of the ASN.1 encoding + * of the public key, converted to hexadecimal, in upper case, with a + * space after every four digits. + * + * If <b>add_space</b> is false, omit the spaces. + */ +int +crypto_pk_get_fingerprint(crypto_pk_t *pk, char *fp_out, int add_space) +{ + char digest[DIGEST_LEN]; + char hexdigest[HEX_DIGEST_LEN+1]; + if (crypto_pk_get_digest(pk, digest)) { + return -1; + } + base16_encode(hexdigest,sizeof(hexdigest),digest,DIGEST_LEN); + if (add_space) { + crypto_add_spaces_to_fp(fp_out, FINGERPRINT_LEN+1, hexdigest); + } else { + strncpy(fp_out, hexdigest, HEX_DIGEST_LEN+1); + } + return 0; +} + +/** Given a private or public key <b>pk</b>, put a hashed fingerprint of + * the public key into <b>fp_out</b> (must have at least FINGERPRINT_LEN+1 + * bytes of space). Return 0 on success, -1 on failure. + * + * Hashed fingerprints are computed as the SHA1 digest of the SHA1 digest + * of the ASN.1 encoding of the public key, converted to hexadecimal, in + * upper case. + */ +int +crypto_pk_get_hashed_fingerprint(crypto_pk_t *pk, char *fp_out) +{ + char digest[DIGEST_LEN], hashed_digest[DIGEST_LEN]; + if (crypto_pk_get_digest(pk, digest)) { + return -1; + } + if (crypto_digest(hashed_digest, digest, DIGEST_LEN) < 0) { + return -1; + } + base16_encode(fp_out, FINGERPRINT_LEN + 1, hashed_digest, DIGEST_LEN); + return 0; +} + +/** Copy <b>in</b> to the <b>outlen</b>-byte buffer <b>out</b>, adding spaces + * every four characters. */ +void +crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in) +{ + int n = 0; + char *end = out+outlen; + tor_assert(outlen < SIZE_T_CEILING); + + while (*in && out<end) { + *out++ = *in++; + if (++n == 4 && *in && out<end) { + n = 0; + *out++ = ' '; + } + } + tor_assert(out<end); + *out = '\0'; +} + +/** Check a siglen-byte long signature at <b>sig</b> against + * <b>datalen</b> bytes of data at <b>data</b>, using the public key + * in <b>env</b>. Return 0 if <b>sig</b> is a correct signature for + * SHA1(data). Else return -1. + */ +MOCK_IMPL(int, +crypto_pk_public_checksig_digest,(crypto_pk_t *env, const char *data, + size_t datalen, const char *sig, + size_t siglen)) +{ + char digest[DIGEST_LEN]; + char *buf; + size_t buflen; + int r; + + tor_assert(env); + tor_assert(data); + tor_assert(sig); + tor_assert(datalen < SIZE_T_CEILING); + tor_assert(siglen < SIZE_T_CEILING); + + if (crypto_digest(digest,data,datalen)<0) { + log_warn(LD_BUG, "couldn't compute digest"); + return -1; + } + buflen = crypto_pk_keysize(env); + buf = tor_malloc(buflen); + r = crypto_pk_public_checksig(env,buf,buflen,sig,siglen); + if (r != DIGEST_LEN) { + log_warn(LD_CRYPTO, "Invalid signature"); + tor_free(buf); + return -1; + } + if (tor_memneq(buf, digest, DIGEST_LEN)) { + log_warn(LD_CRYPTO, "Signature mismatched with digest."); + tor_free(buf); + return -1; + } + tor_free(buf); + + return 0; +} + +/** Compute a SHA1 digest of <b>fromlen</b> bytes of data stored at + * <b>from</b>; sign the data with the private key in <b>env</b>, and + * store it in <b>to</b>. Return the number of bytes written on + * success, and -1 on failure. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +int +crypto_pk_private_sign_digest(crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen) +{ + int r; + char digest[DIGEST_LEN]; + if (crypto_digest(digest,from,fromlen)<0) + return -1; + r = crypto_pk_private_sign(env,to,tolen,digest,DIGEST_LEN); + memwipe(digest, 0, sizeof(digest)); + return r; +} + +/** Given a private or public key <b>pk</b>, put a SHA1 hash of the + * public key into <b>digest_out</b> (must have DIGEST_LEN bytes of space). + * Return 0 on success, -1 on failure. + */ +int +crypto_pk_get_digest(const crypto_pk_t *pk, char *digest_out) +{ + char *buf; + size_t buflen; + int len; + int rv = -1; + + buflen = crypto_pk_keysize(pk)*2; + buf = tor_malloc(buflen); + len = crypto_pk_asn1_encode(pk, buf, buflen); + if (len < 0) + goto done; + + if (crypto_digest(digest_out, buf, len) < 0) + goto done; + + rv = 0; + done: + tor_free(buf); + return rv; +} + +/** Compute all digests of the DER encoding of <b>pk</b>, and store them + * in <b>digests_out</b>. Return 0 on success, -1 on failure. */ +int +crypto_pk_get_common_digests(crypto_pk_t *pk, common_digests_t *digests_out) +{ + char *buf; + size_t buflen; + int len; + int rv = -1; + + buflen = crypto_pk_keysize(pk)*2; + buf = tor_malloc(buflen); + len = crypto_pk_asn1_encode(pk, buf, buflen); + if (len < 0) + goto done; + + if (crypto_common_digests(digests_out, (char*)buf, len) < 0) + goto done; + + rv = 0; + done: + tor_free(buf); + return rv; +} + +static const char RSA_PUBLIC_TAG[] = "RSA PUBLIC KEY"; +static const char RSA_PRIVATE_TAG[] = "RSA PRIVATE KEY"; + +/* These are overestimates for how many extra bytes we might need to encode + * a key in DER */ +#define PRIVATE_ASN_MAX_OVERHEAD_FACTOR 16 +#define PUBLIC_ASN_MAX_OVERHEAD_FACTOR 3 + +/** Helper: PEM-encode <b>env</b> and write it to a newly allocated string. + * If <b>private_key</b>, write the private part of <b>env</b>; otherwise + * write only the public portion. On success, set *<b>dest</b> to the new + * string, *<b>len</b> to the string's length, and return 0. On failure, + * return -1. + */ +static int +crypto_pk_write_to_string_generic(crypto_pk_t *env, + char **dest, size_t *len, + bool private_key) +{ + const int factor = + private_key ? PRIVATE_ASN_MAX_OVERHEAD_FACTOR + : PUBLIC_ASN_MAX_OVERHEAD_FACTOR; + size_t buflen = crypto_pk_keysize(env) * factor; + const char *tag = + private_key ? RSA_PRIVATE_TAG : RSA_PUBLIC_TAG; + char *buf = tor_malloc(buflen); + char *result = NULL; + size_t resultlen = 0; + int rv = -1; + + int n = private_key + ? crypto_pk_asn1_encode_private(env, buf, buflen) + : crypto_pk_asn1_encode(env, buf, buflen); + if (n < 0) + goto done; + + resultlen = pem_encoded_size(n, tag); + result = tor_malloc(resultlen); + if (pem_encode(result, resultlen, + (const unsigned char *)buf, n, tag) < 0) { + goto done; + } + + *dest = result; + *len = resultlen; + rv = 0; + + done: + if (rv < 0 && result) { + memwipe(result, 0, resultlen); + tor_free(result); + } + memwipe(buf, 0, buflen); + tor_free(buf); + return rv; +} + +/** PEM-encode the public key portion of <b>env</b> and write it to a + * newly allocated string. On success, set *<b>dest</b> to the new + * string, *<b>len</b> to the string's length, and return 0. On + * failure, return -1. + */ +int +crypto_pk_write_public_key_to_string(crypto_pk_t *env, + char **dest, size_t *len) +{ + return crypto_pk_write_to_string_generic(env, dest, len, false); +} + +/** PEM-encode the private key portion of <b>env</b> and write it to a + * newly allocated string. On success, set *<b>dest</b> to the new + * string, *<b>len</b> to the string's length, and return 0. On + * failure, return -1. + */ +int +crypto_pk_write_private_key_to_string(crypto_pk_t *env, + char **dest, size_t *len) +{ + return crypto_pk_write_to_string_generic(env, dest, len, true); +} + +/** + * Helper. Read a PEM-encoded RSA from the first <b>len</b> characters of + * <b>src</b>, and store the result in <b>env</b>. If <b>private_key</b>, + * expect a private key; otherwise expect a public key. Return 0 on success, + * -1 on failure. If len is -1, the string is nul-terminated. + */ +static int +crypto_pk_read_from_string_generic(crypto_pk_t *env, const char *src, + size_t len, int severity, + bool private_key) +{ + if (len == (size_t)-1) // "-1" indicates "use the length of the string." + len = strlen(src); + + const char *ktype = private_key ? "private key" : "public key"; + const char *tag = + private_key ? RSA_PRIVATE_TAG : RSA_PUBLIC_TAG; + size_t buflen = len; + uint8_t *buf = tor_malloc(buflen); + int rv = -1; + + int n = pem_decode(buf, buflen, src, len, tag); + if (n < 0) { + log_fn(severity, LD_CRYPTO, + "Error decoding PEM wrapper while reading %s", ktype); + goto done; + } + + crypto_pk_t *pk = private_key + ? crypto_pk_asn1_decode_private((const char*)buf, n) + : crypto_pk_asn1_decode((const char*)buf, n); + if (! pk) { + log_fn(severity, LD_CRYPTO, + "Error decoding ASN.1 while reading %s", ktype); + goto done; + } + + if (private_key) + crypto_pk_assign_private(env, pk); + else + crypto_pk_assign_public(env, pk); + crypto_pk_free(pk); + rv = 0; + + done: + memwipe(buf, 0, buflen); + tor_free(buf); + return rv; +} + +/** Read a PEM-encoded public key from the first <b>len</b> characters of + * <b>src</b>, and store the result in <b>env</b>. Return 0 on success, -1 on + * failure. If len is -1, the string is nul-terminated. + */ +int +crypto_pk_read_public_key_from_string(crypto_pk_t *env, + const char *src, size_t len) +{ + return crypto_pk_read_from_string_generic(env, src, len, LOG_INFO, false); +} + +/** Read a PEM-encoded private key from the <b>len</b>-byte string <b>src</b> + * into <b>env</b>. Return 0 on success, -1 on failure. If len is -1, + * the string is nul-terminated. + */ +int +crypto_pk_read_private_key_from_string(crypto_pk_t *env, + const char *src, ssize_t len) +{ + return crypto_pk_read_from_string_generic(env, src, len, LOG_INFO, true); +} + +/** If a file is longer than this, we won't try to decode its private key */ +#define MAX_PRIVKEY_FILE_LEN (16*1024*1024) + +/** Read a PEM-encoded private key from the file named by + * <b>keyfile</b> into <b>env</b>. Return 0 on success, -1 on failure. + */ +int +crypto_pk_read_private_key_from_filename(crypto_pk_t *env, + const char *keyfile) +{ + struct stat st; + char *buf = read_file_to_str(keyfile, 0, &st); + if (!buf) { + log_warn(LD_CRYPTO, "Unable to read file for private key in %s", + escaped(keyfile)); + return -1; + } + if (st.st_size > MAX_PRIVKEY_FILE_LEN) { + log_warn(LD_CRYPTO, "Private key file %s was far too large.", + escaped(keyfile)); + tor_free(buf); + return -1; + } + + int rv = crypto_pk_read_from_string_generic(env, buf, (ssize_t)st.st_size, + LOG_WARN, true); + if (rv < 0) { + log_warn(LD_CRYPTO, "Unable to decode private key from file %s", + escaped(keyfile)); + } + memwipe(buf, 0, (size_t)st.st_size); + tor_free(buf); + return rv; +} + +/** Write the private key from <b>env</b> into the file named by <b>fname</b>, + * PEM-encoded. Return 0 on success, -1 on failure. + */ +int +crypto_pk_write_private_key_to_filename(crypto_pk_t *env, + const char *fname) +{ + char *s = NULL; + size_t n = 0; + + if (crypto_pk_write_private_key_to_string(env, &s, &n) < 0) + return -1; + + int rv = write_bytes_to_file(fname, s, n, 0); + memwipe(s, 0, n); + tor_free(s); + return rv; +} + +/** Given a crypto_pk_t <b>pk</b>, allocate a new buffer containing the + * Base64 encoding of the DER representation of the private key as a NUL + * terminated string, and return it via <b>priv_out</b>. Return 0 on + * success, -1 on failure. + * + * It is the caller's responsibility to sanitize and free the resulting buffer. + */ +int +crypto_pk_base64_encode_private(const crypto_pk_t *pk, char **priv_out) +{ + size_t buflen = crypto_pk_keysize(pk)*16; + char *buf = tor_malloc(buflen); + char *result = NULL; + size_t reslen = 0; + bool ok = false; + + int n = crypto_pk_asn1_encode_private(pk, buf, buflen); + + if (n < 0) + goto done; + + reslen = base64_encode_size(n, 0)+1; + result = tor_malloc(reslen); + if (base64_encode(result, reslen, buf, n, 0) < 0) + goto done; + + ok = true; + + done: + memwipe(buf, 0, buflen); + tor_free(buf); + if (result && ! ok) { + memwipe(result, 0, reslen); + tor_free(result); + } + *priv_out = result; + return ok ? 0 : -1; +} + +/** Given a string containing the Base64 encoded DER representation of the + * private key <b>str</b>, decode and return the result on success, or NULL + * on failure. + */ +crypto_pk_t * +crypto_pk_base64_decode_private(const char *str, size_t len) +{ + crypto_pk_t *pk = NULL; + + char *der = tor_malloc_zero(len + 1); + int der_len = base64_decode(der, len, str, len); + if (der_len <= 0) { + log_warn(LD_CRYPTO, "Stored RSA private key seems corrupted (base64)."); + goto out; + } + + pk = crypto_pk_asn1_decode_private(der, der_len); + + out: + memwipe(der, 0, len+1); + tor_free(der); + + return pk; +} diff --git a/src/lib/crypt_ops/crypto_rsa.h b/src/lib/crypt_ops/crypto_rsa.h new file mode 100644 index 0000000000..c1ea767f85 --- /dev/null +++ b/src/lib/crypt_ops/crypto_rsa.h @@ -0,0 +1,145 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_rsa.h + * + * \brief Headers for crypto_rsa.c + **/ + +#ifndef TOR_CRYPTO_RSA_H +#define TOR_CRYPTO_RSA_H + +#include "orconfig.h" + +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/cc/torint.h" +#include "lib/testsupport/testsupport.h" +#include "lib/log/log.h" + +/** Length of our public keys. */ +#define PK_BYTES (1024/8) + +/** Constant used to indicate OAEP padding for public-key encryption */ +#define PK_PKCS1_OAEP_PADDING 60002 + +/** Number of bytes added for PKCS1-OAEP padding. */ +#define PKCS1_OAEP_PADDING_OVERHEAD 42 + +/** Length of encoded public key fingerprints, including space; but not + * including terminating NUL. */ +#define FINGERPRINT_LEN 49 + +/** Value of 'e' to use in our public keys */ +#define TOR_RSA_EXPONENT 65537 + +/** A public key, or a public/private key-pair. */ +typedef struct crypto_pk_t crypto_pk_t; + +/* RSA environment setup */ +MOCK_DECL(crypto_pk_t *,crypto_pk_new,(void)); +void crypto_pk_free_(crypto_pk_t *env); +#define crypto_pk_free(pk) FREE_AND_NULL(crypto_pk_t, crypto_pk_free_, (pk)) +int crypto_get_rsa_padding_overhead(int padding); +int crypto_get_rsa_padding(int padding); + +/* public key crypto */ +MOCK_DECL(int, crypto_pk_generate_key_with_bits,(crypto_pk_t *env, int bits)); +#define crypto_pk_generate_key(env) \ + crypto_pk_generate_key_with_bits((env), (PK_BYTES*8)) + +int crypto_pk_read_private_key_from_filename(crypto_pk_t *env, + const char *keyfile); +int crypto_pk_write_public_key_to_string(crypto_pk_t *env, + char **dest, size_t *len); +int crypto_pk_write_private_key_to_string(crypto_pk_t *env, + char **dest, size_t *len); +int crypto_pk_read_public_key_from_string(crypto_pk_t *env, + const char *src, size_t len); +int crypto_pk_read_private_key_from_string(crypto_pk_t *env, + const char *s, ssize_t len); +int crypto_pk_write_private_key_to_filename(crypto_pk_t *env, + const char *fname); + +int crypto_pk_is_valid_private_key(const crypto_pk_t *env); +int crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b); +int crypto_pk_eq_keys(const crypto_pk_t *a, const crypto_pk_t *b); +size_t crypto_pk_keysize(const crypto_pk_t *env); +int crypto_pk_num_bits(crypto_pk_t *env); +crypto_pk_t *crypto_pk_dup_key(crypto_pk_t *orig); +crypto_pk_t *crypto_pk_copy_full(crypto_pk_t *orig); +int crypto_pk_key_is_private(const crypto_pk_t *key); +int crypto_pk_public_exponent_ok(const crypto_pk_t *env); +int crypto_pk_obsolete_public_hybrid_encrypt(crypto_pk_t *env, char *to, + size_t tolen, + const char *from, size_t fromlen, + int padding, int force); +int crypto_pk_obsolete_private_hybrid_decrypt(crypto_pk_t *env, char *to, + size_t tolen, + const char *from, size_t fromlen, + int padding, int warnOnFailure); +int crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen, int padding); +int crypto_pk_private_decrypt(crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen, + int padding, int warnOnFailure); +MOCK_DECL(int, crypto_pk_public_checksig,(const crypto_pk_t *env, + char *to, size_t tolen, + const char *from, size_t fromlen)); +int crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen); +int crypto_pk_asn1_encode(const crypto_pk_t *pk, char *dest, size_t dest_len); +crypto_pk_t *crypto_pk_asn1_decode(const char *str, size_t len); +int crypto_pk_asn1_encode_private(const crypto_pk_t *pk, + char *dest, size_t dest_len); +crypto_pk_t *crypto_pk_asn1_decode_private(const char *str, size_t len); +int crypto_pk_get_fingerprint(crypto_pk_t *pk, char *fp_out,int add_space); +int crypto_pk_get_hashed_fingerprint(crypto_pk_t *pk, char *fp_out); +void crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in); + +MOCK_DECL(int, crypto_pk_public_checksig_digest,(crypto_pk_t *env, + const char *data, size_t datalen, const char *sig, size_t siglen)); +int crypto_pk_private_sign_digest(crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen); +int crypto_pk_get_digest(const crypto_pk_t *pk, char *digest_out); +int crypto_pk_get_common_digests(crypto_pk_t *pk, + common_digests_t *digests_out); +int crypto_pk_base64_encode_private(const crypto_pk_t *pk, char **priv_out); +crypto_pk_t *crypto_pk_base64_decode_private(const char *str, size_t len); + +#ifdef ENABLE_OPENSSL +/* Prototypes for private functions only used by tortls.c, crypto.c, and the + * unit tests. */ +struct rsa_st; +struct evp_pkey_st; +struct rsa_st *crypto_pk_get_openssl_rsa_(crypto_pk_t *env); +crypto_pk_t *crypto_new_pk_from_openssl_rsa_(struct rsa_st *rsa); +MOCK_DECL(struct evp_pkey_st *, crypto_pk_get_openssl_evp_pkey_,( + crypto_pk_t *env,int private)); +#endif + +#ifdef ENABLE_NSS +struct SECKEYPublicKeyStr; +struct SECKEYPrivateKeyStr; +crypto_pk_t *crypto_pk_new_from_nss_pubkey(struct SECKEYPublicKeyStr *pub); +const struct SECKEYPublicKeyStr *crypto_pk_get_nss_pubkey( + const crypto_pk_t *key); +const struct SECKEYPrivateKeyStr *crypto_pk_get_nss_privkey( + const crypto_pk_t *key); +#endif + +void crypto_pk_assign_public(crypto_pk_t *dest, const crypto_pk_t *src); +void crypto_pk_assign_private(crypto_pk_t *dest, const crypto_pk_t *src); + +#ifdef TOR_UNIT_TESTS +#ifdef ENABLE_NSS +struct SECItemStr; +STATIC int secitem_uint_cmp(const struct SECItemStr *a, + const struct SECItemStr *b); +#endif +#endif + +#endif diff --git a/src/lib/crypt_ops/crypto_rsa_nss.c b/src/lib/crypt_ops/crypto_rsa_nss.c new file mode 100644 index 0000000000..ad2ad38b66 --- /dev/null +++ b/src/lib/crypt_ops/crypto_rsa_nss.c @@ -0,0 +1,738 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_rsa.c + * \brief NSS implementations of our RSA code. + **/ + +#include "lib/crypt_ops/crypto_rsa.h" + +#include "lib/crypt_ops/crypto_nss_mgt.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/ctime/di_ops.h" +#include "lib/encoding/binascii.h" +#include "lib/fs/files.h" +#include "lib/intmath/cmp.h" +#include "lib/intmath/muldiv.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +#include <string.h> + +#include <keyhi.h> +#include <pk11pub.h> +#include <secder.h> + +#ifdef ENABLE_OPENSSL +#include <openssl/rsa.h> +#include <openssl/evp.h> +#endif + +/** Declaration for crypto_pk_t structure. */ +struct crypto_pk_t +{ + SECKEYPrivateKey *seckey; + SECKEYPublicKey *pubkey; +}; + +/** Return true iff <b>key</b> contains the private-key portion of the RSA + * key. */ +int +crypto_pk_key_is_private(const crypto_pk_t *key) +{ + return key && key->seckey; +} + +/** used by tortls.c: wrap a SecKEYPublicKey in a crypto_pk_t. Take ownership + * of the RSA object. */ +crypto_pk_t * +crypto_pk_new_from_nss_pubkey(struct SECKEYPublicKeyStr *pub) +{ + crypto_pk_t *result = tor_malloc_zero(sizeof(crypto_pk_t)); + result->pubkey = pub; + return result; +} + +/** Return the SECKEYPublicKey for the provided crypto_pk_t. */ +const SECKEYPublicKey * +crypto_pk_get_nss_pubkey(const crypto_pk_t *key) +{ + tor_assert(key); + return key->pubkey; +} + +/** Return the SECKEYPrivateKey for the provided crypto_pk_t, or NULL if it + * does not exist. */ +const SECKEYPrivateKey * +crypto_pk_get_nss_privkey(const crypto_pk_t *key) +{ + tor_assert(key); + return key->seckey; +} + +#ifdef ENABLE_OPENSSL +/** used by tortls.c: wrap an RSA* in a crypto_pk_t. Take ownership of the + * RSA object. */ +crypto_pk_t * +crypto_new_pk_from_openssl_rsa_(RSA *rsa) +{ + crypto_pk_t *pk = NULL; + unsigned char *buf = NULL; + int len = i2d_RSAPublicKey(rsa, &buf); + RSA_free(rsa); + + if (len < 0 || buf == NULL) + goto end; + + pk = crypto_pk_asn1_decode((const char *)buf, len); + + end: + if (buf) + OPENSSL_free(buf); + return pk; +} + +/** Helper, used by tor-gencert.c. Return the RSA from a + * crypto_pk_t. */ +struct rsa_st * +crypto_pk_get_openssl_rsa_(crypto_pk_t *pk) +{ + size_t buflen = crypto_pk_keysize(pk)*16; + unsigned char *buf = tor_malloc_zero(buflen); + const unsigned char *cp = buf; + RSA *rsa = NULL; + + int used = crypto_pk_asn1_encode_private(pk, (char*)buf, buflen); + if (used < 0) + goto end; + rsa = d2i_RSAPrivateKey(NULL, &cp, used); + + end: + memwipe(buf, 0, buflen); + tor_free(buf); + return rsa; +} + +/** used by tortls.c: get an equivalent EVP_PKEY* for a crypto_pk_t. Iff + * private is set, include the private-key portion of the key. Return a valid + * pointer on success, and NULL on failure. */ +MOCK_IMPL(struct evp_pkey_st *, +crypto_pk_get_openssl_evp_pkey_,(crypto_pk_t *pk, int private)) +{ + size_t buflen = crypto_pk_keysize(pk)*16; + unsigned char *buf = tor_malloc_zero(buflen); + const unsigned char *cp = buf; + RSA *rsa = NULL; + EVP_PKEY *result = NULL; + + if (private) { + int len = crypto_pk_asn1_encode_private(pk, (char*)buf, buflen); + if (len < 0) + goto end; + rsa = d2i_RSAPrivateKey(NULL, &cp, len); + } else { + int len = crypto_pk_asn1_encode(pk, (char*)buf, buflen); + if (len < 0) + goto end; + rsa = d2i_RSAPublicKey(NULL, &cp, len); + } + if (!rsa) + goto end; + + if (!(result = EVP_PKEY_new())) + goto end; + if (!(EVP_PKEY_assign_RSA(result, rsa))) { + EVP_PKEY_free(result); + RSA_free(rsa); + result = NULL; + } + + end: + memwipe(buf, 0, buflen); + tor_free(buf); + return result; +} +#endif + +/** Allocate and return storage for a public key. The key itself will not yet + * be set. + */ +MOCK_IMPL(crypto_pk_t *, +crypto_pk_new,(void)) +{ + crypto_pk_t *result = tor_malloc_zero(sizeof(crypto_pk_t)); + return result; +} + +/** Release the NSS objects held in <b>key</b> */ +static void +crypto_pk_clear(crypto_pk_t *key) +{ + if (key->pubkey) + SECKEY_DestroyPublicKey(key->pubkey); + if (key->seckey) + SECKEY_DestroyPrivateKey(key->seckey); + memset(key, 0, sizeof(crypto_pk_t)); +} + +/** Release a reference to an asymmetric key; when all the references + * are released, free the key. + */ +void +crypto_pk_free_(crypto_pk_t *key) +{ + if (!key) + return; + + crypto_pk_clear(key); + + tor_free(key); +} + +/** Generate a <b>bits</b>-bit new public/private keypair in <b>env</b>. + * Return 0 on success, -1 on failure. + */ +MOCK_IMPL(int, +crypto_pk_generate_key_with_bits,(crypto_pk_t *key, int bits)) +{ + tor_assert(key); + + PK11RSAGenParams params = { + .keySizeInBits = bits, + .pe = TOR_RSA_EXPONENT + }; + + int result = -1; + PK11SlotInfo *slot = PK11_GetBestSlot(CKM_RSA_PKCS_KEY_PAIR_GEN, NULL); + SECKEYPrivateKey *seckey = NULL; + SECKEYPublicKey *pubkey = NULL; + + if (!slot) { + crypto_nss_log_errors(LOG_WARN, "getting slot for RSA keygen"); + goto done; + } + + seckey = PK11_GenerateKeyPair(slot, CKM_RSA_PKCS_KEY_PAIR_GEN, ¶ms, + &pubkey, + PR_FALSE /*isPerm */, + PR_FALSE /*isSensitive*/, + NULL); + if (seckey == NULL || pubkey == NULL) { + crypto_nss_log_errors(LOG_WARN, "generating an RSA key"); + goto done; + } + + crypto_pk_clear(key); + key->seckey = seckey; + key->pubkey = pubkey; + seckey = NULL; + pubkey = NULL; + + result = 0; + done: + if (slot) + PK11_FreeSlot(slot); + if (pubkey) + SECKEY_DestroyPublicKey(pubkey); + if (seckey) + SECKEY_DestroyPrivateKey(seckey); + + return result; +} + +/** Return true iff <b>env</b> is a valid private key. + */ +int +crypto_pk_is_valid_private_key(const crypto_pk_t *key) +{ + /* We don't need to do validation here, since unlike OpenSSL, NSS won't let + * us load private keys without validating them. */ + return key && key->seckey; +} + +/** Return true iff <b>env</b> contains a public key whose public exponent + * equals 65537. + */ +int +crypto_pk_public_exponent_ok(const crypto_pk_t *key) +{ + return key && + key->pubkey && + key->pubkey->keyType == rsaKey && + DER_GetUInteger(&key->pubkey->u.rsa.publicExponent) == TOR_RSA_EXPONENT; +} + +/** Compare two big-endian integers stored in a and b; return a tristate. + */ +STATIC int +secitem_uint_cmp(const SECItem *a, const SECItem *b) +{ + const unsigned abits = SECKEY_BigIntegerBitLength(a); + const unsigned bbits = SECKEY_BigIntegerBitLength(b); + + if (abits < bbits) + return -1; + else if (abits > bbits) + return 1; + + /* okay, they have the same number of bits set. Get a pair of aligned + * pointers to their bytes that are set... */ + const unsigned nbytes = CEIL_DIV(abits, 8); + tor_assert(nbytes <= a->len); + tor_assert(nbytes <= b->len); + + const unsigned char *aptr = a->data + (a->len - nbytes); + const unsigned char *bptr = b->data + (b->len - nbytes); + + /* And compare them. */ + return fast_memcmp(aptr, bptr, nbytes); +} + +/** Compare the public-key components of a and b. Return less than 0 + * if a\<b, 0 if a==b, and greater than 0 if a\>b. A NULL key is + * considered to be less than all non-NULL keys, and equal to itself. + * + * Note that this may leak information about the keys through timing. + */ +int +crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b) +{ + int result; + char a_is_non_null = (a != NULL) && (a->pubkey != NULL); + char b_is_non_null = (b != NULL) && (b->pubkey != NULL); + char an_argument_is_null = !a_is_non_null | !b_is_non_null; + + result = tor_memcmp(&a_is_non_null, &b_is_non_null, sizeof(a_is_non_null)); + if (an_argument_is_null) + return result; + + // This is all Tor uses with this structure. + tor_assert(a->pubkey->keyType == rsaKey); + tor_assert(b->pubkey->keyType == rsaKey); + + const SECItem *a_n, *a_e, *b_n, *b_e; + a_n = &a->pubkey->u.rsa.modulus; + b_n = &b->pubkey->u.rsa.modulus; + a_e = &a->pubkey->u.rsa.publicExponent; + b_e = &b->pubkey->u.rsa.publicExponent; + + result = secitem_uint_cmp(a_n, b_n); + if (result) + return result; + return secitem_uint_cmp(a_e, b_e); +} + +/** Return the size of the public key modulus in <b>env</b>, in bytes. */ +size_t +crypto_pk_keysize(const crypto_pk_t *key) +{ + tor_assert(key); + tor_assert(key->pubkey); + return SECKEY_PublicKeyStrength(key->pubkey); +} + +/** Return the size of the public key modulus of <b>env</b>, in bits. */ +int +crypto_pk_num_bits(crypto_pk_t *key) +{ + tor_assert(key); + tor_assert(key->pubkey); + return SECKEY_PublicKeyStrengthInBits(key->pubkey); +} + +/** + * Make a copy of <b>key</b> and return it. + */ +crypto_pk_t * +crypto_pk_dup_key(crypto_pk_t *key) +{ + crypto_pk_t *result = crypto_pk_new(); + if (key->pubkey) + result->pubkey = SECKEY_CopyPublicKey(key->pubkey); + if (key->seckey) + result->seckey = SECKEY_CopyPrivateKey(key->seckey); + return result; +} + +/** For testing: replace dest with src. (Dest must have a refcount + * of 1) */ +void +crypto_pk_assign_public(crypto_pk_t *dest, const crypto_pk_t *src) +{ + crypto_pk_clear(dest); + if (src->pubkey) + dest->pubkey = SECKEY_CopyPublicKey(src->pubkey); +} + +/** For testing: replace dest with src. (Dest must have a refcount + * of 1) */ +void +crypto_pk_assign_private(crypto_pk_t *dest, const crypto_pk_t *src) +{ + crypto_pk_clear(dest); + if (src->pubkey) + dest->pubkey = SECKEY_CopyPublicKey(src->pubkey); + if (src->seckey) + dest->seckey = SECKEY_CopyPrivateKey(src->seckey); +} + +/** Make a real honest-to-goodness copy of <b>env</b>, and return it. + * Returns NULL on failure. */ +crypto_pk_t * +crypto_pk_copy_full(crypto_pk_t *key) +{ + // These aren't reference-counted is nss, so it's fine to just + // use the same function. + return crypto_pk_dup_key(key); +} + +static const CK_RSA_PKCS_OAEP_PARAMS oaep_params = { + .hashAlg = CKM_SHA_1, + .mgf = CKG_MGF1_SHA1, + .source = CKZ_DATA_SPECIFIED, + .pSourceData = NULL, + .ulSourceDataLen = 0 +}; +static const SECItem oaep_item = { + .type = siBuffer, + .data = (unsigned char *) &oaep_params, + .len = sizeof(oaep_params) +}; + +/** Return the mechanism code and parameters for a given padding method when + * used with RSA */ +static CK_MECHANISM_TYPE +padding_to_mechanism(int padding, SECItem **item_out) +{ + switch (padding) { + case PK_PKCS1_OAEP_PADDING: + *item_out = (SECItem *)&oaep_item; + return CKM_RSA_PKCS_OAEP; + default: + tor_assert_unreached(); + *item_out = NULL; + return CKM_INVALID_MECHANISM; + } +} + +/** Encrypt <b>fromlen</b> bytes from <b>from</b> with the public key + * in <b>env</b>, using the padding method <b>padding</b>. On success, + * write the result to <b>to</b>, and return the number of bytes + * written. On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +int +crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen, int padding) +{ + tor_assert(env); + tor_assert(to); + tor_assert(from); + tor_assert(tolen < INT_MAX); + tor_assert(fromlen < INT_MAX); + + if (BUG(! env->pubkey)) + return -1; + + unsigned int result_len = 0; + SECItem *item = NULL; + CK_MECHANISM_TYPE m = padding_to_mechanism(padding, &item); + + SECStatus s = PK11_PubEncrypt(env->pubkey, m, item, + (unsigned char *)to, &result_len, + (unsigned int)tolen, + (const unsigned char *)from, + (unsigned int)fromlen, + NULL); + if (s != SECSuccess) { + crypto_nss_log_errors(LOG_WARN, "encrypting to an RSA key"); + return -1; + } + + return (int)result_len; +} + +/** Decrypt <b>fromlen</b> bytes from <b>from</b> with the private key + * in <b>env</b>, using the padding method <b>padding</b>. On success, + * write the result to <b>to</b>, and return the number of bytes + * written. On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>key</b>. + */ +int +crypto_pk_private_decrypt(crypto_pk_t *key, char *to, + size_t tolen, + const char *from, size_t fromlen, + int padding, int warnOnFailure) +{ + tor_assert(key); + tor_assert(to); + tor_assert(from); + tor_assert(tolen < INT_MAX); + tor_assert(fromlen < INT_MAX); + + if (!crypto_pk_key_is_private(key)) + return -1; /* Not a private key. */ + + unsigned int result_len = 0; + SECItem *item = NULL; + CK_MECHANISM_TYPE m = padding_to_mechanism(padding, &item); + SECStatus s = PK11_PrivDecrypt(key->seckey, m, item, + (unsigned char *)to, &result_len, + (unsigned int)tolen, + (const unsigned char *)from, + (unsigned int)fromlen); + + if (s != SECSuccess) { + const int severity = warnOnFailure ? LOG_WARN : LOG_INFO; + crypto_nss_log_errors(severity, "decrypting with an RSA key"); + return -1; + } + + return (int)result_len; +} + +/** Check the signature in <b>from</b> (<b>fromlen</b> bytes long) with the + * public key in <b>key</b>, using PKCS1 padding. On success, write the + * signed data to <b>to</b>, and return the number of bytes written. + * On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>key</b>. + */ +MOCK_IMPL(int, +crypto_pk_public_checksig,(const crypto_pk_t *key, char *to, + size_t tolen, + const char *from, size_t fromlen)) +{ + tor_assert(key); + tor_assert(to); + tor_assert(from); + tor_assert(tolen < INT_MAX); + tor_assert(fromlen < INT_MAX); + tor_assert(key->pubkey); + + SECItem sig = { + .type = siBuffer, + .data = (unsigned char *) from, + .len = (unsigned int) fromlen, + }; + SECItem dsig = { + .type = siBuffer, + .data = (unsigned char *) to, + .len = (unsigned int) tolen + }; + SECStatus s; + s = PK11_VerifyRecover(key->pubkey, &sig, &dsig, NULL); + if (s != SECSuccess) + return -1; + + return (int)dsig.len; +} + +/** Sign <b>fromlen</b> bytes of data from <b>from</b> with the private key in + * <b>env</b>, using PKCS1 padding. On success, write the signature to + * <b>to</b>, and return the number of bytes written. On failure, return + * -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +int +crypto_pk_private_sign(const crypto_pk_t *key, char *to, size_t tolen, + const char *from, size_t fromlen) +{ + tor_assert(key); + tor_assert(to); + tor_assert(from); + tor_assert(tolen < INT_MAX); + tor_assert(fromlen < INT_MAX); + + if (BUG(!crypto_pk_key_is_private(key))) + return -1; + + SECItem sig = { + .type = siBuffer, + .data = (unsigned char *)to, + .len = (unsigned int) tolen + }; + SECItem hash = { + .type = siBuffer, + .data = (unsigned char *)from, + .len = (unsigned int) fromlen + }; + CK_MECHANISM_TYPE m = CKM_RSA_PKCS; + SECStatus s = PK11_SignWithMechanism(key->seckey, m, NULL, + &sig, &hash); + + if (s != SECSuccess) { + crypto_nss_log_errors(LOG_WARN, "signing with an RSA key"); + return -1; + } + + return (int)sig.len; +} + +/* "This has lead to people trading hard-to-find object identifiers and ASN.1 + * definitions like baseball cards" - Peter Gutmann, "X.509 Style Guide". */ +static const unsigned char RSA_OID[] = { + /* RSADSI */ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + /* PKCS1 */ 0x01, 0x01, + /* RSA */ 0x01 +}; + +/** ASN.1-encode the public portion of <b>pk</b> into <b>dest</b>. + * Return -1 on error, or the number of characters used on success. + */ +int +crypto_pk_asn1_encode(const crypto_pk_t *pk, char *dest, size_t dest_len) +{ + tor_assert(pk); + if (pk->pubkey == NULL) + return -1; + + CERTSubjectPublicKeyInfo *info; + info = SECKEY_CreateSubjectPublicKeyInfo(pk->pubkey); + if (! info) + return -1; + + const SECItem *item = &info->subjectPublicKey; + size_t actual_len = (item->len) >> 3; /* bits to bytes */ + size_t n_used = MIN(actual_len, dest_len); + memcpy(dest, item->data, n_used); + + SECKEY_DestroySubjectPublicKeyInfo(info); + return (int) n_used; +} + +/** Decode an ASN.1-encoded public key from <b>str</b>; return the result on + * success and NULL on failure. + */ +crypto_pk_t * +crypto_pk_asn1_decode(const char *str, size_t len) +{ + tor_assert(str); + if (len >= INT_MAX) + return NULL; + CERTSubjectPublicKeyInfo info = { + .algorithm = { + .algorithm = { + .type = siDEROID, + .data = (unsigned char *)RSA_OID, + .len = sizeof(RSA_OID) + } + }, + .subjectPublicKey = { + .type = siBuffer, + .data = (unsigned char *)str, + .len = (unsigned int)(len << 3) /* bytes to bits */ + } + }; + + SECKEYPublicKey *pub = SECKEY_ExtractPublicKey(&info); + if (pub == NULL) + return NULL; + + crypto_pk_t *result = crypto_pk_new(); + result->pubkey = pub; + return result; +} + +DISABLE_GCC_WARNING(unused-parameter) + +/** Given a crypto_pk_t <b>pk</b>, allocate a new buffer containing the Base64 + * encoding of the DER representation of the private key into the + * <b>dest_len</b>-byte buffer in <b>dest</b>. + * Return the number of bytes written on success, -1 on failure. + */ +int +crypto_pk_asn1_encode_private(const crypto_pk_t *pk, + char *dest, size_t destlen) +{ + tor_assert(destlen <= INT_MAX); + if (!crypto_pk_key_is_private(pk)) + return -1; + + SECKEYPrivateKeyInfo *info = PK11_ExportPrivKeyInfo(pk->seckey, NULL); + if (!info) + return -1; + SECItem *item = &info->privateKey; + + if (destlen < item->len) { + SECKEY_DestroyPrivateKeyInfo(info, PR_TRUE); + return -1; + } + int result = (int)item->len; + memcpy(dest, item->data, item->len); + SECKEY_DestroyPrivateKeyInfo(info, PR_TRUE); + + return result; +} + +/** Given a buffer containing the DER representation of the + * private key <b>str</b>, decode and return the result on success, or NULL + * on failure. + */ +crypto_pk_t * +crypto_pk_asn1_decode_private(const char *str, size_t len) +{ + tor_assert(str); + tor_assert(len < INT_MAX); + PK11SlotInfo *slot = PK11_GetBestSlot(CKM_RSA_PKCS, NULL); + if (!slot) + return NULL; + + SECKEYPrivateKeyInfo info = { + .algorithm = { + .algorithm = { + .type = siBuffer, + .data = (unsigned char *)RSA_OID, + .len = sizeof(RSA_OID) + } + }, + .privateKey = { + .type = siBuffer, + .data = (unsigned char *)str, + .len = (int)len, + } + }; + + SECStatus s; + SECKEYPrivateKey *seckey = NULL; + + s = PK11_ImportPrivateKeyInfoAndReturnKey(slot, &info, + NULL /* nickname */, + NULL /* publicValue */, + PR_FALSE /* isPerm */, + PR_FALSE /* isPrivate */, + KU_ALL /* keyUsage */, + &seckey, NULL); + + crypto_pk_t *output = NULL; + + if (s == SECSuccess && seckey) { + output = crypto_pk_new(); + output->seckey = seckey; + output->pubkey = SECKEY_ConvertToPublicKey(seckey); + tor_assert(output->pubkey); + } else { + crypto_nss_log_errors(LOG_WARN, "decoding an RSA private key"); + } + + if (! crypto_pk_is_valid_private_key(output)) { + crypto_pk_free(output); + output = NULL; + } + + if (slot) + PK11_FreeSlot(slot); + + return output; +} diff --git a/src/lib/crypt_ops/crypto_rsa_openssl.c b/src/lib/crypt_ops/crypto_rsa_openssl.c new file mode 100644 index 0000000000..fbdc76ccd6 --- /dev/null +++ b/src/lib/crypt_ops/crypto_rsa_openssl.c @@ -0,0 +1,590 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_rsa.c + * \brief OpenSSL implementations of our RSA code. + **/ + +#include "lib/crypt_ops/compat_openssl.h" +#include "lib/crypt_ops/crypto_rsa.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/ctime/di_ops.h" +#include "lib/log/util_bug.h" +#include "lib/fs/files.h" + +DISABLE_GCC_WARNING(redundant-decls) + +#include <openssl/err.h> +#include <openssl/rsa.h> +#include <openssl/pem.h> +#include <openssl/evp.h> +#include <openssl/engine.h> +#include <openssl/rand.h> +#include <openssl/bn.h> +#include <openssl/conf.h> + +ENABLE_GCC_WARNING(redundant-decls) + +#include "lib/log/log.h" +#include "lib/encoding/binascii.h" + +#include <string.h> + +/** Declaration for crypto_pk_t structure. */ +struct crypto_pk_t +{ + int refs; /**< reference count, so we don't have to copy keys */ + RSA *key; /**< The key itself */ +}; + +/** Return true iff <b>key</b> contains the private-key portion of the RSA + * key. */ +int +crypto_pk_key_is_private(const crypto_pk_t *k) +{ +#ifdef OPENSSL_1_1_API + if (!k || !k->key) + return 0; + + const BIGNUM *p, *q; + RSA_get0_factors(k->key, &p, &q); + return p != NULL; /* XXX/yawning: Should we check q? */ +#else /* !(defined(OPENSSL_1_1_API)) */ + return k && k->key && k->key->p; +#endif /* defined(OPENSSL_1_1_API) */ +} + +/** used by tortls.c: wrap an RSA* in a crypto_pk_t. Takes ownership of + * its argument. */ +crypto_pk_t * +crypto_new_pk_from_openssl_rsa_(RSA *rsa) +{ + crypto_pk_t *env; + tor_assert(rsa); + env = tor_malloc(sizeof(crypto_pk_t)); + env->refs = 1; + env->key = rsa; + return env; +} + +/** Helper, used by tor-gencert.c. Return a copy of the private RSA from a + * crypto_pk_t. */ +RSA * +crypto_pk_get_openssl_rsa_(crypto_pk_t *env) +{ + return RSAPrivateKey_dup(env->key); +} + +/** used by tortls.c: get an equivalent EVP_PKEY* for a crypto_pk_t. Iff + * private is set, include the private-key portion of the key. Return a valid + * pointer on success, and NULL on failure. */ +MOCK_IMPL(EVP_PKEY *, +crypto_pk_get_openssl_evp_pkey_,(crypto_pk_t *env, int private)) +{ + RSA *key = NULL; + EVP_PKEY *pkey = NULL; + tor_assert(env->key); + if (private) { + if (!(key = RSAPrivateKey_dup(env->key))) + goto error; + } else { + if (!(key = RSAPublicKey_dup(env->key))) + goto error; + } + if (!(pkey = EVP_PKEY_new())) + goto error; + if (!(EVP_PKEY_assign_RSA(pkey, key))) + goto error; + return pkey; + error: + if (pkey) + EVP_PKEY_free(pkey); + if (key) + RSA_free(key); + return NULL; +} + +/** Allocate and return storage for a public key. The key itself will not yet + * be set. + */ +MOCK_IMPL(crypto_pk_t *, +crypto_pk_new,(void)) +{ + RSA *rsa; + + rsa = RSA_new(); + tor_assert(rsa); + return crypto_new_pk_from_openssl_rsa_(rsa); +} + +/** Release a reference to an asymmetric key; when all the references + * are released, free the key. + */ +void +crypto_pk_free_(crypto_pk_t *env) +{ + if (!env) + return; + + if (--env->refs > 0) + return; + tor_assert(env->refs == 0); + + if (env->key) + RSA_free(env->key); + + tor_free(env); +} + +/** Generate a <b>bits</b>-bit new public/private keypair in <b>env</b>. + * Return 0 on success, -1 on failure. + */ +MOCK_IMPL(int, +crypto_pk_generate_key_with_bits,(crypto_pk_t *env, int bits)) +{ + tor_assert(env); + + if (env->key) { + RSA_free(env->key); + env->key = NULL; + } + + { + BIGNUM *e = BN_new(); + RSA *r = NULL; + if (!e) + goto done; + if (! BN_set_word(e, TOR_RSA_EXPONENT)) + goto done; + r = RSA_new(); + if (!r) + goto done; + if (RSA_generate_key_ex(r, bits, e, NULL) == -1) + goto done; + + env->key = r; + r = NULL; + done: + if (e) + BN_clear_free(e); + if (r) + RSA_free(r); + } + + if (!env->key) { + crypto_openssl_log_errors(LOG_WARN, "generating RSA key"); + return -1; + } + + return 0; +} + +/** Return true if <b>env</b> has a valid key; false otherwise. + */ +int +crypto_pk_is_valid_private_key(const crypto_pk_t *env) +{ + int r; + tor_assert(env); + + r = RSA_check_key(env->key); + if (r <= 0) { + crypto_openssl_log_errors(LOG_WARN,"checking RSA key"); + return 0; + } else { + return 1; + } +} + +/** Return true iff <b>env</b> contains a public key whose public exponent + * equals TOR_RSA_EXPONENT. + */ +int +crypto_pk_public_exponent_ok(const crypto_pk_t *env) +{ + tor_assert(env); + tor_assert(env->key); + + const BIGNUM *e; + +#ifdef OPENSSL_1_1_API + const BIGNUM *n, *d; + RSA_get0_key(env->key, &n, &e, &d); +#else + e = env->key->e; +#endif /* defined(OPENSSL_1_1_API) */ + return BN_is_word(e, TOR_RSA_EXPONENT); +} + +/** Compare the public-key components of a and b. Return less than 0 + * if a\<b, 0 if a==b, and greater than 0 if a\>b. A NULL key is + * considered to be less than all non-NULL keys, and equal to itself. + * + * Note that this may leak information about the keys through timing. + */ +int +crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b) +{ + int result; + char a_is_non_null = (a != NULL) && (a->key != NULL); + char b_is_non_null = (b != NULL) && (b->key != NULL); + char an_argument_is_null = !a_is_non_null | !b_is_non_null; + + result = tor_memcmp(&a_is_non_null, &b_is_non_null, sizeof(a_is_non_null)); + if (an_argument_is_null) + return result; + + const BIGNUM *a_n, *a_e; + const BIGNUM *b_n, *b_e; + +#ifdef OPENSSL_1_1_API + const BIGNUM *a_d, *b_d; + RSA_get0_key(a->key, &a_n, &a_e, &a_d); + RSA_get0_key(b->key, &b_n, &b_e, &b_d); +#else + a_n = a->key->n; + a_e = a->key->e; + b_n = b->key->n; + b_e = b->key->e; +#endif /* defined(OPENSSL_1_1_API) */ + + tor_assert(a_n != NULL && a_e != NULL); + tor_assert(b_n != NULL && b_e != NULL); + + result = BN_cmp(a_n, b_n); + if (result) + return result; + return BN_cmp(a_e, b_e); +} + +/** Return the size of the public key modulus in <b>env</b>, in bytes. */ +size_t +crypto_pk_keysize(const crypto_pk_t *env) +{ + tor_assert(env); + tor_assert(env->key); + + return (size_t) RSA_size((RSA*)env->key); +} + +/** Return the size of the public key modulus of <b>env</b>, in bits. */ +int +crypto_pk_num_bits(crypto_pk_t *env) +{ + tor_assert(env); + tor_assert(env->key); + +#ifdef OPENSSL_1_1_API + /* It's so stupid that there's no other way to check that n is valid + * before calling RSA_bits(). + */ + const BIGNUM *n, *e, *d; + RSA_get0_key(env->key, &n, &e, &d); + tor_assert(n != NULL); + + return RSA_bits(env->key); +#else /* !(defined(OPENSSL_1_1_API)) */ + tor_assert(env->key->n); + return BN_num_bits(env->key->n); +#endif /* defined(OPENSSL_1_1_API) */ +} + +/** Increase the reference count of <b>env</b>, and return it. + */ +crypto_pk_t * +crypto_pk_dup_key(crypto_pk_t *env) +{ + tor_assert(env); + tor_assert(env->key); + + env->refs++; + return env; +} + +/** Replace dest with src (private key only). (Dest must have a refcount + * of 1) + */ +void +crypto_pk_assign_private(crypto_pk_t *dest, const crypto_pk_t *src) +{ + tor_assert(dest); + tor_assert(dest->refs == 1); + tor_assert(src); + RSA_free(dest->key); + dest->key = RSAPrivateKey_dup(src->key); +} + +/** Replace dest with src (public key only). (Dest must have a refcount + * of 1) + */ +void +crypto_pk_assign_public(crypto_pk_t *dest, const crypto_pk_t *src) +{ + tor_assert(dest); + tor_assert(dest->refs == 1); + tor_assert(src); + RSA_free(dest->key); + dest->key = RSAPublicKey_dup(src->key); +} + +/** Make a real honest-to-goodness copy of <b>env</b>, and return it. + * Returns NULL on failure. */ +crypto_pk_t * +crypto_pk_copy_full(crypto_pk_t *env) +{ + RSA *new_key; + int privatekey = 0; + tor_assert(env); + tor_assert(env->key); + + if (crypto_pk_key_is_private(env)) { + new_key = RSAPrivateKey_dup(env->key); + privatekey = 1; + } else { + new_key = RSAPublicKey_dup(env->key); + } + if (!new_key) { + /* LCOV_EXCL_START + * + * We can't cause RSA*Key_dup() to fail, so we can't really test this. + */ + log_err(LD_CRYPTO, "Unable to duplicate a %s key: openssl failed.", + privatekey?"private":"public"); + crypto_openssl_log_errors(LOG_ERR, + privatekey ? "Duplicating a private key" : + "Duplicating a public key"); + tor_fragile_assert(); + return NULL; + /* LCOV_EXCL_STOP */ + } + + return crypto_new_pk_from_openssl_rsa_(new_key); +} + +/** Encrypt <b>fromlen</b> bytes from <b>from</b> with the public key + * in <b>env</b>, using the padding method <b>padding</b>. On success, + * write the result to <b>to</b>, and return the number of bytes + * written. On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +int +crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen, int padding) +{ + int r; + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(fromlen<INT_MAX); + tor_assert(tolen >= crypto_pk_keysize(env)); + + r = RSA_public_encrypt((int)fromlen, + (unsigned char*)from, (unsigned char*)to, + env->key, crypto_get_rsa_padding(padding)); + if (r<0) { + crypto_openssl_log_errors(LOG_WARN, "performing RSA encryption"); + return -1; + } + return r; +} + +/** Decrypt <b>fromlen</b> bytes from <b>from</b> with the private key + * in <b>env</b>, using the padding method <b>padding</b>. On success, + * write the result to <b>to</b>, and return the number of bytes + * written. On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +int +crypto_pk_private_decrypt(crypto_pk_t *env, char *to, + size_t tolen, + const char *from, size_t fromlen, + int padding, int warnOnFailure) +{ + int r; + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(env->key); + tor_assert(fromlen<INT_MAX); + tor_assert(tolen >= crypto_pk_keysize(env)); + if (!crypto_pk_key_is_private(env)) + /* Not a private key */ + return -1; + + r = RSA_private_decrypt((int)fromlen, + (unsigned char*)from, (unsigned char*)to, + env->key, crypto_get_rsa_padding(padding)); + + if (r<0) { + crypto_openssl_log_errors(warnOnFailure?LOG_WARN:LOG_DEBUG, + "performing RSA decryption"); + return -1; + } + return r; +} + +/** Check the signature in <b>from</b> (<b>fromlen</b> bytes long) with the + * public key in <b>env</b>, using PKCS1 padding. On success, write the + * signed data to <b>to</b>, and return the number of bytes written. + * On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +MOCK_IMPL(int, +crypto_pk_public_checksig,(const crypto_pk_t *env, char *to, + size_t tolen, + const char *from, size_t fromlen)) +{ + int r; + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(fromlen < INT_MAX); + tor_assert(tolen >= crypto_pk_keysize(env)); + r = RSA_public_decrypt((int)fromlen, + (unsigned char*)from, (unsigned char*)to, + env->key, RSA_PKCS1_PADDING); + + if (r<0) { + crypto_openssl_log_errors(LOG_INFO, "checking RSA signature"); + return -1; + } + return r; +} + +/** Sign <b>fromlen</b> bytes of data from <b>from</b> with the private key in + * <b>env</b>, using PKCS1 padding. On success, write the signature to + * <b>to</b>, and return the number of bytes written. On failure, return + * -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +int +crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen) +{ + int r; + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(fromlen < INT_MAX); + tor_assert(tolen >= crypto_pk_keysize(env)); + if (!crypto_pk_key_is_private(env)) + /* Not a private key */ + return -1; + + r = RSA_private_encrypt((int)fromlen, + (unsigned char*)from, (unsigned char*)to, + (RSA*)env->key, RSA_PKCS1_PADDING); + if (r<0) { + crypto_openssl_log_errors(LOG_WARN, "generating RSA signature"); + return -1; + } + return r; +} + +/** ASN.1-encode the public portion of <b>pk</b> into <b>dest</b>. + * Return -1 on error, or the number of characters used on success. + */ +int +crypto_pk_asn1_encode(const crypto_pk_t *pk, char *dest, size_t dest_len) +{ + int len; + unsigned char *buf = NULL; + + len = i2d_RSAPublicKey(pk->key, &buf); + if (len < 0 || buf == NULL) + return -1; + + if ((size_t)len > dest_len || dest_len > SIZE_T_CEILING) { + OPENSSL_free(buf); + return -1; + } + /* We don't encode directly into 'dest', because that would be illegal + * type-punning. (C99 is smarter than me, C99 is smarter than me...) + */ + memcpy(dest,buf,len); + OPENSSL_free(buf); + return len; +} + +/** Decode an ASN.1-encoded public key from <b>str</b>; return the result on + * success and NULL on failure. + */ +crypto_pk_t * +crypto_pk_asn1_decode(const char *str, size_t len) +{ + RSA *rsa; + unsigned char *buf; + const unsigned char *cp; + cp = buf = tor_malloc(len); + memcpy(buf,str,len); + rsa = d2i_RSAPublicKey(NULL, &cp, len); + tor_free(buf); + if (!rsa) { + crypto_openssl_log_errors(LOG_WARN,"decoding public key"); + return NULL; + } + return crypto_new_pk_from_openssl_rsa_(rsa); +} + +/** ASN.1-encode the private portion of <b>pk</b> into <b>dest</b>. + * Return -1 on error, or the number of characters used on success. + */ +int +crypto_pk_asn1_encode_private(const crypto_pk_t *pk, char *dest, + size_t dest_len) +{ + int len; + unsigned char *buf = NULL; + + len = i2d_RSAPrivateKey(pk->key, &buf); + if (len < 0 || buf == NULL) + return -1; + + if ((size_t)len > dest_len || dest_len > SIZE_T_CEILING) { + OPENSSL_free(buf); + return -1; + } + /* We don't encode directly into 'dest', because that would be illegal + * type-punning. (C99 is smarter than me, C99 is smarter than me...) + */ + memcpy(dest,buf,len); + OPENSSL_free(buf); + return len; +} + +/** Decode an ASN.1-encoded private key from <b>str</b>; return the result on + * success and NULL on failure. + */ +crypto_pk_t * +crypto_pk_asn1_decode_private(const char *str, size_t len) +{ + RSA *rsa; + unsigned char *buf; + const unsigned char *cp; + cp = buf = tor_malloc(len); + memcpy(buf,str,len); + rsa = d2i_RSAPrivateKey(NULL, &cp, len); + tor_free(buf); + if (!rsa) { + crypto_openssl_log_errors(LOG_WARN,"decoding public key"); + return NULL; + } + crypto_pk_t *result = crypto_new_pk_from_openssl_rsa_(rsa); + if (! crypto_pk_is_valid_private_key(result)) { + crypto_pk_free(result); + return NULL; + } + return result; +} diff --git a/src/lib/crypt_ops/crypto_s2k.c b/src/lib/crypt_ops/crypto_s2k.c new file mode 100644 index 0000000000..42276597d4 --- /dev/null +++ b/src/lib/crypt_ops/crypto_s2k.c @@ -0,0 +1,525 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_s2k.c + * + * \brief Functions for deriving keys from human-readable passphrases. + */ + +#define CRYPTO_S2K_PRIVATE + +#include "lib/crypt_ops/crypto_cipher.h" +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/crypt_ops/crypto_hkdf.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_s2k.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/ctime/di_ops.h" +#include "lib/log/util_bug.h" +#include "lib/intmath/cmp.h" + +#ifdef ENABLE_OPENSSL +#include <openssl/evp.h> +#endif +#ifdef ENABLE_NSS +#include <pk11pub.h> +#endif + +#if defined(HAVE_LIBSCRYPT_H) && defined(HAVE_LIBSCRYPT_SCRYPT) +#define HAVE_SCRYPT +#include <libscrypt.h> +#endif + +#include <string.h> + +/* Encoded secrets take the form: + + u8 type; + u8 salt_and_parameters[depends on type]; + u8 key[depends on type]; + + As a special case, if the encoded secret is exactly 29 bytes long, + type 0 is understood. + + Recognized types are: + 00 -- RFC2440. salt_and_parameters is 9 bytes. key is 20 bytes. + salt_and_parameters is 8 bytes random salt, + 1 byte iteration info. + 01 -- PKBDF2_SHA1. salt_and_parameters is 17 bytes. key is 20 bytes. + salt_and_parameters is 16 bytes random salt, + 1 byte iteration info. + 02 -- SCRYPT_SALSA208_SHA256. salt_and_parameters is 18 bytes. key is + 32 bytes. + salt_and_parameters is 18 bytes random salt, 2 bytes iteration + info. +*/ + +#define S2K_TYPE_RFC2440 0 +#define S2K_TYPE_PBKDF2 1 +#define S2K_TYPE_SCRYPT 2 + +#define PBKDF2_SPEC_LEN 17 +#define PBKDF2_KEY_LEN 20 + +#define SCRYPT_SPEC_LEN 18 +#define SCRYPT_KEY_LEN 32 + +/** Given an algorithm ID (one of S2K_TYPE_*), return the length of the + * specifier part of it, without the prefix type byte. Return -1 if it is not + * a valid algorithm ID. */ +static int +secret_to_key_spec_len(uint8_t type) +{ + switch (type) { + case S2K_TYPE_RFC2440: + return S2K_RFC2440_SPECIFIER_LEN; + case S2K_TYPE_PBKDF2: + return PBKDF2_SPEC_LEN; + case S2K_TYPE_SCRYPT: + return SCRYPT_SPEC_LEN; + default: + return -1; + } +} + +/** Given an algorithm ID (one of S2K_TYPE_*), return the length of the + * its preferred output. */ +static int +secret_to_key_key_len(uint8_t type) +{ + switch (type) { + case S2K_TYPE_RFC2440: + return DIGEST_LEN; + case S2K_TYPE_PBKDF2: + return DIGEST_LEN; + case S2K_TYPE_SCRYPT: + return DIGEST256_LEN; + // LCOV_EXCL_START + default: + tor_fragile_assert(); + return -1; + // LCOV_EXCL_STOP + } +} + +/** Given a specifier in <b>spec_and_key</b> of length + * <b>spec_and_key_len</b>, along with its prefix algorithm ID byte, and along + * with a key if <b>key_included</b> is true, check whether the whole + * specifier-and-key is of valid length, and return the algorithm type if it + * is. Set *<b>legacy_out</b> to 1 iff this is a legacy password hash or + * legacy specifier. Return an error code on failure. + */ +static int +secret_to_key_get_type(const uint8_t *spec_and_key, size_t spec_and_key_len, + int key_included, int *legacy_out) +{ + size_t legacy_len = S2K_RFC2440_SPECIFIER_LEN; + uint8_t type; + int total_len; + + if (key_included) + legacy_len += DIGEST_LEN; + + if (spec_and_key_len == legacy_len) { + *legacy_out = 1; + return S2K_TYPE_RFC2440; + } + + *legacy_out = 0; + if (spec_and_key_len == 0) + return S2K_BAD_LEN; + + type = spec_and_key[0]; + total_len = secret_to_key_spec_len(type); + if (total_len < 0) + return S2K_BAD_ALGORITHM; + if (key_included) { + int keylen = secret_to_key_key_len(type); + if (keylen < 0) + return S2K_BAD_ALGORITHM; + total_len += keylen; + } + + if ((size_t)total_len + 1 == spec_and_key_len) + return type; + else + return S2K_BAD_LEN; +} + +/** + * Write a new random s2k specifier of type <b>type</b>, without prefixing + * type byte, to <b>spec_out</b>, which must have enough room. May adjust + * parameter choice based on <b>flags</b>. + */ +static int +make_specifier(uint8_t *spec_out, uint8_t type, unsigned flags) +{ + int speclen = secret_to_key_spec_len(type); + if (speclen < 0) + return S2K_BAD_ALGORITHM; + + crypto_rand((char*)spec_out, speclen); + switch (type) { + case S2K_TYPE_RFC2440: + /* Hash 64 k of data. */ + spec_out[S2K_RFC2440_SPECIFIER_LEN-1] = 96; + break; + case S2K_TYPE_PBKDF2: + /* 131 K iterations */ + spec_out[PBKDF2_SPEC_LEN-1] = 17; + break; + case S2K_TYPE_SCRYPT: + if (flags & S2K_FLAG_LOW_MEM) { + /* N = 1<<12 */ + spec_out[SCRYPT_SPEC_LEN-2] = 12; + } else { + /* N = 1<<15 */ + spec_out[SCRYPT_SPEC_LEN-2] = 15; + } + /* r = 8; p = 2. */ + spec_out[SCRYPT_SPEC_LEN-1] = (3u << 4) | (1u << 0); + break; + // LCOV_EXCL_START - we should have returned above. + default: + tor_fragile_assert(); + return S2K_BAD_ALGORITHM; + // LCOV_EXCL_STOP + } + + return speclen; +} + +/** Implement RFC2440-style iterated-salted S2K conversion: convert the + * <b>secret_len</b>-byte <b>secret</b> into a <b>key_out_len</b> byte + * <b>key_out</b>. As in RFC2440, the first 8 bytes of s2k_specifier + * are a salt; the 9th byte describes how much iteration to do. + * If <b>key_out_len</b> > DIGEST_LEN, use HDKF to expand the result. + */ +void +secret_to_key_rfc2440(char *key_out, size_t key_out_len, const char *secret, + size_t secret_len, const char *s2k_specifier) +{ + crypto_digest_t *d; + uint8_t c; + size_t count, tmplen; + char *tmp; + uint8_t buf[DIGEST_LEN]; + tor_assert(key_out_len < SIZE_T_CEILING); + +#define EXPBIAS 6 + c = s2k_specifier[8]; + count = ((uint32_t)16 + (c & 15)) << ((c >> 4) + EXPBIAS); +#undef EXPBIAS + + d = crypto_digest_new(); + tmplen = 8+secret_len; + tmp = tor_malloc(tmplen); + memcpy(tmp,s2k_specifier,8); + memcpy(tmp+8,secret,secret_len); + secret_len += 8; + while (count) { + if (count >= secret_len) { + crypto_digest_add_bytes(d, tmp, secret_len); + count -= secret_len; + } else { + crypto_digest_add_bytes(d, tmp, count); + count = 0; + } + } + crypto_digest_get_digest(d, (char*)buf, sizeof(buf)); + + if (key_out_len <= sizeof(buf)) { + memcpy(key_out, buf, key_out_len); + } else { + crypto_expand_key_material_rfc5869_sha256(buf, DIGEST_LEN, + (const uint8_t*)s2k_specifier, 8, + (const uint8_t*)"EXPAND", 6, + (uint8_t*)key_out, key_out_len); + } + memwipe(tmp, 0, tmplen); + memwipe(buf, 0, sizeof(buf)); + tor_free(tmp); + crypto_digest_free(d); +} + +/** + * Helper: given a valid specifier without prefix type byte in <b>spec</b>, + * whose length must be correct, and given a secret passphrase <b>secret</b> + * of length <b>secret_len</b>, compute the key and store it into + * <b>key_out</b>, which must have enough room for secret_to_key_key_len(type) + * bytes. Return the number of bytes written on success and an error code + * on failure. + */ +STATIC int +secret_to_key_compute_key(uint8_t *key_out, size_t key_out_len, + const uint8_t *spec, size_t spec_len, + const char *secret, size_t secret_len, + int type) +{ + int rv; + if (key_out_len > INT_MAX) + return S2K_BAD_LEN; + + switch (type) { + case S2K_TYPE_RFC2440: + secret_to_key_rfc2440((char*)key_out, key_out_len, secret, secret_len, + (const char*)spec); + return (int)key_out_len; + + case S2K_TYPE_PBKDF2: { + uint8_t log_iters; + if (spec_len < 1 || secret_len > INT_MAX || spec_len > INT_MAX) + return S2K_BAD_LEN; + log_iters = spec[spec_len-1]; + if (log_iters > 31) + return S2K_BAD_PARAMS; +#ifdef ENABLE_OPENSSL + rv = PKCS5_PBKDF2_HMAC_SHA1(secret, (int)secret_len, + spec, (int)spec_len-1, + (1<<log_iters), + (int)key_out_len, key_out); + if (rv < 0) + return S2K_FAILED; + return (int)key_out_len; +#else + SECItem passItem = { .type = siBuffer, + .data = (unsigned char *) secret, + .len = (int)secret_len }; + SECItem saltItem = { .type = siBuffer, + .data = (unsigned char *) spec, + .len = (int)spec_len - 1 }; + SECAlgorithmID *alg = NULL; + PK11SymKey *key = NULL; + + rv = S2K_FAILED; + alg = PK11_CreatePBEV2AlgorithmID( + SEC_OID_PKCS5_PBKDF2, SEC_OID_HMAC_SHA1, SEC_OID_HMAC_SHA1, + (int)key_out_len, (1<<log_iters), &saltItem); + if (alg == NULL) + return S2K_FAILED; + + key = PK11_PBEKeyGen(NULL /* slot */, + alg, + &passItem, + false, + NULL); + + SECStatus st = PK11_ExtractKeyValue(key); + if (st != SECSuccess) + goto nss_pbkdf_err; + + const SECItem *iptr = PK11_GetKeyData(key); + if (iptr == NULL) + goto nss_pbkdf_err; + + rv = MIN((int)iptr->len, (int)key_out_len); + memcpy(key_out, iptr->data, rv); + + nss_pbkdf_err: + if (key) + PK11_FreeSymKey(key); + if (alg) + SECOID_DestroyAlgorithmID(alg, PR_TRUE); + return rv; +#endif + } + + case S2K_TYPE_SCRYPT: { +#ifdef HAVE_SCRYPT + uint8_t log_N, log_r, log_p; + uint64_t N; + uint32_t r, p; + if (spec_len < 2) + return S2K_BAD_LEN; + log_N = spec[spec_len-2]; + log_r = (spec[spec_len-1]) >> 4; + log_p = (spec[spec_len-1]) & 15; + if (log_N > 63) + return S2K_BAD_PARAMS; + N = ((uint64_t)1) << log_N; + r = 1u << log_r; + p = 1u << log_p; + rv = libscrypt_scrypt((const uint8_t*)secret, secret_len, + spec, spec_len-2, N, r, p, key_out, key_out_len); + if (rv != 0) + return S2K_FAILED; + return (int)key_out_len; +#else /* !(defined(HAVE_SCRYPT)) */ + return S2K_NO_SCRYPT_SUPPORT; +#endif /* defined(HAVE_SCRYPT) */ + } + default: + return S2K_BAD_ALGORITHM; + } +} + +/** + * Given a specifier previously constructed with secret_to_key_make_specifier + * in <b>spec</b> of length <b>spec_len</b>, and a secret password in + * <b>secret</b> of length <b>secret_len</b>, generate <b>key_out_len</b> + * bytes of cryptographic material in <b>key_out</b>. The native output of + * the secret-to-key function will be truncated if key_out_len is short, and + * expanded with HKDF if key_out_len is long. Returns S2K_OKAY on success, + * and an error code on failure. + */ +int +secret_to_key_derivekey(uint8_t *key_out, size_t key_out_len, + const uint8_t *spec, size_t spec_len, + const char *secret, size_t secret_len) +{ + int legacy_format = 0; + int type = secret_to_key_get_type(spec, spec_len, 0, &legacy_format); + int r; + + if (type < 0) + return type; +#ifndef HAVE_SCRYPT + if (type == S2K_TYPE_SCRYPT) + return S2K_NO_SCRYPT_SUPPORT; + #endif + + if (! legacy_format) { + ++spec; + --spec_len; + } + + r = secret_to_key_compute_key(key_out, key_out_len, spec, spec_len, + secret, secret_len, type); + if (r < 0) + return r; + else + return S2K_OKAY; +} + +/** + * Construct a new s2k algorithm specifier and salt in <b>buf</b>, according + * to the bitwise-or of some S2K_FLAG_* options in <b>flags</b>. Up to + * <b>buf_len</b> bytes of storage may be used in <b>buf</b>. Return the + * number of bytes used on success and an error code on failure. + */ +int +secret_to_key_make_specifier(uint8_t *buf, size_t buf_len, unsigned flags) +{ + int rv; + int spec_len; +#ifdef HAVE_SCRYPT + uint8_t type = S2K_TYPE_SCRYPT; +#else + uint8_t type = S2K_TYPE_RFC2440; +#endif + + if (flags & S2K_FLAG_NO_SCRYPT) + type = S2K_TYPE_RFC2440; + if (flags & S2K_FLAG_USE_PBKDF2) + type = S2K_TYPE_PBKDF2; + + spec_len = secret_to_key_spec_len(type); + + if ((int)buf_len < spec_len + 1) + return S2K_TRUNCATED; + + buf[0] = type; + rv = make_specifier(buf+1, type, flags); + if (rv < 0) + return rv; + else + return rv + 1; +} + +/** + * Hash a passphrase from <b>secret</b> of length <b>secret_len</b>, according + * to the bitwise-or of some S2K_FLAG_* options in <b>flags</b>, and store the + * hash along with salt and hashing parameters into <b>buf</b>. Up to + * <b>buf_len</b> bytes of storage may be used in <b>buf</b>. Set + * *<b>len_out</b> to the number of bytes used and return S2K_OKAY on success; + * and return an error code on failure. + */ +int +secret_to_key_new(uint8_t *buf, + size_t buf_len, + size_t *len_out, + const char *secret, size_t secret_len, + unsigned flags) +{ + int key_len; + int spec_len; + int type; + int rv; + + spec_len = secret_to_key_make_specifier(buf, buf_len, flags); + + if (spec_len < 0) + return spec_len; + + type = buf[0]; + key_len = secret_to_key_key_len(type); + + if (key_len < 0) + return key_len; + + if ((int)buf_len < key_len + spec_len) + return S2K_TRUNCATED; + + rv = secret_to_key_compute_key(buf + spec_len, key_len, + buf + 1, spec_len-1, + secret, secret_len, type); + if (rv < 0) + return rv; + + *len_out = spec_len + key_len; + + return S2K_OKAY; +} + +/** + * Given a hashed passphrase in <b>spec_and_key</b> of length + * <b>spec_and_key_len</b> as generated by secret_to_key_new(), verify whether + * it is a hash of the passphrase <b>secret</b> of length <b>secret_len</b>. + * Return S2K_OKAY on a match, S2K_BAD_SECRET on a well-formed hash that + * doesn't match this secret, and another error code on other errors. + */ +int +secret_to_key_check(const uint8_t *spec_and_key, size_t spec_and_key_len, + const char *secret, size_t secret_len) +{ + int is_legacy = 0; + int type = secret_to_key_get_type(spec_and_key, spec_and_key_len, + 1, &is_legacy); + uint8_t buf[32]; + int spec_len; + int key_len; + int rv; + + if (type < 0) + return type; + + if (! is_legacy) { + spec_and_key++; + spec_and_key_len--; + } + + spec_len = secret_to_key_spec_len(type); + key_len = secret_to_key_key_len(type); + tor_assert(spec_len > 0); + tor_assert(key_len > 0); + tor_assert(key_len <= (int) sizeof(buf)); + tor_assert((int)spec_and_key_len == spec_len + key_len); + rv = secret_to_key_compute_key(buf, key_len, + spec_and_key, spec_len, + secret, secret_len, type); + if (rv < 0) + goto done; + + if (tor_memeq(buf, spec_and_key + spec_len, key_len)) + rv = S2K_OKAY; + else + rv = S2K_BAD_SECRET; + + done: + memwipe(buf, 0, sizeof(buf)); + return rv; +} diff --git a/src/lib/crypt_ops/crypto_s2k.h b/src/lib/crypt_ops/crypto_s2k.h new file mode 100644 index 0000000000..a16a3d781e --- /dev/null +++ b/src/lib/crypt_ops/crypto_s2k.h @@ -0,0 +1,78 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_s2k.h + * + * \brief Header for crypto_s2k.c + **/ + +#ifndef TOR_CRYPTO_S2K_H_INCLUDED +#define TOR_CRYPTO_S2K_H_INCLUDED + +#include <stdio.h> +#include "lib/cc/torint.h" + +/** Length of RFC2440-style S2K specifier: the first 8 bytes are a salt, the + * 9th describes how much iteration to do. */ +#define S2K_RFC2440_SPECIFIER_LEN 9 +void secret_to_key_rfc2440( + char *key_out, size_t key_out_len, const char *secret, + size_t secret_len, const char *s2k_specifier); + +/** Flag for secret-to-key function: do not use scrypt. */ +#define S2K_FLAG_NO_SCRYPT (1u<<0) +/** Flag for secret-to-key functions: if using a memory-tuned s2k function, + * assume that we have limited memory. */ +#define S2K_FLAG_LOW_MEM (1u<<1) +/** Flag for secret-to-key functions: force use of pbkdf2. Without this, we + * default to scrypt, then RFC2440. */ +#define S2K_FLAG_USE_PBKDF2 (1u<<2) + +/** Maximum possible output length from secret_to_key_new. */ +#define S2K_MAXLEN 64 + +/** Error code from secret-to-key functions: all is well */ +#define S2K_OKAY 0 +/** Error code from secret-to-key functions: generic failure */ +#define S2K_FAILED -1 +/** Error code from secret-to-key functions: provided secret didn't match */ +#define S2K_BAD_SECRET -2 +/** Error code from secret-to-key functions: didn't recognize the algorithm */ +#define S2K_BAD_ALGORITHM -3 +/** Error code from secret-to-key functions: specifier wasn't valid */ +#define S2K_BAD_PARAMS -4 +/** Error code from secret-to-key functions: compiled without scrypt */ +#define S2K_NO_SCRYPT_SUPPORT -5 +/** Error code from secret-to-key functions: not enough space to write output. + */ +#define S2K_TRUNCATED -6 +/** Error code from secret-to-key functions: Wrong length for specifier. */ +#define S2K_BAD_LEN -7 + +int secret_to_key_new(uint8_t *buf, + size_t buf_len, + size_t *len_out, + const char *secret, size_t secret_len, + unsigned flags); + +int secret_to_key_make_specifier(uint8_t *buf, size_t buf_len, unsigned flags); + +int secret_to_key_check(const uint8_t *spec_and_key, size_t spec_and_key_len, + const char *secret, size_t secret_len); + +int secret_to_key_derivekey(uint8_t *key_out, size_t key_out_len, + const uint8_t *spec, size_t spec_len, + const char *secret, size_t secret_len); + +#ifdef CRYPTO_S2K_PRIVATE +STATIC int secret_to_key_compute_key(uint8_t *key_out, size_t key_out_len, + const uint8_t *spec, size_t spec_len, + const char *secret, size_t secret_len, + int type); +#endif /* defined(CRYPTO_S2K_PRIVATE) */ + +#endif /* !defined(TOR_CRYPTO_S2K_H_INCLUDED) */ diff --git a/src/lib/crypt_ops/crypto_util.c b/src/lib/crypt_ops/crypto_util.c new file mode 100644 index 0000000000..67a1a9eb92 --- /dev/null +++ b/src/lib/crypt_ops/crypto_util.c @@ -0,0 +1,111 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_util.c + * + * \brief Common cryptographic utilities. + **/ + +#define CRYPTO_UTIL_PRIVATE + +#include "lib/crypt_ops/crypto_util.h" +#include "lib/cc/compat_compiler.h" + +#include <string.h> + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#include <wincrypt.h> +#endif /* defined(_WIN32) */ + +#include <stdlib.h> + +#ifdef ENABLE_OPENSSL +DISABLE_GCC_WARNING(redundant-decls) +#include <openssl/err.h> +#include <openssl/crypto.h> +ENABLE_GCC_WARNING(redundant-decls) +#endif + +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +/** + * Destroy the <b>sz</b> bytes of data stored at <b>mem</b>, setting them to + * the value <b>byte</b>. + * If <b>mem</b> is NULL or <b>sz</b> is zero, nothing happens. + * + * This function is preferable to memset, since many compilers will happily + * optimize out memset() when they can convince themselves that the data being + * cleared will never be read. + * + * Right now, our convention is to use this function when we are wiping data + * that's about to become inaccessible, such as stack buffers that are about + * to go out of scope or structures that are about to get freed. (In + * practice, it appears that the compilers we're currently using will optimize + * out the memset()s for stack-allocated buffers, but not those for + * about-to-be-freed structures. That could change, though, so we're being + * wary.) If there are live reads for the data, then you can just use + * memset(). + */ +void +memwipe(void *mem, uint8_t byte, size_t sz) +{ + if (sz == 0) { + return; + } + /* If sz is nonzero, then mem must not be NULL. */ + tor_assert(mem != NULL); + + /* Data this large is likely to be an underflow. */ + tor_assert(sz < SIZE_T_CEILING); + + /* Because whole-program-optimization exists, we may not be able to just + * have this function call "memset". A smart compiler could inline it, then + * eliminate dead memsets, and declare itself to be clever. */ + +#if defined(SecureZeroMemory) || defined(HAVE_SECUREZEROMEMORY) + /* Here's what you do on windows. */ + SecureZeroMemory(mem,sz); +#elif defined(HAVE_RTLSECUREZEROMEMORY) + RtlSecureZeroMemory(mem,sz); +#elif defined(HAVE_EXPLICIT_BZERO) + /* The BSDs provide this. */ + explicit_bzero(mem, sz); +#elif defined(HAVE_MEMSET_S) + /* This is in the C99 standard. */ + memset_s(mem, sz, 0, sz); +#elif defined(ENABLE_OPENSSL) + /* This is a slow and ugly function from OpenSSL that fills 'mem' with junk + * based on the pointer value, then uses that junk to update a global + * variable. It's an elaborate ruse to trick the compiler into not + * optimizing out the "wipe this memory" code. Read it if you like zany + * programming tricks! In later versions of Tor, we should look for better + * not-optimized-out memory wiping stuff... + * + * ...or maybe not. In practice, there are pure-asm implementations of + * OPENSSL_cleanse() on most platforms, which ought to do the job. + **/ + + OPENSSL_cleanse(mem, sz); +#else + memset(mem, 0, sz); + asm volatile("" ::: "memory"); +#endif /* defined(SecureZeroMemory) || defined(HAVE_SECUREZEROMEMORY) || ... */ + + /* Just in case some caller of memwipe() is relying on getting a buffer + * filled with a particular value, fill the buffer. + * + * If this function gets inlined, this memset might get eliminated, but + * that's okay: We only care about this particular memset in the case where + * the caller should have been using memset(), and the memset() wouldn't get + * eliminated. In other words, this is here so that we won't break anything + * if somebody accidentally calls memwipe() instead of memset(). + **/ + memset(mem, byte, sz); +} diff --git a/src/lib/crypt_ops/crypto_util.h b/src/lib/crypt_ops/crypto_util.h new file mode 100644 index 0000000000..613a1bd0dd --- /dev/null +++ b/src/lib/crypt_ops/crypto_util.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_util.h + * + * \brief Common functions for cryptographic routines. + **/ + +#ifndef TOR_CRYPTO_UTIL_H +#define TOR_CRYPTO_UTIL_H + +#include "lib/cc/torint.h" + +/** OpenSSL-based utility functions. */ +void memwipe(void *mem, uint8_t byte, size_t sz); + +#endif /* !defined(TOR_CRYPTO_UTIL_H) */ diff --git a/src/lib/crypt_ops/digestset.c b/src/lib/crypt_ops/digestset.c new file mode 100644 index 0000000000..0dba64d595 --- /dev/null +++ b/src/lib/crypt_ops/digestset.c @@ -0,0 +1,58 @@ +/* Copyright (c) 2018-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file digestset.c + * \brief Implementation for a set of digests + **/ + +#include "orconfig.h" +#include "lib/container/bloomfilt.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/defs/digest_sizes.h" +#include "lib/crypt_ops/digestset.h" +#include "siphash.h" + +/* Wrap our hash function to have the signature that the bloom filter + * needs. */ +static uint64_t +bloomfilt_digest_hash(const struct sipkey *key, + const void *item) +{ + return siphash24(item, DIGEST_LEN, key); +} + +/** + * Allocate and return an digestset, suitable for holding up to + * <b>max_guess</b> distinct values. + */ +digestset_t * +digestset_new(int max_guess) +{ + uint8_t k[BLOOMFILT_KEY_LEN]; + crypto_rand((void*)k, sizeof(k)); + return bloomfilt_new(max_guess, bloomfilt_digest_hash, k); +} + +/** + * Add <b>digest</b> to <b>set</b>. + * + * All future queries for <b>digest</b> in set will return true. Removing + * items is not possible. + */ +void +digestset_add(digestset_t *set, const char *digest) +{ + bloomfilt_add(set, digest); +} + +/** + * Return true if <b>digest</b> is a member of <b>set</b>. (And probably, + * return false if <b>digest</b> is not a member of set.) + */ +int +digestset_probably_contains(const digestset_t *set, + const char *digest) +{ + return bloomfilt_probably_contains(set, digest); +} diff --git a/src/lib/crypt_ops/digestset.h b/src/lib/crypt_ops/digestset.h new file mode 100644 index 0000000000..91d53a0542 --- /dev/null +++ b/src/lib/crypt_ops/digestset.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2018-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file digestset.h + * \brief Types to handle sets of digests, based on bloom filters. + **/ + +#ifndef TOR_DIGESTSET_H +#define TOR_DIGESTSET_H + +#include "orconfig.h" +#include "lib/cc/torint.h" +#include "lib/container/bloomfilt.h" + +/** + * An digestset_t represents a set of 20-byte digest values. The + * implementation is probabilistic: false negatives cannot occur but false + * positives are possible. + */ +typedef struct bloomfilt_t digestset_t; + +digestset_t *digestset_new(int max_addresses_guess); +#define digestset_free(set) bloomfilt_free(set) +void digestset_add(digestset_t *set, const char *addr); +int digestset_probably_contains(const digestset_t *set, + const char *addr); + +#endif diff --git a/src/lib/crypt_ops/include.am b/src/lib/crypt_ops/include.am new file mode 100644 index 0000000000..1022096fdc --- /dev/null +++ b/src/lib/crypt_ops/include.am @@ -0,0 +1,70 @@ + +noinst_LIBRARIES += src/lib/libtor-crypt-ops.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-crypt-ops-testing.a +endif + +src_lib_libtor_crypt_ops_a_SOURCES = \ + src/lib/crypt_ops/crypto_cipher.c \ + src/lib/crypt_ops/crypto_curve25519.c \ + src/lib/crypt_ops/crypto_dh.c \ + src/lib/crypt_ops/crypto_digest.c \ + src/lib/crypt_ops/crypto_ed25519.c \ + src/lib/crypt_ops/crypto_format.c \ + src/lib/crypt_ops/crypto_hkdf.c \ + src/lib/crypt_ops/crypto_init.c \ + src/lib/crypt_ops/crypto_ope.c \ + src/lib/crypt_ops/crypto_pwbox.c \ + src/lib/crypt_ops/crypto_rand.c \ + src/lib/crypt_ops/crypto_rsa.c \ + src/lib/crypt_ops/crypto_s2k.c \ + src/lib/crypt_ops/crypto_util.c \ + src/lib/crypt_ops/digestset.c + +if USE_NSS +src_lib_libtor_crypt_ops_a_SOURCES += \ + src/lib/crypt_ops/aes_nss.c \ + src/lib/crypt_ops/crypto_dh_nss.c \ + src/lib/crypt_ops/crypto_nss_mgt.c \ + src/lib/crypt_ops/crypto_rsa_nss.c +else +src_lib_libtor_crypt_ops_a_SOURCES += \ + src/lib/crypt_ops/aes_openssl.c \ + src/lib/crypt_ops/crypto_rsa_openssl.c +endif + +if USE_OPENSSL +src_lib_libtor_crypt_ops_a_SOURCES += \ + src/lib/crypt_ops/crypto_dh_openssl.c \ + src/lib/crypt_ops/crypto_openssl_mgt.c +endif + +src_lib_libtor_crypt_ops_a_CFLAGS = $(AM_CFLAGS) $(TOR_CFLAGS_CRYPTLIB) + +src_lib_libtor_crypt_ops_testing_a_SOURCES = \ + $(src_lib_libtor_crypt_ops_a_SOURCES) +src_lib_libtor_crypt_ops_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_crypt_ops_testing_a_CFLAGS = \ + $(AM_CFLAGS) $(TOR_CFLAGS_CRYPTLIB) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/crypt_ops/aes.h \ + src/lib/crypt_ops/compat_openssl.h \ + src/lib/crypt_ops/crypto_curve25519.h \ + src/lib/crypt_ops/crypto_dh.h \ + src/lib/crypt_ops/crypto_digest.h \ + src/lib/crypt_ops/crypto_ed25519.h \ + src/lib/crypt_ops/crypto_format.h \ + src/lib/crypt_ops/crypto_cipher.h \ + src/lib/crypt_ops/crypto_hkdf.h \ + src/lib/crypt_ops/crypto_init.h \ + src/lib/crypt_ops/crypto_nss_mgt.h \ + src/lib/crypt_ops/crypto_openssl_mgt.h \ + src/lib/crypt_ops/crypto_ope.h \ + src/lib/crypt_ops/crypto_pwbox.h \ + src/lib/crypt_ops/crypto_rand.h \ + src/lib/crypt_ops/crypto_rsa.h \ + src/lib/crypt_ops/crypto_s2k.h \ + src/lib/crypt_ops/crypto_util.h \ + src/lib/crypt_ops/digestset.h diff --git a/src/lib/ctime/.may_include b/src/lib/ctime/.may_include new file mode 100644 index 0000000000..e74669bce1 --- /dev/null +++ b/src/lib/ctime/.may_include @@ -0,0 +1,5 @@ +orconfig.h +lib/cc/*.h +lib/ctime/*.h +lib/err/*.h +lib/malloc/*.h diff --git a/src/lib/ctime/di_ops.c b/src/lib/ctime/di_ops.c new file mode 100644 index 0000000000..89e0837ae9 --- /dev/null +++ b/src/lib/ctime/di_ops.c @@ -0,0 +1,278 @@ +/* Copyright (c) 2011-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file di_ops.c + * \brief Functions for data-independent operations. + **/ + +#include "orconfig.h" +#include "lib/ctime/di_ops.h" +#include "lib/err/torerr.h" +#include "lib/malloc/malloc.h" + +#include <string.h> + +/** + * Timing-safe version of memcmp. As memcmp, compare the <b>sz</b> bytes at + * <b>a</b> with the <b>sz</b> bytes at <b>b</b>, and return less than 0 if + * the bytes at <b>a</b> lexically precede those at <b>b</b>, 0 if the byte + * ranges are equal, and greater than zero if the bytes at <b>a</b> lexically + * follow those at <b>b</b>. + * + * This implementation differs from memcmp in that its timing behavior is not + * data-dependent: it should return in the same amount of time regardless of + * the contents of <b>a</b> and <b>b</b>. + * + * Note that if all you care about is equality, this implementation is + * overkill: it would be better to use tor_memeq() or tor_memneq(). + */ +int +tor_memcmp(const void *a, const void *b, size_t len) +{ +#ifdef HAVE_TIMINGSAFE_MEMCMP + return timingsafe_memcmp(a, b, len); +#else + const uint8_t *x = a; + const uint8_t *y = b; + size_t i = len; + int retval = 0; + + /* This loop goes from the end of the arrays to the start. At the + * start of every iteration, before we decrement i, we have set + * "retval" equal to the result of memcmp(a+i,b+i,len-i). During the + * loop, we update retval by leaving it unchanged if x[i]==y[i] and + * setting it to x[i]-y[i] if x[i]!= y[i]. + * + * The following assumes we are on a system with two's-complement + * arithmetic. We check for this at configure-time with the check + * that sets USING_TWOS_COMPLEMENT. If we aren't two's complement, then + * torint.h will stop compilation with an error. + */ + while (i--) { + int v1 = x[i]; + int v2 = y[i]; + int equal_p = v1 ^ v2; + + /* The following sets bits 8 and above of equal_p to 'equal_p == + * 0', and thus to v1 == v2. (To see this, note that if v1 == + * v2, then v1^v2 == equal_p == 0, so equal_p-1 == -1, which is the + * same as ~0 on a two's-complement machine. Then note that if + * v1 != v2, then 0 < v1 ^ v2 < 256, so 0 <= equal_p - 1 < 255.) + */ + --equal_p; + + equal_p >>= 8; + /* Thanks to (sign-preserving) arithmetic shift, equal_p is now + * equal to -(v1 == v2), which is exactly what we need below. + * (Since we're assuming two's-complement arithmetic, -1 is the + * same as ~0 (all bits set).) + * + * (The result of an arithmetic shift on a negative value is + * actually implementation-defined in standard C. So how do we + * get away with assuming it? Easy. We check.) */ +#if ((-60 >> 8) != -1) +#error "According to cpp, right-shift doesn't perform sign-extension." +#endif +#ifndef RSHIFT_DOES_SIGN_EXTEND +#error "According to configure, right-shift doesn't perform sign-extension." +#endif + + /* If v1 == v2, equal_p is ~0, so this will leave retval + * unchanged; otherwise, equal_p is 0, so this will zero it. */ + retval &= equal_p; + + /* If v1 == v2, then this adds 0, and leaves retval unchanged. + * Otherwise, we just zeroed retval, so this sets it to v1 - v2. */ + retval += (v1 - v2); + + /* There. Now retval is equal to its previous value if v1 == v2, and + * equal to v1 - v2 if v1 != v2. */ + } + + return retval; +#endif /* defined(HAVE_TIMINGSAFE_MEMCMP) */ +} + +/** + * Timing-safe memory comparison. Return true if the <b>sz</b> bytes at + * <b>a</b> are the same as the <b>sz</b> bytes at <b>b</b>, and 0 otherwise. + * + * This implementation differs from !memcmp(a,b,sz) in that its timing + * behavior is not data-dependent: it should return in the same amount of time + * regardless of the contents of <b>a</b> and <b>b</b>. It differs from + * !tor_memcmp(a,b,sz) by being faster. + */ +int +tor_memeq(const void *a, const void *b, size_t sz) +{ + /* Treat a and b as byte ranges. */ + const uint8_t *ba = a, *bb = b; + uint32_t any_difference = 0; + while (sz--) { + /* Set byte_diff to all of those bits that are different in *ba and *bb, + * and advance both ba and bb. */ + const uint8_t byte_diff = *ba++ ^ *bb++; + + /* Set bits in any_difference if they are set in byte_diff. */ + any_difference |= byte_diff; + } + + /* Now any_difference is 0 if there are no bits different between + * a and b, and is nonzero if there are bits different between a + * and b. Now for paranoia's sake, let's convert it to 0 or 1. + * + * (If we say "!any_difference", the compiler might get smart enough + * to optimize-out our data-independence stuff above.) + * + * To unpack: + * + * If any_difference == 0: + * any_difference - 1 == ~0 + * (any_difference - 1) >> 8 == 0x00ffffff + * 1 & ((any_difference - 1) >> 8) == 1 + * + * If any_difference != 0: + * 0 < any_difference < 256, so + * 0 <= any_difference - 1 < 255 + * (any_difference - 1) >> 8 == 0 + * 1 & ((any_difference - 1) >> 8) == 0 + */ + + /*coverity[overflow]*/ + return 1 & ((any_difference - 1) >> 8); +} + +/* Implement di_digest256_map_t as a linked list of entries. */ +struct di_digest256_map_t { + struct di_digest256_map_t *next; + uint8_t key[32]; + void *val; +}; + +/** Release all storage held in <b>map</b>, calling free_fn on each value + * as we go. */ +void +dimap_free_(di_digest256_map_t *map, dimap_free_fn free_fn) +{ + while (map) { + di_digest256_map_t *victim = map; + map = map->next; + if (free_fn) + free_fn(victim->val); + tor_free(victim); + } +} + +/** Adjust the map at *<b>map</b>, adding an entry for <b>key</b> -> + * <b>val</b>, where <b>key</b> is a DIGEST256_LEN-byte key. + * + * The caller MUST NOT add a key that already appears in the map. + */ +void +dimap_add_entry(di_digest256_map_t **map, + const uint8_t *key, void *val) +{ + di_digest256_map_t *new_ent; + { + void *old_val = dimap_search(*map, key, NULL); + raw_assert(! old_val); + raw_assert(val); + } + new_ent = tor_malloc_zero(sizeof(di_digest256_map_t)); + new_ent->next = *map; + memcpy(new_ent->key, key, 32); + new_ent->val = val; + *map = new_ent; +} + +/** Search the map at <b>map</b> for an entry whose key is <b>key</b> (a + * DIGEST256_LEN-byte key) returning the corresponding value if we found one, + * and returning <b>dflt_val</b> if the key wasn't found. + * + * This operation takes an amount of time dependent only on the length of + * <b>map</b>, not on the position or presence of <b>key</b> within <b>map</b>. + */ +void * +dimap_search(const di_digest256_map_t *map, const uint8_t *key, + void *dflt_val) +{ + uintptr_t result = (uintptr_t)dflt_val; + + while (map) { + uintptr_t r = (uintptr_t) tor_memeq(map->key, key, 32); + r -= 1; /* Now r is (uintptr_t)-1 if memeq returned false, and + * 0 if memeq returned true. */ + + result &= r; + result |= ((uintptr_t)(map->val)) & ~r; + + map = map->next; + } + + return (void *)result; +} + +/** + * Return true iff the <b>sz</b> bytes at <b>mem</b> are all zero. Runs in + * time independent of the contents of <b>mem</b>. + */ +int +safe_mem_is_zero(const void *mem, size_t sz) +{ + uint32_t total = 0; + const uint8_t *ptr = mem; + + while (sz--) { + total |= *ptr++; + } + + /*coverity[overflow]*/ + return 1 & ((total - 1) >> 8); +} + +/** Time-invariant 64-bit greater-than; works on two integers in the range + * (0,INT64_MAX). */ +#if SIZEOF_VOID_P == 8 +#define gt_i64_timei(a,b) ((a) > (b)) +#else +static inline int +gt_i64_timei(uint64_t a, uint64_t b) +{ + int64_t diff = (int64_t) (b - a); + int res = diff >> 63; + return res & 1; +} +#endif /* SIZEOF_VOID_P == 8 */ + +/** + * Given an array of list of <b>n_entries</b> uint64_t values, whose sum is + * <b>total</b>, find the first i such that the total of all elements 0...i is + * greater than rand_val. + * + * Try to perform this operation in a constant-time way. + */ +int +select_array_member_cumulative_timei(const uint64_t *entries, int n_entries, + uint64_t total, uint64_t rand_val) +{ + int i, i_chosen=-1, n_chosen=0; + uint64_t total_so_far = 0; + + for (i = 0; i < n_entries; ++i) { + total_so_far += entries[i]; + if (gt_i64_timei(total_so_far, rand_val)) { + i_chosen = i; + n_chosen++; + /* Set rand_val to INT64_MAX rather than stopping the loop. This way, + * the time we spend in the loop does not leak which element we chose. */ + rand_val = INT64_MAX; + } + } + raw_assert(total_so_far == total); + raw_assert(n_chosen == 1); + raw_assert(i_chosen >= 0); + raw_assert(i_chosen < n_entries); + + return i_chosen; +} diff --git a/src/lib/ctime/di_ops.h b/src/lib/ctime/di_ops.h new file mode 100644 index 0000000000..264b56a8c1 --- /dev/null +++ b/src/lib/ctime/di_ops.h @@ -0,0 +1,55 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file di_ops.h + * \brief Headers for di_ops.c + **/ + +#ifndef TOR_DI_OPS_H +#define TOR_DI_OPS_H + +#include "orconfig.h" +#include "lib/cc/torint.h" + +int tor_memcmp(const void *a, const void *b, size_t sz); +int tor_memeq(const void *a, const void *b, size_t sz); +#define tor_memneq(a,b,sz) (!tor_memeq((a),(b),(sz))) + +/** Alias for the platform's memcmp() function. This function is + * <em>not</em> data-independent: we define this alias so that we can + * mark cases where we are deliberately using a data-dependent memcmp() + * implementation. + */ +#define fast_memcmp(a,b,c) (memcmp((a),(b),(c))) +#define fast_memeq(a,b,c) (0==memcmp((a),(b),(c))) +#define fast_memneq(a,b,c) (0!=memcmp((a),(b),(c))) + +int safe_mem_is_zero(const void *mem, size_t sz); + +/** A type for a map from DIGEST256_LEN-byte blobs to void*, such that + * data lookups take an amount of time proportional only to the size + * of the map, and not to the position or presence of the item in the map. + * + * Not efficient for large maps! */ +typedef struct di_digest256_map_t di_digest256_map_t; +typedef void (*dimap_free_fn)(void *); + +void dimap_free_(di_digest256_map_t *map, dimap_free_fn free_fn); +#define dimap_free(map, free_fn) \ + do { \ + dimap_free_((map), (free_fn)); \ + (map) = NULL; \ + } while (0) +void dimap_add_entry(di_digest256_map_t **map, + const uint8_t *key, void *val); +void *dimap_search(const di_digest256_map_t *map, const uint8_t *key, + void *dflt_val); +int select_array_member_cumulative_timei(const uint64_t *entries, + int n_entries, + uint64_t total, uint64_t rand_val); + +#endif /* !defined(TOR_DI_OPS_H) */ + diff --git a/src/lib/ctime/include.am b/src/lib/ctime/include.am new file mode 100644 index 0000000000..b46c43ba0c --- /dev/null +++ b/src/lib/ctime/include.am @@ -0,0 +1,25 @@ + +noinst_LIBRARIES += src/lib/libtor-ctime.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-ctime-testing.a +endif + +if ADD_MULODI4 +mulodi4_source=src/ext/mulodi/mulodi4.c +else +mulodi4_source= +endif + +src_lib_libtor_ctime_a_SOURCES = \ + $(mulodi4_source) \ + src/ext/csiphash.c \ + src/lib/ctime/di_ops.c + +src_lib_libtor_ctime_testing_a_SOURCES = \ + $(src_lib_libtor_ctime_a_SOURCES) +src_lib_libtor_ctime_a_CFLAGS = @CFLAGS_CONSTTIME@ +src_lib_libtor_ctime_testing_a_CFLAGS = @CFLAGS_CONSTTIME@ $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/ctime/di_ops.h diff --git a/src/lib/defs/.may_include b/src/lib/defs/.may_include new file mode 100644 index 0000000000..2b06e8519c --- /dev/null +++ b/src/lib/defs/.may_include @@ -0,0 +1 @@ +orconfig.h diff --git a/src/lib/defs/dh_sizes.h b/src/lib/defs/dh_sizes.h new file mode 100644 index 0000000000..a2ffbc51c2 --- /dev/null +++ b/src/lib/defs/dh_sizes.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file dh_sizes.h + + * \brief Definitions for sizes of Diffie-Hellman groups elements in Z_p. + * + * Tor uses these definitions throughout its codebase, even in parts that + * don't actually do any Diffie-Hellman calculations. + **/ + +#ifndef TOR_DH_SIZES_H +#define TOR_DH_SIZES_H + +/** Length of our legacy DH keys. */ +#define DH1024_KEY_LEN (1024/8) + +#endif diff --git a/src/lib/defs/digest_sizes.h b/src/lib/defs/digest_sizes.h new file mode 100644 index 0000000000..525e5209d6 --- /dev/null +++ b/src/lib/defs/digest_sizes.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_DIGEST_SIZES_H +#define TOR_DIGEST_SIZES_H + +/** + * \file digest_sizes.h + * + * \brief Definitions for common sizes of cryptographic digests. + * + * Tor uses digests throughout its codebase, even in parts that don't actually + * calculate the digests. + **/ + +/** Length of the output of our message digest. */ +#define DIGEST_LEN 20 +/** Length of the output of our second (improved) message digests. (For now + * this is just sha256, but it could be any other 256-bit digest.) */ +#define DIGEST256_LEN 32 +/** Length of the output of our 64-bit optimized message digests (SHA512). */ +#define DIGEST512_LEN 64 + +#endif diff --git a/src/lib/defs/include.am b/src/lib/defs/include.am new file mode 100644 index 0000000000..48ee7f29fc --- /dev/null +++ b/src/lib/defs/include.am @@ -0,0 +1,5 @@ + +noinst_HEADERS += \ + src/lib/defs/dh_sizes.h \ + src/lib/defs/digest_sizes.h \ + src/lib/defs/x25519_sizes.h diff --git a/src/lib/defs/x25519_sizes.h b/src/lib/defs/x25519_sizes.h new file mode 100644 index 0000000000..8933a8866b --- /dev/null +++ b/src/lib/defs/x25519_sizes.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file x25519_sizes.h + + * \brief Definitions for sizes of x25519 keys and elements. + * + * Tor uses these definitions throughout its codebase, even in parts that + * don't actually do any x25519 calculations. + **/ + +#ifndef TOR_X25519_SIZES_H +#define TOR_X25519_SIZES_H + +/** Length of a curve25519 public key when encoded. */ +#define CURVE25519_PUBKEY_LEN 32 +/** Length of a curve25519 secret key when encoded. */ +#define CURVE25519_SECKEY_LEN 32 +/** Length of the result of a curve25519 handshake. */ +#define CURVE25519_OUTPUT_LEN 32 + +#define ED25519_PUBKEY_LEN 32 +#define ED25519_SECKEY_LEN 64 +#define ED25519_SECKEY_SEED_LEN 32 +#define ED25519_SIG_LEN 64 + +#define CURVE25519_BASE64_PADDED_LEN 44 + +#define ED25519_BASE64_LEN 43 +#define ED25519_SIG_BASE64_LEN 86 + +#endif diff --git a/src/lib/encoding/.may_include b/src/lib/encoding/.may_include new file mode 100644 index 0000000000..7c2ef36929 --- /dev/null +++ b/src/lib/encoding/.may_include @@ -0,0 +1,10 @@ +orconfig.h +lib/cc/*.h +lib/ctime/*.h +lib/encoding/*.h +lib/intmath/*.h +lib/log/*.h +lib/malloc/*.h +lib/string/*.h +lib/testsupport/*.h +lib/wallclock/*.h diff --git a/src/lib/encoding/binascii.c b/src/lib/encoding/binascii.c new file mode 100644 index 0000000000..bd063440d6 --- /dev/null +++ b/src/lib/encoding/binascii.c @@ -0,0 +1,520 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file binascii.c + * + * \brief Miscellaneous functions for encoding and decoding various things + * in base{16,32,64}. + */ + +#include "orconfig.h" + +#include "lib/encoding/binascii.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/cc/torint.h" +#include "lib/string/compat_ctype.h" +#include "lib/intmath/muldiv.h" +#include "lib/malloc/malloc.h" + +#include <stddef.h> +#include <string.h> +#include <stdlib.h> + +/** Return a pointer to a NUL-terminated hexadecimal string encoding + * the first <b>fromlen</b> bytes of <b>from</b>. (fromlen must be \<= 32.) The + * result does not need to be deallocated, but repeated calls to + * hex_str will trash old results. + */ +const char * +hex_str(const char *from, size_t fromlen) +{ + static char buf[65]; + if (fromlen>(sizeof(buf)-1)/2) + fromlen = (sizeof(buf)-1)/2; + base16_encode(buf,sizeof(buf),from,fromlen); + return buf; +} + +/* Return the base32 encoded size in bytes using the source length srclen. + * + * (WATCH OUT: This API counts the terminating NUL byte, but + * base64_encode_size does not.) + */ +size_t +base32_encoded_size(size_t srclen) +{ + size_t enclen; + tor_assert(srclen < SIZE_T_CEILING / 8); + enclen = BASE32_NOPAD_BUFSIZE(srclen); + tor_assert(enclen < INT_MAX && enclen > srclen); + return enclen; +} + +/** Implements base32 encoding as in RFC 4648. */ +void +base32_encode(char *dest, size_t destlen, const char *src, size_t srclen) +{ + unsigned int i, v, u; + size_t nbits = srclen * 8; + size_t bit; + + /* We need enough space for the encoded data and the extra NUL byte. */ + tor_assert(base32_encoded_size(srclen) <= destlen); + tor_assert(destlen < SIZE_T_CEILING); + + /* Make sure we leave no uninitialized data in the destination buffer. */ + memset(dest, 0, destlen); + + for (i=0,bit=0; bit < nbits; ++i, bit+=5) { + /* set v to the 16-bit value starting at src[bits/8], 0-padded. */ + size_t idx = bit / 8; + v = ((uint8_t)src[idx]) << 8; + if (idx+1 < srclen) + v += (uint8_t)src[idx+1]; + /* set u to the 5-bit value at the bit'th bit of buf. */ + u = (v >> (11-(bit%8))) & 0x1F; + dest[i] = BASE32_CHARS[u]; + } + dest[i] = '\0'; +} + +/** Implements base32 decoding as in RFC 4648. + * Returns 0 if successful, -1 otherwise. + */ +int +base32_decode(char *dest, size_t destlen, const char *src, size_t srclen) +{ + /* XXXX we might want to rewrite this along the lines of base64_decode, if + * it ever shows up in the profile. */ + unsigned int i; + size_t nbits, j, bit; + char *tmp; + nbits = ((srclen * 5) / 8) * 8; + + tor_assert(srclen < SIZE_T_CEILING / 5); + tor_assert((nbits/8) <= destlen); /* We need enough space. */ + tor_assert(destlen < SIZE_T_CEILING); + + /* Make sure we leave no uninitialized data in the destination buffer. */ + memset(dest, 0, destlen); + + /* Convert base32 encoded chars to the 5-bit values that they represent. */ + tmp = tor_malloc_zero(srclen); + for (j = 0; j < srclen; ++j) { + if (src[j] > 0x60 && src[j] < 0x7B) tmp[j] = src[j] - 0x61; + else if (src[j] > 0x31 && src[j] < 0x38) tmp[j] = src[j] - 0x18; + else if (src[j] > 0x40 && src[j] < 0x5B) tmp[j] = src[j] - 0x41; + else { + log_warn(LD_GENERAL, "illegal character in base32 encoded string"); + tor_free(tmp); + return -1; + } + } + + /* Assemble result byte-wise by applying five possible cases. */ + for (i = 0, bit = 0; bit < nbits; ++i, bit += 8) { + switch (bit % 40) { + case 0: + dest[i] = (((uint8_t)tmp[(bit/5)]) << 3) + + (((uint8_t)tmp[(bit/5)+1]) >> 2); + break; + case 8: + dest[i] = (((uint8_t)tmp[(bit/5)]) << 6) + + (((uint8_t)tmp[(bit/5)+1]) << 1) + + (((uint8_t)tmp[(bit/5)+2]) >> 4); + break; + case 16: + dest[i] = (((uint8_t)tmp[(bit/5)]) << 4) + + (((uint8_t)tmp[(bit/5)+1]) >> 1); + break; + case 24: + dest[i] = (((uint8_t)tmp[(bit/5)]) << 7) + + (((uint8_t)tmp[(bit/5)+1]) << 2) + + (((uint8_t)tmp[(bit/5)+2]) >> 3); + break; + case 32: + dest[i] = (((uint8_t)tmp[(bit/5)]) << 5) + + ((uint8_t)tmp[(bit/5)+1]); + break; + } + } + + memset(tmp, 0, srclen); /* on the heap, this should be safe */ + tor_free(tmp); + tmp = NULL; + return 0; +} + +#define BASE64_OPENSSL_LINELEN 64 + +/** Return the Base64 encoded size of <b>srclen</b> bytes of data in + * bytes. + * + * (WATCH OUT: This API <em>does not</em> count the terminating NUL byte, + * but base32_encoded_size does.) + * + * If <b>flags</b>&BASE64_ENCODE_MULTILINE is true, return the size + * of the encoded output as multiline output (64 character, `\n' terminated + * lines). + */ +size_t +base64_encode_size(size_t srclen, int flags) +{ + size_t enclen; + + /* Use INT_MAX for overflow checking because base64_encode() returns int. */ + tor_assert(srclen < INT_MAX); + tor_assert(CEIL_DIV(srclen, 3) < INT_MAX / 4); + + enclen = BASE64_LEN(srclen); + if (flags & BASE64_ENCODE_MULTILINE) + enclen += CEIL_DIV(enclen, BASE64_OPENSSL_LINELEN); + + tor_assert(enclen < INT_MAX && (enclen == 0 || enclen > srclen)); + return enclen; +} + +/** Internal table mapping 6 bit values to the Base64 alphabet. */ +static const char base64_encode_table[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' +}; + +/** Base64 encode <b>srclen</b> bytes of data from <b>src</b>. Write + * the result into <b>dest</b>, if it will fit within <b>destlen</b> + * bytes. Return the number of bytes written on success; -1 if + * destlen is too short, or other failure. + * + * If <b>flags</b>&BASE64_ENCODE_MULTILINE is true, return encoded + * output in multiline format (64 character, `\n' terminated lines). + */ +int +base64_encode(char *dest, size_t destlen, const char *src, size_t srclen, + int flags) +{ + const unsigned char *usrc = (unsigned char *)src; + const unsigned char *eous = usrc + srclen; + char *d = dest; + uint32_t n = 0; + size_t linelen = 0; + size_t enclen; + int n_idx = 0; + + if (!src || !dest) + return -1; + + /* Ensure that there is sufficient space, including the NUL. */ + enclen = base64_encode_size(srclen, flags); + if (destlen < enclen + 1) + return -1; + if (destlen > SIZE_T_CEILING) + return -1; + if (enclen > INT_MAX) + return -1; + + /* Make sure we leave no uninitialized data in the destination buffer. */ + memset(dest, 0, destlen); + + /* XXX/Yawning: If this ends up being too slow, this can be sped up + * by separating the multiline format case and the normal case, and + * processing 48 bytes of input at a time when newlines are desired. + */ +#define ENCODE_CHAR(ch) \ + STMT_BEGIN \ + *d++ = ch; \ + if (flags & BASE64_ENCODE_MULTILINE) { \ + if (++linelen % BASE64_OPENSSL_LINELEN == 0) { \ + linelen = 0; \ + *d++ = '\n'; \ + } \ + } \ + STMT_END + +#define ENCODE_N(idx) \ + ENCODE_CHAR(base64_encode_table[(n >> ((3 - idx) * 6)) & 0x3f]) + +#define ENCODE_PAD() ENCODE_CHAR('=') + + /* Iterate over all the bytes in src. Each one will add 8 bits to the + * value we're encoding. Accumulate bits in <b>n</b>, and whenever we + * have 24 bits, batch them into 4 bytes and flush those bytes to dest. + */ + for ( ; usrc < eous; ++usrc) { + n = (n << 8) | *usrc; + if ((++n_idx) == 3) { + ENCODE_N(0); + ENCODE_N(1); + ENCODE_N(2); + ENCODE_N(3); + n_idx = 0; + n = 0; + } + } + switch (n_idx) { + case 0: + /* 0 leftover bits, no pading to add. */ + break; + case 1: + /* 8 leftover bits, pad to 12 bits, write the 2 6-bit values followed + * by 2 padding characters. + */ + n <<= 4; + ENCODE_N(2); + ENCODE_N(3); + ENCODE_PAD(); + ENCODE_PAD(); + break; + case 2: + /* 16 leftover bits, pad to 18 bits, write the 3 6-bit values followed + * by 1 padding character. + */ + n <<= 2; + ENCODE_N(1); + ENCODE_N(2); + ENCODE_N(3); + ENCODE_PAD(); + break; + // LCOV_EXCL_START -- we can't reach this point, because we enforce + // 0 <= ncov_idx < 3 in the loop above. + default: + /* Something went catastrophically wrong. */ + tor_fragile_assert(); + return -1; + // LCOV_EXCL_STOP + } + +#undef ENCODE_N +#undef ENCODE_PAD +#undef ENCODE_CHAR + + /* Multiline output always includes at least one newline. */ + if (flags & BASE64_ENCODE_MULTILINE && linelen != 0) + *d++ = '\n'; + + tor_assert(d - dest == (ptrdiff_t)enclen); + + *d++ = '\0'; /* NUL terminate the output. */ + + return (int) enclen; +} + +/** As base64_encode, but do not add any internal spaces or external padding + * to the output stream. */ +int +base64_encode_nopad(char *dest, size_t destlen, + const uint8_t *src, size_t srclen) +{ + int n = base64_encode(dest, destlen, (const char*) src, srclen, 0); + if (n <= 0) + return n; + tor_assert((size_t)n < destlen && dest[n] == 0); + char *in, *out; + in = out = dest; + while (*in) { + if (*in == '=' || *in == '\n') { + ++in; + } else { + *out++ = *in++; + } + } + *out = 0; + + tor_assert(out - dest <= INT_MAX); + + return (int)(out - dest); +} + +#undef BASE64_OPENSSL_LINELEN + +/** @{ */ +/** Special values used for the base64_decode_table */ +#define X 255 +#define SP 64 +#define PAD 65 +/** @} */ +/** Internal table mapping byte values to what they represent in base64. + * Numbers 0..63 are 6-bit integers. SPs are spaces, and should be + * skipped. Xs are invalid and must not appear in base64. PAD indicates + * end-of-string. */ +static const uint8_t base64_decode_table[256] = { + X, X, X, X, X, X, X, X, X, SP, SP, SP, X, SP, X, X, /* */ + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + SP, X, X, X, X, X, X, X, X, X, X, 62, X, X, X, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, X, X, X, PAD, X, X, + X, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, X, X, X, X, X, + X, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, +}; + +/** Base64 decode <b>srclen</b> bytes of data from <b>src</b>. Write + * the result into <b>dest</b>, if it will fit within <b>destlen</b> + * bytes. Return the number of bytes written on success; -1 if + * destlen is too short, or other failure. + * + * NOTE 1: destlen is checked conservatively, as though srclen contained no + * spaces or padding. + * + * NOTE 2: This implementation does not check for the correct number of + * padding "=" characters at the end of the string, and does not check + * for internal padding characters. + */ +int +base64_decode(char *dest, size_t destlen, const char *src, size_t srclen) +{ + const char *eos = src+srclen; + uint32_t n=0; + int n_idx=0; + size_t di = 0; + + if (destlen > INT_MAX) + return -1; + + /* Make sure we leave no uninitialized data in the destination buffer. */ + memset(dest, 0, destlen); + + /* Iterate over all the bytes in src. Each one will add 0 or 6 bits to the + * value we're decoding. Accumulate bits in <b>n</b>, and whenever we have + * 24 bits, batch them into 3 bytes and flush those bytes to dest. + */ + for ( ; src < eos; ++src) { + unsigned char c = (unsigned char) *src; + uint8_t v = base64_decode_table[c]; + switch (v) { + case X: + /* This character isn't allowed in base64. */ + return -1; + case SP: + /* This character is whitespace, and has no effect. */ + continue; + case PAD: + /* We've hit an = character: the data is over. */ + goto end_of_loop; + default: + /* We have an actual 6-bit value. Append it to the bits in n. */ + n = (n<<6) | v; + if ((++n_idx) == 4) { + /* We've accumulated 24 bits in n. Flush them. */ + if (destlen < 3 || di > destlen - 3) + return -1; + dest[di++] = (n>>16); + dest[di++] = (n>>8) & 0xff; + dest[di++] = (n) & 0xff; + n_idx = 0; + n = 0; + } + } + } + end_of_loop: + /* If we have leftover bits, we need to cope. */ + switch (n_idx) { + case 0: + default: + /* No leftover bits. We win. */ + break; + case 1: + /* 6 leftover bits. That's invalid; we can't form a byte out of that. */ + return -1; + case 2: + /* 12 leftover bits: The last 4 are padding and the first 8 are data. */ + if (destlen < 1 || di > destlen - 1) + return -1; + dest[di++] = n >> 4; + break; + case 3: + /* 18 leftover bits: The last 2 are padding and the first 16 are data. */ + if (destlen < 2 || di > destlen - 2) + return -1; + dest[di++] = n >> 10; + dest[di++] = n >> 2; + } + + tor_assert(di <= destlen); + + return (int)di; +} +#undef X +#undef SP +#undef PAD + +/** Encode the <b>srclen</b> bytes at <b>src</b> in a NUL-terminated, + * uppercase hexadecimal string; store it in the <b>destlen</b>-byte buffer + * <b>dest</b>. + */ +void +base16_encode(char *dest, size_t destlen, const char *src, size_t srclen) +{ + const char *end; + char *cp; + + tor_assert(srclen < SIZE_T_CEILING / 2 - 1); + tor_assert(destlen >= BASE16_BUFSIZE(srclen)); + tor_assert(destlen < SIZE_T_CEILING); + + /* Make sure we leave no uninitialized data in the destination buffer. */ + memset(dest, 0, destlen); + + cp = dest; + end = src+srclen; + while (src<end) { + *cp++ = "0123456789ABCDEF"[ (*(const uint8_t*)src) >> 4 ]; + *cp++ = "0123456789ABCDEF"[ (*(const uint8_t*)src) & 0xf ]; + ++src; + } + *cp = '\0'; +} + +/** Given a hexadecimal string of <b>srclen</b> bytes in <b>src</b>, decode + * it and store the result in the <b>destlen</b>-byte buffer at <b>dest</b>. + * Return the number of bytes decoded on success, -1 on failure. If + * <b>destlen</b> is greater than INT_MAX or less than half of + * <b>srclen</b>, -1 is returned. */ +int +base16_decode(char *dest, size_t destlen, const char *src, size_t srclen) +{ + const char *end; + char *dest_orig = dest; + int v1,v2; + + if ((srclen % 2) != 0) + return -1; + if (destlen < srclen/2 || destlen > INT_MAX) + return -1; + + /* Make sure we leave no uninitialized data in the destination buffer. */ + memset(dest, 0, destlen); + + end = src+srclen; + while (src<end) { + v1 = hex_decode_digit(*src); + v2 = hex_decode_digit(*(src+1)); + if (v1<0||v2<0) + return -1; + *(uint8_t*)dest = (v1<<4)|v2; + ++dest; + src+=2; + } + + tor_assert((dest-dest_orig) <= (ptrdiff_t) destlen); + + return (int) (dest-dest_orig); +} diff --git a/src/lib/encoding/binascii.h b/src/lib/encoding/binascii.h new file mode 100644 index 0000000000..7e3cc04f09 --- /dev/null +++ b/src/lib/encoding/binascii.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file binascii.h + * + * \brief Header for binascii.c + **/ + +#ifndef TOR_BINASCII_H +#define TOR_BINASCII_H + +#include "orconfig.h" +#include <stddef.h> +#include "lib/testsupport/testsupport.h" +#include "lib/cc/torint.h" + +const char *hex_str(const char *from, size_t fromlen); + +/** @{ */ +/** These macros don't check for overflow. Use them only for constant inputs + * (like array declarations). The *_LEN macros are the raw encoding lengths + * (without terminating NUL), while the *_BUFSIZE macros count the terminating + * NUL. */ +#define BASE64_LEN(n) (CEIL_DIV((n), 3) * 4) +#define BASE32_LEN(n) (CEIL_DIV((n), 5) * 8) +#define BASE16_LEN(n) ((n) * 2) + +#define BASE64_BUFSIZE(n) (BASE64_LEN(n) + 1) +#define BASE32_BUFSIZE(n) (BASE32_LEN(n) + 1) +#define BASE16_BUFSIZE(n) (BASE16_LEN(n) + 1) + +#define BASE64_NOPAD_LEN(n) (CEIL_DIV((n) * 4, 3)) +#define BASE32_NOPAD_LEN(n) (CEIL_DIV((n) * 8, 5)) + +#define BASE64_NOPAD_BUFSIZE(n) (BASE64_NOPAD_LEN(n) + 1) +#define BASE32_NOPAD_BUFSIZE(n) (BASE32_NOPAD_LEN(n) + 1) +/** @} */ + +#define BASE64_ENCODE_MULTILINE 1 +size_t base64_encode_size(size_t srclen, int flags); +int base64_encode(char *dest, size_t destlen, const char *src, size_t srclen, + int flags); +int base64_decode(char *dest, size_t destlen, const char *src, size_t srclen); +int base64_encode_nopad(char *dest, size_t destlen, + const uint8_t *src, size_t srclen); + +/** Characters that can appear (case-insensitively) in a base32 encoding. */ +#define BASE32_CHARS "abcdefghijklmnopqrstuvwxyz234567" +void base32_encode(char *dest, size_t destlen, const char *src, size_t srclen); +int base32_decode(char *dest, size_t destlen, const char *src, size_t srclen); +size_t base32_encoded_size(size_t srclen); + +void base16_encode(char *dest, size_t destlen, const char *src, size_t srclen); +int base16_decode(char *dest, size_t destlen, const char *src, size_t srclen); + +#endif /* !defined(TOR_UTIL_FORMAT_H) */ diff --git a/src/lib/encoding/confline.c b/src/lib/encoding/confline.c new file mode 100644 index 0000000000..8110f3dd9c --- /dev/null +++ b/src/lib/encoding/confline.c @@ -0,0 +1,402 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file confline.c + * + * \brief Functions to manipulate a linked list of key-value pairs, of the + * type used in Tor's configuration files. + * + * Tor uses the config_line_t type and its associated serialized format for + * human-readable key-value pairs in many places, including its configuration, + * its state files, its consensus cache, and so on. + **/ + +#include "lib/encoding/confline.h" +#include "lib/encoding/cstring.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/string/compat_ctype.h" +#include "lib/string/compat_string.h" +#include "lib/string/util_string.h" + +#include <string.h> + +/** Helper: allocate a new configuration option mapping 'key' to 'val', + * append it to *<b>lst</b>. */ +void +config_line_append(config_line_t **lst, + const char *key, + const char *val) +{ + tor_assert(lst); + + config_line_t *newline; + + newline = tor_malloc_zero(sizeof(config_line_t)); + newline->key = tor_strdup(key); + newline->value = tor_strdup(val); + newline->next = NULL; + while (*lst) + lst = &((*lst)->next); + + (*lst) = newline; +} + +/** Helper: allocate a new configuration option mapping 'key' to 'val', + * and prepend it to *<b>lst</b> */ +void +config_line_prepend(config_line_t **lst, + const char *key, + const char *val) +{ + tor_assert(lst); + + config_line_t *newline; + + newline = tor_malloc_zero(sizeof(config_line_t)); + newline->key = tor_strdup(key); + newline->value = tor_strdup(val); + newline->next = *lst; + *lst = newline; +} + +/** Return the first line in <b>lines</b> whose key is exactly <b>key</b>, or + * NULL if no such key exists. + * + * (In options parsing, this is for handling commandline-only options only; + * other options should be looked up in the appropriate data structure.) */ +const config_line_t * +config_line_find(const config_line_t *lines, + const char *key) +{ + const config_line_t *cl; + for (cl = lines; cl; cl = cl->next) { + if (!strcmp(cl->key, key)) + return cl; + } + return NULL; +} + +/** Auxiliary function that does all the work of config_get_lines. + * <b>recursion_level</b> is the count of how many nested %includes we have. + * <b>opened_lst</b> will have a list of opened files if provided. + * Returns the a pointer to the last element of the <b>result</b> in + * <b>last</b>. */ +int +config_get_lines_aux(const char *string, config_line_t **result, int extended, + int allow_include, int *has_include, + struct smartlist_t *opened_lst, int recursion_level, + config_line_t **last, + include_handler_fn handle_include) +{ + config_line_t *list = NULL, **next, *list_last = NULL; + char *k, *v; + const char *parse_err; + int include_used = 0; + + if (recursion_level > MAX_INCLUDE_RECURSION_LEVEL) { + log_warn(LD_CONFIG, "Error while parsing configuration: more than %d " + "nested %%includes.", MAX_INCLUDE_RECURSION_LEVEL); + return -1; + } + + next = &list; + do { + k = v = NULL; + string = parse_config_line_from_str_verbose(string, &k, &v, &parse_err); + if (!string) { + log_warn(LD_CONFIG, "Error while parsing configuration: %s", + parse_err?parse_err:"<unknown>"); + config_free_lines(list); + tor_free(k); + tor_free(v); + return -1; + } + if (k && v) { + unsigned command = CONFIG_LINE_NORMAL; + if (extended) { + if (k[0] == '+') { + char *k_new = tor_strdup(k+1); + tor_free(k); + k = k_new; + command = CONFIG_LINE_APPEND; + } else if (k[0] == '/') { + char *k_new = tor_strdup(k+1); + tor_free(k); + k = k_new; + tor_free(v); + v = tor_strdup(""); + command = CONFIG_LINE_CLEAR; + } + } + + if (allow_include && !strcmp(k, "%include") && handle_include) { + tor_free(k); + include_used = 1; + + config_line_t *include_list; + if (handle_include(v, recursion_level, extended, &include_list, + &list_last, opened_lst) < 0) { + log_warn(LD_CONFIG, "Error reading included configuration " + "file or directory: \"%s\".", v); + config_free_lines(list); + tor_free(v); + return -1; + } + log_notice(LD_CONFIG, "Included configuration file or " + "directory at recursion level %d: \"%s\".", + recursion_level, v); + *next = include_list; + if (list_last) + next = &list_last->next; + tor_free(v); + } else { + /* This list can get long, so we keep a pointer to the end of it + * rather than using config_line_append over and over and getting + * n^2 performance. */ + *next = tor_malloc_zero(sizeof(**next)); + (*next)->key = k; + (*next)->value = v; + (*next)->next = NULL; + (*next)->command = command; + list_last = *next; + next = &((*next)->next); + } + } else { + tor_free(k); + tor_free(v); + } + } while (*string); + + if (last) { + *last = list_last; + } + if (has_include) { + *has_include = include_used; + } + *result = list; + return 0; +} + +/** Same as config_get_lines_include but does not allow %include */ +int +config_get_lines(const char *string, config_line_t **result, int extended) +{ + return config_get_lines_aux(string, result, extended, 0, NULL, NULL, 1, + NULL, NULL); +} + +/** + * Free all the configuration lines on the linked list <b>front</b>. + */ +void +config_free_lines_(config_line_t *front) +{ + config_line_t *tmp; + + while (front) { + tmp = front; + front = tmp->next; + + tor_free(tmp->key); + tor_free(tmp->value); + tor_free(tmp); + } +} + +/** Return a newly allocated deep copy of the lines in <b>inp</b>. */ +config_line_t * +config_lines_dup(const config_line_t *inp) +{ + return config_lines_dup_and_filter(inp, NULL); +} + +/** Return a newly allocated deep copy of the lines in <b>inp</b>, + * but only the ones whose keys begin with <b>key</b> (case-insensitive). + * If <b>key</b> is NULL, do not filter. */ +config_line_t * +config_lines_dup_and_filter(const config_line_t *inp, + const char *key) +{ + config_line_t *result = NULL; + config_line_t **next_out = &result; + while (inp) { + if (key && strcasecmpstart(inp->key, key)) { + inp = inp->next; + continue; + } + *next_out = tor_malloc_zero(sizeof(config_line_t)); + (*next_out)->key = tor_strdup(inp->key); + (*next_out)->value = tor_strdup(inp->value); + inp = inp->next; + next_out = &((*next_out)->next); + } + (*next_out) = NULL; + return result; +} + +/** Return true iff a and b contain identical keys and values in identical + * order. */ +int +config_lines_eq(config_line_t *a, config_line_t *b) +{ + while (a && b) { + if (strcasecmp(a->key, b->key) || strcmp(a->value, b->value)) + return 0; + a = a->next; + b = b->next; + } + if (a || b) + return 0; + return 1; +} + +/** Return the number of lines in <b>a</b> whose key is <b>key</b>. */ +int +config_count_key(const config_line_t *a, const char *key) +{ + int n = 0; + while (a) { + if (!strcasecmp(a->key, key)) { + ++n; + } + a = a->next; + } + return n; +} + +/** Given a string containing part of a configuration file or similar format, + * advance past comments and whitespace and try to parse a single line. If we + * parse a line successfully, set *<b>key_out</b> to a new string holding the + * key portion and *<b>value_out</b> to a new string holding the value portion + * of the line, and return a pointer to the start of the next line. If we run + * out of data, return a pointer to the end of the string. If we encounter an + * error, return NULL and set *<b>err_out</b> (if provided) to an error + * message. + */ +const char * +parse_config_line_from_str_verbose(const char *line, char **key_out, + char **value_out, + const char **err_out) +{ + /* + See torrc_format.txt for a description of the (silly) format this parses. + */ + const char *key, *val, *cp; + int continuation = 0; + + tor_assert(key_out); + tor_assert(value_out); + + *key_out = *value_out = NULL; + key = val = NULL; + /* Skip until the first keyword. */ + while (1) { + while (TOR_ISSPACE(*line)) + ++line; + if (*line == '#') { + while (*line && *line != '\n') + ++line; + } else { + break; + } + } + + if (!*line) { /* End of string? */ + *key_out = *value_out = NULL; + return line; + } + + /* Skip until the next space or \ followed by newline. */ + key = line; + while (*line && !TOR_ISSPACE(*line) && *line != '#' && + ! (line[0] == '\\' && line[1] == '\n')) + ++line; + *key_out = tor_strndup(key, line-key); + + /* Skip until the value. */ + while (*line == ' ' || *line == '\t') + ++line; + + val = line; + + /* Find the end of the line. */ + if (*line == '\"') { // XXX No continuation handling is done here + if (!(line = unescape_string(line, value_out, NULL))) { + if (err_out) + *err_out = "Invalid escape sequence in quoted string"; + return NULL; + } + while (*line == ' ' || *line == '\t') + ++line; + if (*line == '\r' && *(++line) == '\n') + ++line; + if (*line && *line != '#' && *line != '\n') { + if (err_out) + *err_out = "Excess data after quoted string"; + return NULL; + } + } else { + /* Look for the end of the line. */ + while (*line && *line != '\n' && (*line != '#' || continuation)) { + if (*line == '\\' && line[1] == '\n') { + continuation = 1; + line += 2; + } else if (*line == '#') { + do { + ++line; + } while (*line && *line != '\n'); + if (*line == '\n') + ++line; + } else { + ++line; + } + } + + if (*line == '\n') { + cp = line++; + } else { + cp = line; + } + /* Now back cp up to be the last nonspace character */ + while (cp>val && TOR_ISSPACE(*(cp-1))) + --cp; + + tor_assert(cp >= val); + + /* Now copy out and decode the value. */ + *value_out = tor_strndup(val, cp-val); + if (continuation) { + char *v_out, *v_in; + v_out = v_in = *value_out; + while (*v_in) { + if (*v_in == '#') { + do { + ++v_in; + } while (*v_in && *v_in != '\n'); + if (*v_in == '\n') + ++v_in; + } else if (v_in[0] == '\\' && v_in[1] == '\n') { + v_in += 2; + } else { + *v_out++ = *v_in++; + } + } + *v_out = '\0'; + } + } + + if (*line == '#') { + do { + ++line; + } while (*line && *line != '\n'); + } + while (TOR_ISSPACE(*line)) ++line; + + return line; +} diff --git a/src/lib/encoding/confline.h b/src/lib/encoding/confline.h new file mode 100644 index 0000000000..3d9ae8a662 --- /dev/null +++ b/src/lib/encoding/confline.h @@ -0,0 +1,78 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file confline.h + * + * \brief Header for confline.c + **/ + +#ifndef TOR_CONFLINE_H +#define TOR_CONFLINE_H + +struct smartlist_t; + +/** Ordinary configuration line. */ +#define CONFIG_LINE_NORMAL 0 +/** Appends to previous configuration for the same option, even if we + * would ordinary replace it. */ +#define CONFIG_LINE_APPEND 1 +/* Removes all previous configuration for an option. */ +#define CONFIG_LINE_CLEAR 2 + +#define MAX_INCLUDE_RECURSION_LEVEL 31 + +/** A linked list of lines in a config file, or elsewhere */ +typedef struct config_line_t { + char *key; + char *value; + struct config_line_t *next; + + /** What special treatment (if any) does this line require? */ + unsigned int command:2; + /** If true, subsequent assignments to this linelist should replace + * it, not extend it. Set only on the first item in a linelist in an + * or_options_t. */ + unsigned int fragile:1; +} config_line_t; + +void config_line_append(config_line_t **lst, + const char *key, const char *val); +void config_line_prepend(config_line_t **lst, + const char *key, const char *val); +config_line_t *config_lines_dup(const config_line_t *inp); +config_line_t *config_lines_dup_and_filter(const config_line_t *inp, + const char *key); +const config_line_t *config_line_find(const config_line_t *lines, + const char *key); +int config_lines_eq(config_line_t *a, config_line_t *b); +int config_count_key(const config_line_t *a, const char *key); +void config_free_lines_(config_line_t *front); +#define config_free_lines(front) \ + do { \ + config_free_lines_(front); \ + (front) = NULL; \ + } while (0) +const char *parse_config_line_from_str_verbose(const char *line, + char **key_out, char **value_out, + const char **err_out); + +int config_get_lines(const char *string, struct config_line_t **result, + int extended); + +typedef int (*include_handler_fn)(const char *, int, int, + struct config_line_t **, + struct config_line_t **, + struct smartlist_t *); + +int config_get_lines_aux(const char *string, struct config_line_t **result, + int extended, + int allow_include, int *has_include, + struct smartlist_t *opened_lst, int recursion_level, + config_line_t **last, + include_handler_fn handle_include); + +#endif /* !defined(TOR_CONFLINE_H) */ diff --git a/src/lib/encoding/cstring.c b/src/lib/encoding/cstring.c new file mode 100644 index 0000000000..29d3714126 --- /dev/null +++ b/src/lib/encoding/cstring.c @@ -0,0 +1,138 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file cstring.c + * + * \brief Decode data that has been written as a C literal. + **/ + +#include "lib/encoding/cstring.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/string/compat_ctype.h" + +#include <string.h> + +#define TOR_ISODIGIT(c) ('0' <= (c) && (c) <= '7') + +/** Given a c-style double-quoted escaped string in <b>s</b>, extract and + * decode its contents into a newly allocated string. On success, assign this + * string to *<b>result</b>, assign its length to <b>size_out</b> (if + * provided), and return a pointer to the position in <b>s</b> immediately + * after the string. On failure, return NULL. + */ +const char * +unescape_string(const char *s, char **result, size_t *size_out) +{ + const char *cp; + char *out; + if (s[0] != '\"') + return NULL; + cp = s+1; + while (1) { + switch (*cp) { + case '\0': + case '\n': + return NULL; + case '\"': + goto end_of_loop; + case '\\': + if (cp[1] == 'x' || cp[1] == 'X') { + if (!(TOR_ISXDIGIT(cp[2]) && TOR_ISXDIGIT(cp[3]))) + return NULL; + cp += 4; + } else if (TOR_ISODIGIT(cp[1])) { + cp += 2; + if (TOR_ISODIGIT(*cp)) ++cp; + if (TOR_ISODIGIT(*cp)) ++cp; + } else if (cp[1] == 'n' || cp[1] == 'r' || cp[1] == 't' || cp[1] == '"' + || cp[1] == '\\' || cp[1] == '\'') { + cp += 2; + } else { + return NULL; + } + break; + default: + ++cp; + break; + } + } + end_of_loop: + out = *result = tor_malloc(cp-s + 1); + cp = s+1; + while (1) { + switch (*cp) + { + case '\"': + *out = '\0'; + if (size_out) *size_out = out - *result; + return cp+1; + + /* LCOV_EXCL_START -- we caught this in parse_config_from_line. */ + case '\0': + tor_fragile_assert(); + tor_free(*result); + return NULL; + /* LCOV_EXCL_STOP */ + case '\\': + switch (cp[1]) + { + case 'n': *out++ = '\n'; cp += 2; break; + case 'r': *out++ = '\r'; cp += 2; break; + case 't': *out++ = '\t'; cp += 2; break; + case 'x': case 'X': + { + int x1, x2; + + x1 = hex_decode_digit(cp[2]); + x2 = hex_decode_digit(cp[3]); + if (x1 == -1 || x2 == -1) { + /* LCOV_EXCL_START */ + /* we caught this above in the initial loop. */ + tor_assert_nonfatal_unreached(); + tor_free(*result); + return NULL; + /* LCOV_EXCL_STOP */ + } + + *out++ = ((x1<<4) + x2); + cp += 4; + } + break; + case '0': case '1': case '2': case '3': case '4': case '5': + case '6': case '7': + { + int n = cp[1]-'0'; + cp += 2; + if (TOR_ISODIGIT(*cp)) { n = n*8 + *cp-'0'; cp++; } + if (TOR_ISODIGIT(*cp)) { n = n*8 + *cp-'0'; cp++; } + if (n > 255) { tor_free(*result); return NULL; } + *out++ = (char)n; + } + break; + case '\'': + case '\"': + case '\\': + case '\?': + *out++ = cp[1]; + cp += 2; + break; + + /* LCOV_EXCL_START */ + default: + /* we caught this above in the initial loop. */ + tor_assert_nonfatal_unreached(); + tor_free(*result); return NULL; + /* LCOV_EXCL_STOP */ + } + break; + default: + *out++ = *cp++; + } + } +} diff --git a/src/lib/encoding/cstring.h b/src/lib/encoding/cstring.h new file mode 100644 index 0000000000..904a2c9c1c --- /dev/null +++ b/src/lib/encoding/cstring.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file cstring.h + * + * \brief Header for cstring.c + **/ + +#ifndef TOR_CSTRING_H +#define TOR_CSTRING_H + +#include <stddef.h> +const char *unescape_string(const char *s, char **result, size_t *size_out); + +#endif /* !defined(TOR_CSTRING_H) */ diff --git a/src/lib/encoding/include.am b/src/lib/encoding/include.am new file mode 100644 index 0000000000..2d2aa3988a --- /dev/null +++ b/src/lib/encoding/include.am @@ -0,0 +1,26 @@ +noinst_LIBRARIES += src/lib/libtor-encoding.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-encoding-testing.a +endif + +src_lib_libtor_encoding_a_SOURCES = \ + src/lib/encoding/binascii.c \ + src/lib/encoding/confline.c \ + src/lib/encoding/cstring.c \ + src/lib/encoding/keyval.c \ + src/lib/encoding/pem.c \ + src/lib/encoding/time_fmt.c + +src_lib_libtor_encoding_testing_a_SOURCES = \ + $(src_lib_libtor_encoding_a_SOURCES) +src_lib_libtor_encoding_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_encoding_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/encoding/binascii.h \ + src/lib/encoding/confline.h \ + src/lib/encoding/cstring.h \ + src/lib/encoding/keyval.h \ + src/lib/encoding/pem.h \ + src/lib/encoding/time_fmt.h diff --git a/src/lib/encoding/keyval.c b/src/lib/encoding/keyval.c new file mode 100644 index 0000000000..c5da5a0bfc --- /dev/null +++ b/src/lib/encoding/keyval.c @@ -0,0 +1,52 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file keyval.c + * + * \brief Handle data encoded as a key=value pair. + **/ + +#include "orconfig.h" +#include "lib/encoding/keyval.h" +#include "lib/log/escape.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +#include <stdlib.h> +#include <string.h> + +/** Return true if <b>string</b> is a valid 'key=[value]' string. + * "value" is optional, to indicate the empty string. Log at logging + * <b>severity</b> if something ugly happens. */ +int +string_is_key_value(int severity, const char *string) +{ + /* position of equal sign in string */ + const char *equal_sign_pos = NULL; + + tor_assert(string); + + if (strlen(string) < 2) { /* "x=" is shortest args string */ + tor_log(severity, LD_GENERAL, "'%s' is too short to be a k=v value.", + escaped(string)); + return 0; + } + + equal_sign_pos = strchr(string, '='); + if (!equal_sign_pos) { + tor_log(severity, LD_GENERAL, "'%s' is not a k=v value.", escaped(string)); + return 0; + } + + /* validate that the '=' is not in the beginning of the string. */ + if (equal_sign_pos == string) { + tor_log(severity, LD_GENERAL, "'%s' is not a valid k=v value.", + escaped(string)); + return 0; + } + + return 1; +} diff --git a/src/lib/encoding/keyval.h b/src/lib/encoding/keyval.h new file mode 100644 index 0000000000..cd327b7a82 --- /dev/null +++ b/src/lib/encoding/keyval.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file keyval.h + * + * \brief Header for keyval.c + **/ + +#ifndef TOR_KEYVAL_H +#define TOR_KEYVAL_H + +int string_is_key_value(int severity, const char *string); + +#endif diff --git a/src/lib/encoding/pem.c b/src/lib/encoding/pem.c new file mode 100644 index 0000000000..24b238b130 --- /dev/null +++ b/src/lib/encoding/pem.c @@ -0,0 +1,106 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file pem.c + * + * \brief Implement a trivial version of PEM encoding, for use with NSS. + * + * We deliberately do not support any encryption here. + **/ + +#include "orconfig.h" + +#include "lib/encoding/pem.h" + +#include "lib/ctime/di_ops.h" +#include "lib/encoding/binascii.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/string/printf.h" +#include "lib/string/util_string.h" + +#include <string.h> + +/** + * Return the length of a <b>src_len</b>-byte object when tagged with + * <b>objtype</b> and PEM-encoded. Includes terminating NUL. + */ +size_t +pem_encoded_size(size_t src_len, const char *objtype) +{ + return + strlen("-----BEGIN -----\n") + + strlen("-----END -----\n") + + strlen(objtype) * 2 + + base64_encode_size(src_len, BASE64_ENCODE_MULTILINE) + + 1; +} + +/** + * PEM-encode the <b>srclen</b>-byte object at <b>src</b> into the + * <b>destlen<\b>-byte buffer at <b>dest</b>, tagging it with <b>objtype</b>. + * Return 0 on success and -1 on failure. + */ +int +pem_encode(char *dest, size_t destlen, const uint8_t *src, size_t srclen, + const char *objtype) +{ + if (tor_snprintf(dest, destlen, "-----BEGIN %s-----\n", objtype) < 0) + return -1; + + size_t offset = strlen(dest); + + int n = base64_encode(dest + offset, destlen - offset, + (const char *)src, srclen, BASE64_ENCODE_MULTILINE); + if (n < 0) + return -1; + offset += n; + if (BUG(offset > destlen)) + return -1; + + if (tor_snprintf(dest + offset, destlen - offset, + "-----END %s-----\n", objtype) < 0) + return -1; + + tor_assert(strlen(dest) + 1 <= pem_encoded_size(srclen, objtype)); + return 0; +} + +/** + * Given a PEM-encoded block of size <b>srclen</b> in <b>src</b>, if it has + * object type <b>objtype</b>, decode it into the <b>destlen</b>-byte buffer + * at <b>dest</b>. Return the number of characters decoded on success, or -1 + * on failure. + */ +int +pem_decode(uint8_t *dest, size_t destlen, const char *src, size_t srclen, + const char *objtype) +{ + const char *eos = src + srclen; + + src = eat_whitespace_eos(src, eos); + + char *tag = NULL; + tor_asprintf(&tag, "-----BEGIN %s-----\n", objtype); + if ((size_t)(eos-src) < strlen(tag) || fast_memneq(src, tag, strlen(tag))) { + tor_free(tag); + return -1; + } + src += strlen(tag); + tor_free(tag); + + // NOTE lack of trailing \n. We do not enforce its presence. + tor_asprintf(&tag, "\n-----END %s-----", objtype); + const char *end_of_base64 = tor_memstr(src, eos-src, tag); + tor_free(tag); + if (end_of_base64 == NULL) + return -1; + + /* Should we actually allow extra stuff at the end? */ + + return base64_decode((char*)dest, destlen, src, end_of_base64-src); +} diff --git a/src/lib/encoding/pem.h b/src/lib/encoding/pem.h new file mode 100644 index 0000000000..0bbb06a794 --- /dev/null +++ b/src/lib/encoding/pem.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file pem.h + * + * \brief Header for pem.c + **/ + +#ifndef TOR_PEM_H +#define TOR_PEM_H + +#include "orconfig.h" +#include <stddef.h> +#include "lib/cc/torint.h" + +size_t pem_encoded_size(size_t src_len, const char *objtype); +int pem_encode(char *dest, size_t destlen, const uint8_t *src, size_t srclen, + const char *objtype); +int pem_decode(uint8_t *dest, size_t destlen, const char *src, size_t srclen, + const char *objtype); + +#endif diff --git a/src/lib/encoding/time_fmt.c b/src/lib/encoding/time_fmt.c new file mode 100644 index 0000000000..40543d41e0 --- /dev/null +++ b/src/lib/encoding/time_fmt.c @@ -0,0 +1,516 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file time_fmt.c + * + * \brief Encode and decode time in various formats. + * + * This module is higher-level than the conversion functions in "wallclock", + * and handles a larger variety of types. It converts between different time + * formats, and encodes and decodes them from strings. + **/ + +#include "lib/encoding/time_fmt.h" +#include "lib/log/log.h" +#include "lib/log/escape.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/string/printf.h" +#include "lib/string/scanf.h" +#include "lib/wallclock/time_to_tm.h" + +#include <string.h> +#include <time.h> + +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#ifdef _WIN32 +/* For struct timeval */ +#include <winsock2.h> +#endif + +/** As localtime_r, but defined for platforms that don't have it: + * + * Convert *<b>timep</b> to a struct tm in local time, and store the value in + * *<b>result</b>. Return the result on success, or NULL on failure. + * + * Treat malformatted inputs localtime outputs as a BUG. + */ +struct tm * +tor_localtime_r(const time_t *timep, struct tm *result) +{ + char *err = NULL; + struct tm *r = tor_localtime_r_msg(timep, result, &err); + if (err) { + log_warn(LD_BUG, "%s", err); + tor_free(err); + } + return r; +} + +/** As gmtime_r, but defined for platforms that don't have it: + * + * Convert *<b>timep</b> to a struct tm in UTC, and store the value in + * *<b>result</b>. Return the result on success, or NULL on failure. + * + * Treat malformatted inputs or gmtime outputs as a BUG. + */ +struct tm * +tor_gmtime_r(const time_t *timep, struct tm *result) +{ + char *err = NULL; + struct tm *r = tor_gmtime_r_msg(timep, result, &err); + if (err) { + log_warn(LD_BUG, "%s", err); + tor_free(err); + } + return r; +} + +/** Yield true iff <b>y</b> is a leap-year. */ +#define IS_LEAPYEAR(y) (!(y % 4) && ((y % 100) || !(y % 400))) +/** Helper: Return the number of leap-days between Jan 1, y1 and Jan 1, y2. */ +static int +n_leapdays(int year1, int year2) +{ + --year1; + --year2; + return (year2/4 - year1/4) - (year2/100 - year1/100) + + (year2/400 - year1/400); +} +/** Number of days per month in non-leap year; used by tor_timegm and + * parse_rfc1123_time. */ +static const int days_per_month[] = + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +/** Compute a time_t given a struct tm. The result is given in UTC, and + * does not account for leap seconds. Return 0 on success, -1 on failure. + */ +int +tor_timegm(const struct tm *tm, time_t *time_out) +{ + /* This is a pretty ironclad timegm implementation, snarfed from Python2.2. + * It's way more brute-force than fiddling with tzset(). + * + * We use int64_t rather than time_t to avoid overflow on multiplication on + * platforms with 32-bit time_t. Since year is clipped to INT32_MAX, and + * since 365 * 24 * 60 * 60 is approximately 31 million, it's not possible + * for INT32_MAX years to overflow int64_t when converted to seconds. */ + int64_t year, days, hours, minutes, seconds; + int i, invalid_year, dpm; + + /* Initialize time_out to 0 for now, to avoid bad usage in case this function + fails and the caller ignores the return value. */ + tor_assert(time_out); + *time_out = 0; + + /* avoid int overflow on addition */ + if (tm->tm_year < INT32_MAX-1900) { + year = tm->tm_year + 1900; + } else { + /* clamp year */ + year = INT32_MAX; + } + invalid_year = (year < 1970 || tm->tm_year >= INT32_MAX-1900); + + if (tm->tm_mon >= 0 && tm->tm_mon <= 11) { + dpm = days_per_month[tm->tm_mon]; + if (tm->tm_mon == 1 && !invalid_year && IS_LEAPYEAR(tm->tm_year)) { + dpm = 29; + } + } else { + /* invalid month - default to 0 days per month */ + dpm = 0; + } + + if (invalid_year || + tm->tm_mon < 0 || tm->tm_mon > 11 || + tm->tm_mday < 1 || tm->tm_mday > dpm || + tm->tm_hour < 0 || tm->tm_hour > 23 || + tm->tm_min < 0 || tm->tm_min > 59 || + tm->tm_sec < 0 || tm->tm_sec > 60) { + log_warn(LD_BUG, "Out-of-range argument to tor_timegm"); + return -1; + } + days = 365 * (year-1970) + n_leapdays(1970,(int)year); + for (i = 0; i < tm->tm_mon; ++i) + days += days_per_month[i]; + if (tm->tm_mon > 1 && IS_LEAPYEAR(year)) + ++days; + days += tm->tm_mday - 1; + hours = days*24 + tm->tm_hour; + + minutes = hours*60 + tm->tm_min; + seconds = minutes*60 + tm->tm_sec; + /* Check that "seconds" will fit in a time_t. On platforms where time_t is + * 32-bit, this check will fail for dates in and after 2038. + * + * We already know that "seconds" can't be negative because "year" >= 1970 */ +#if SIZEOF_TIME_T < 8 + if (seconds < TIME_MIN || seconds > TIME_MAX) { + log_warn(LD_BUG, "Result does not fit in tor_timegm"); + return -1; + } +#endif /* SIZEOF_TIME_T < 8 */ + *time_out = (time_t)seconds; + return 0; +} + +/* strftime is locale-specific, so we need to replace those parts */ + +/** A c-locale array of 3-letter names of weekdays, starting with Sun. */ +static const char *WEEKDAY_NAMES[] = + { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; +/** A c-locale array of 3-letter names of months, starting with Jan. */ +static const char *MONTH_NAMES[] = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + +/** Set <b>buf</b> to the RFC1123 encoding of the UTC value of <b>t</b>. + * The buffer must be at least RFC1123_TIME_LEN+1 bytes long. + * + * (RFC1123 format is "Fri, 29 Sep 2006 15:54:20 GMT". Note the "GMT" + * rather than "UTC".) + */ +void +format_rfc1123_time(char *buf, time_t t) +{ + struct tm tm; + + tor_gmtime_r(&t, &tm); + + strftime(buf, RFC1123_TIME_LEN+1, "___, %d ___ %Y %H:%M:%S GMT", &tm); + tor_assert(tm.tm_wday >= 0); + tor_assert(tm.tm_wday <= 6); + memcpy(buf, WEEKDAY_NAMES[tm.tm_wday], 3); + tor_assert(tm.tm_mon >= 0); + tor_assert(tm.tm_mon <= 11); + memcpy(buf+8, MONTH_NAMES[tm.tm_mon], 3); +} + +/** Parse the (a subset of) the RFC1123 encoding of some time (in UTC) from + * <b>buf</b>, and store the result in *<b>t</b>. + * + * Note that we only accept the subset generated by format_rfc1123_time above, + * not the full range of formats suggested by RFC 1123. + * + * Return 0 on success, -1 on failure. +*/ +int +parse_rfc1123_time(const char *buf, time_t *t) +{ + struct tm tm; + char month[4]; + char weekday[4]; + int i, m, invalid_year; + unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec; + unsigned dpm; + + if (strlen(buf) != RFC1123_TIME_LEN) + return -1; + memset(&tm, 0, sizeof(tm)); + if (tor_sscanf(buf, "%3s, %2u %3s %u %2u:%2u:%2u GMT", weekday, + &tm_mday, month, &tm_year, &tm_hour, + &tm_min, &tm_sec) < 7) { + char *esc = esc_for_log(buf); + log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc); + tor_free(esc); + return -1; + } + + m = -1; + for (i = 0; i < 12; ++i) { + if (!strcmp(month, MONTH_NAMES[i])) { + m = i; + break; + } + } + if (m<0) { + char *esc = esc_for_log(buf); + log_warn(LD_GENERAL, "Got invalid RFC1123 time %s: No such month", esc); + tor_free(esc); + return -1; + } + tm.tm_mon = m; + + invalid_year = (tm_year >= INT32_MAX || tm_year < 1970); + tor_assert(m >= 0 && m <= 11); + dpm = days_per_month[m]; + if (m == 1 && !invalid_year && IS_LEAPYEAR(tm_year)) { + dpm = 29; + } + + if (invalid_year || tm_mday < 1 || tm_mday > dpm || + tm_hour > 23 || tm_min > 59 || tm_sec > 60) { + char *esc = esc_for_log(buf); + log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc); + tor_free(esc); + return -1; + } + tm.tm_mday = (int)tm_mday; + tm.tm_year = (int)tm_year; + tm.tm_hour = (int)tm_hour; + tm.tm_min = (int)tm_min; + tm.tm_sec = (int)tm_sec; + + if (tm.tm_year < 1970) { + /* LCOV_EXCL_START + * XXXX I think this is dead code; we already checked for + * invalid_year above. */ + tor_assert_nonfatal_unreached(); + char *esc = esc_for_log(buf); + log_warn(LD_GENERAL, + "Got invalid RFC1123 time %s. (Before 1970)", esc); + tor_free(esc); + return -1; + /* LCOV_EXCL_STOP */ + } + tm.tm_year -= 1900; + + return tor_timegm(&tm, t); +} + +/** Set <b>buf</b> to the ISO8601 encoding of the local value of <b>t</b>. + * The buffer must be at least ISO_TIME_LEN+1 bytes long. + * + * (ISO8601 format is 2006-10-29 10:57:20) + */ +void +format_local_iso_time(char *buf, time_t t) +{ + struct tm tm; + strftime(buf, ISO_TIME_LEN+1, "%Y-%m-%d %H:%M:%S", tor_localtime_r(&t, &tm)); +} + +/** Set <b>buf</b> to the ISO8601 encoding of the GMT value of <b>t</b>. + * The buffer must be at least ISO_TIME_LEN+1 bytes long. + */ +void +format_iso_time(char *buf, time_t t) +{ + struct tm tm; + strftime(buf, ISO_TIME_LEN+1, "%Y-%m-%d %H:%M:%S", tor_gmtime_r(&t, &tm)); +} + +/** As format_local_iso_time, but use the yyyy-mm-ddThh:mm:ss format to avoid + * embedding an internal space. */ +void +format_local_iso_time_nospace(char *buf, time_t t) +{ + format_local_iso_time(buf, t); + buf[10] = 'T'; +} + +/** As format_iso_time, but use the yyyy-mm-ddThh:mm:ss format to avoid + * embedding an internal space. */ +void +format_iso_time_nospace(char *buf, time_t t) +{ + format_iso_time(buf, t); + buf[10] = 'T'; +} + +/** As format_iso_time_nospace, but include microseconds in decimal + * fixed-point format. Requires that buf be at least ISO_TIME_USEC_LEN+1 + * bytes long. */ +void +format_iso_time_nospace_usec(char *buf, const struct timeval *tv) +{ + tor_assert(tv); + format_iso_time_nospace(buf, (time_t)tv->tv_sec); + tor_snprintf(buf+ISO_TIME_LEN, 8, ".%06d", (int)tv->tv_usec); +} + +/** Given an ISO-formatted UTC time value (after the epoch) in <b>cp</b>, + * parse it and store its value in *<b>t</b>. Return 0 on success, -1 on + * failure. Ignore extraneous stuff in <b>cp</b> after the end of the time + * string, unless <b>strict</b> is set. If <b>nospace</b> is set, + * expect the YYYY-MM-DDTHH:MM:SS format. */ +int +parse_iso_time_(const char *cp, time_t *t, int strict, int nospace) +{ + struct tm st_tm; + unsigned int year=0, month=0, day=0, hour=0, minute=0, second=0; + int n_fields; + char extra_char, separator_char; + n_fields = tor_sscanf(cp, "%u-%2u-%2u%c%2u:%2u:%2u%c", + &year, &month, &day, + &separator_char, + &hour, &minute, &second, &extra_char); + if (strict ? (n_fields != 7) : (n_fields < 7)) { + char *esc = esc_for_log(cp); + log_warn(LD_GENERAL, "ISO time %s was unparseable", esc); + tor_free(esc); + return -1; + } + if (separator_char != (nospace ? 'T' : ' ')) { + char *esc = esc_for_log(cp); + log_warn(LD_GENERAL, "ISO time %s was unparseable", esc); + tor_free(esc); + return -1; + } + if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 || + hour > 23 || minute > 59 || second > 60 || year >= INT32_MAX) { + char *esc = esc_for_log(cp); + log_warn(LD_GENERAL, "ISO time %s was nonsensical", esc); + tor_free(esc); + return -1; + } + st_tm.tm_year = (int)year-1900; + st_tm.tm_mon = month-1; + st_tm.tm_mday = day; + st_tm.tm_hour = hour; + st_tm.tm_min = minute; + st_tm.tm_sec = second; + st_tm.tm_wday = 0; /* Should be ignored. */ + + if (st_tm.tm_year < 70) { + /* LCOV_EXCL_START + * XXXX I think this is dead code; we already checked for + * year < 1970 above. */ + tor_assert_nonfatal_unreached(); + char *esc = esc_for_log(cp); + log_warn(LD_GENERAL, "Got invalid ISO time %s. (Before 1970)", esc); + tor_free(esc); + return -1; + /* LCOV_EXCL_STOP */ + } + return tor_timegm(&st_tm, t); +} + +/** Given an ISO-formatted UTC time value (after the epoch) in <b>cp</b>, + * parse it and store its value in *<b>t</b>. Return 0 on success, -1 on + * failure. Reject the string if any characters are present after the time. + */ +int +parse_iso_time(const char *cp, time_t *t) +{ + return parse_iso_time_(cp, t, 1, 0); +} + +/** + * As parse_iso_time, but parses a time encoded by format_iso_time_nospace(). + */ +int +parse_iso_time_nospace(const char *cp, time_t *t) +{ + return parse_iso_time_(cp, t, 1, 1); +} + +/** Given a <b>date</b> in one of the three formats allowed by HTTP (ugh), + * parse it into <b>tm</b>. Return 0 on success, negative on failure. */ +int +parse_http_time(const char *date, struct tm *tm) +{ + const char *cp; + char month[4]; + char wkday[4]; + int i; + unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec; + + tor_assert(tm); + memset(tm, 0, sizeof(*tm)); + + /* First, try RFC1123 or RFC850 format: skip the weekday. */ + if ((cp = strchr(date, ','))) { + ++cp; + if (*cp != ' ') + return -1; + ++cp; + if (tor_sscanf(cp, "%2u %3s %4u %2u:%2u:%2u GMT", + &tm_mday, month, &tm_year, + &tm_hour, &tm_min, &tm_sec) == 6) { + /* rfc1123-date */ + tm_year -= 1900; + } else if (tor_sscanf(cp, "%2u-%3s-%2u %2u:%2u:%2u GMT", + &tm_mday, month, &tm_year, + &tm_hour, &tm_min, &tm_sec) == 6) { + /* rfc850-date */ + } else { + return -1; + } + } else { + /* No comma; possibly asctime() format. */ + if (tor_sscanf(date, "%3s %3s %2u %2u:%2u:%2u %4u", + wkday, month, &tm_mday, + &tm_hour, &tm_min, &tm_sec, &tm_year) == 7) { + tm_year -= 1900; + } else { + return -1; + } + } + tm->tm_mday = (int)tm_mday; + tm->tm_year = (int)tm_year; + tm->tm_hour = (int)tm_hour; + tm->tm_min = (int)tm_min; + tm->tm_sec = (int)tm_sec; + tm->tm_wday = 0; /* Leave this unset. */ + + month[3] = '\0'; + /* Okay, now decode the month. */ + /* set tm->tm_mon to dummy value so the check below fails. */ + tm->tm_mon = -1; + for (i = 0; i < 12; ++i) { + if (!strcasecmp(MONTH_NAMES[i], month)) { + tm->tm_mon = i; + } + } + + if (tm->tm_year < 0 || + tm->tm_mon < 0 || tm->tm_mon > 11 || + tm->tm_mday < 1 || tm->tm_mday > 31 || + tm->tm_hour < 0 || tm->tm_hour > 23 || + tm->tm_min < 0 || tm->tm_min > 59 || + tm->tm_sec < 0 || tm->tm_sec > 60) + return -1; /* Out of range, or bad month. */ + + return 0; +} + +/** Given an <b>interval</b> in seconds, try to write it to the + * <b>out_len</b>-byte buffer in <b>out</b> in a human-readable form. + * Returns a non-negative integer on success, -1 on failure. + */ +int +format_time_interval(char *out, size_t out_len, long interval) +{ + /* We only report seconds if there's no hours. */ + long sec = 0, min = 0, hour = 0, day = 0; + + /* -LONG_MIN is LONG_MAX + 1, which causes signed overflow */ + if (interval < -LONG_MAX) + interval = LONG_MAX; + else if (interval < 0) + interval = -interval; + + if (interval >= 86400) { + day = interval / 86400; + interval %= 86400; + } + if (interval >= 3600) { + hour = interval / 3600; + interval %= 3600; + } + if (interval >= 60) { + min = interval / 60; + interval %= 60; + } + sec = interval; + + if (day) { + return tor_snprintf(out, out_len, "%ld days, %ld hours, %ld minutes", + day, hour, min); + } else if (hour) { + return tor_snprintf(out, out_len, "%ld hours, %ld minutes", hour, min); + } else if (min) { + return tor_snprintf(out, out_len, "%ld minutes, %ld seconds", min, sec); + } else { + return tor_snprintf(out, out_len, "%ld seconds", sec); + } +} diff --git a/src/lib/encoding/time_fmt.h b/src/lib/encoding/time_fmt.h new file mode 100644 index 0000000000..0ddeca57fc --- /dev/null +++ b/src/lib/encoding/time_fmt.h @@ -0,0 +1,44 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file time_fmt.h + * + * \brief Header for time_fmt.c + **/ + +#ifndef TOR_TIME_FMT_H +#define TOR_TIME_FMT_H + +#include "orconfig.h" +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +struct tm; +struct timeval; + +struct tm *tor_localtime_r(const time_t *timep, struct tm *result); +struct tm *tor_gmtime_r(const time_t *timep, struct tm *result); +int tor_timegm(const struct tm *tm, time_t *time_out); + +#define RFC1123_TIME_LEN 29 +void format_rfc1123_time(char *buf, time_t t); +int parse_rfc1123_time(const char *buf, time_t *t); +#define ISO_TIME_LEN 19 +#define ISO_TIME_USEC_LEN (ISO_TIME_LEN+7) +void format_local_iso_time(char *buf, time_t t); +void format_iso_time(char *buf, time_t t); +void format_local_iso_time_nospace(char *buf, time_t t); +void format_iso_time_nospace(char *buf, time_t t); +void format_iso_time_nospace_usec(char *buf, const struct timeval *tv); +int parse_iso_time_(const char *cp, time_t *t, int strict, int nospace); +int parse_iso_time(const char *buf, time_t *t); +int parse_iso_time_nospace(const char *cp, time_t *t); +int parse_http_time(const char *buf, struct tm *tm); +int format_time_interval(char *out, size_t out_len, long interval); + +#endif diff --git a/src/lib/err/.may_include b/src/lib/err/.may_include new file mode 100644 index 0000000000..48cc0ef088 --- /dev/null +++ b/src/lib/err/.may_include @@ -0,0 +1,3 @@ +orconfig.h +lib/cc/*.h +lib/err/*.h diff --git a/src/lib/err/backtrace.c b/src/lib/err/backtrace.c new file mode 100644 index 0000000000..1d1b3bcfa3 --- /dev/null +++ b/src/lib/err/backtrace.c @@ -0,0 +1,286 @@ +/* Copyright (c) 2013-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file backtrace.c + * + * \brief Functions to produce backtraces on bugs, crashes, or assertion + * failures. + * + * Currently, we've only got an implementation here using the backtrace() + * family of functions, which are sometimes provided by libc and sometimes + * provided by libexecinfo. We tie into the sigaction() backend in order to + * detect crashes. + * + * This is one of the lowest-level modules, since nearly everything needs to + * be able to log an error. As such, it doesn't call the log module or any + * other higher-level modules directly. + */ + +#include "orconfig.h" +#include "lib/err/torerr.h" + +#ifdef HAVE_EXECINFO_H +#include <execinfo.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SIGNAL_H +#include <signal.h> +#endif +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#ifdef HAVE_CYGWIN_SIGNAL_H +#include <cygwin/signal.h> +#elif defined(HAVE_SYS_UCONTEXT_H) +#include <sys/ucontext.h> +#elif defined(HAVE_UCONTEXT_H) +#include <ucontext.h> +#endif /* defined(HAVE_CYGWIN_SIGNAL_H) || ... */ + +#ifdef HAVE_PTHREAD_H +#include <pthread.h> +#endif + +#define EXPOSE_CLEAN_BACKTRACE +#include "lib/err/backtrace.h" +#include "lib/err/torerr.h" + +#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && \ + defined(HAVE_BACKTRACE_SYMBOLS_FD) && defined(HAVE_SIGACTION) +#define USE_BACKTRACE +#endif + +#if !defined(USE_BACKTRACE) +#define NO_BACKTRACE_IMPL +#endif + +// Redundant with util.h, but doing it here so we can avoid that dependency. +#define raw_free free + +#ifdef USE_BACKTRACE +/** Version of Tor to report in backtrace messages. */ +static char bt_version[128] = ""; + +/** Largest stack depth to try to dump. */ +#define MAX_DEPTH 256 +/** Static allocation of stack to dump. This is static so we avoid stack + * pressure. */ +static void *cb_buf[MAX_DEPTH]; +/** Protects cb_buf from concurrent access. Pthreads, since this code + * is Unix-only, and since this code needs to be lowest-level. */ +static pthread_mutex_t cb_buf_mutex = PTHREAD_MUTEX_INITIALIZER; + +/** Change a stacktrace in <b>stack</b> of depth <b>depth</b> so that it will + * log the correct function from which a signal was received with context + * <b>ctx</b>. (When we get a signal, the current function will not have + * called any other function, and will therefore have not pushed its address + * onto the stack. Fortunately, we usually have the program counter in the + * ucontext_t structure. + */ +void +clean_backtrace(void **stack, size_t depth, const ucontext_t *ctx) +{ +#ifdef PC_FROM_UCONTEXT +#if defined(__linux__) + const size_t n = 1; +#elif defined(__darwin__) || defined(__APPLE__) || defined(OpenBSD) \ + || defined(__FreeBSD__) + const size_t n = 2; +#else + const size_t n = 1; +#endif /* defined(__linux__) || ... */ + if (depth <= n) + return; + + stack[n] = (void*) ctx->PC_FROM_UCONTEXT; +#else /* !(defined(PC_FROM_UCONTEXT)) */ + (void) depth; + (void) ctx; + (void) stack; +#endif /* defined(PC_FROM_UCONTEXT) */ +} + +/** Log a message <b>msg</b> at <b>severity</b> in <b>domain</b>, and follow + * that with a backtrace log. Send messages via the tor_log function at + * logger". */ +void +log_backtrace_impl(int severity, int domain, const char *msg, + tor_log_fn logger) +{ + size_t depth; + char **symbols; + size_t i; + + pthread_mutex_lock(&cb_buf_mutex); + + depth = backtrace(cb_buf, MAX_DEPTH); + symbols = backtrace_symbols(cb_buf, (int)depth); + + logger(severity, domain, "%s. Stack trace:", msg); + if (!symbols) { + /* LCOV_EXCL_START -- we can't provoke this. */ + logger(severity, domain, " Unable to generate backtrace."); + goto done; + /* LCOV_EXCL_STOP */ + } + for (i=0; i < depth; ++i) { + logger(severity, domain, " %s", symbols[i]); + } + raw_free(symbols); + + done: + pthread_mutex_unlock(&cb_buf_mutex); +} + +static void crash_handler(int sig, siginfo_t *si, void *ctx_) + __attribute__((noreturn)); + +/** Signal handler: write a crash message with a stack trace, and die. */ +static void +crash_handler(int sig, siginfo_t *si, void *ctx_) +{ + char buf[40]; + size_t depth; + ucontext_t *ctx = (ucontext_t *) ctx_; + int n_fds, i; + const int *fds = NULL; + + (void) si; + + depth = backtrace(cb_buf, MAX_DEPTH); + /* Clean up the top stack frame so we get the real function + * name for the most recently failing function. */ + clean_backtrace(cb_buf, depth, ctx); + + format_dec_number_sigsafe((unsigned)sig, buf, sizeof(buf)); + + tor_log_err_sigsafe(bt_version, " died: Caught signal ", buf, "\n", + NULL); + + n_fds = tor_log_get_sigsafe_err_fds(&fds); + for (i=0; i < n_fds; ++i) + backtrace_symbols_fd(cb_buf, (int)depth, fds[i]); + + abort(); +} + +/** Write a backtrace to all of the emergency-error fds. */ +void +dump_stack_symbols_to_error_fds(void) +{ + int n_fds, i; + const int *fds = NULL; + size_t depth; + + depth = backtrace(cb_buf, MAX_DEPTH); + + n_fds = tor_log_get_sigsafe_err_fds(&fds); + for (i=0; i < n_fds; ++i) + backtrace_symbols_fd(cb_buf, (int)depth, fds[i]); +} + +/** Install signal handlers as needed so that when we crash, we produce a + * useful stack trace. Return 0 on success, -errno on failure. */ +static int +install_bt_handler(const char *software) +{ + int trap_signals[] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGSYS, + SIGIO, -1 }; + int i, rv=0; + + strncpy(bt_version, software, sizeof(bt_version) - 1); + bt_version[sizeof(bt_version) - 1] = 0; + + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = crash_handler; + sa.sa_flags = SA_SIGINFO; + sigfillset(&sa.sa_mask); + + for (i = 0; trap_signals[i] >= 0; ++i) { + if (sigaction(trap_signals[i], &sa, NULL) == -1) { + /* LCOV_EXCL_START */ + rv = -errno; + /* LCOV_EXCL_STOP */ + } + } + + { + /* Now, generate (but do not log) a backtrace. This ensures that + * libc has pre-loaded the symbols we need to dump things, so that later + * reads won't be denied by the sandbox code */ + char **symbols; + size_t depth = backtrace(cb_buf, MAX_DEPTH); + symbols = backtrace_symbols(cb_buf, (int) depth); + if (symbols) + raw_free(symbols); + } + + return rv; +} + +/** Uninstall crash handlers. */ +static void +remove_bt_handler(void) +{ +} +#endif /* defined(USE_BACKTRACE) */ + +#ifdef NO_BACKTRACE_IMPL +void +log_backtrace_impl(int severity, int domain, const char *msg, + tor_log_fn logger) +{ + logger(severity, domain, "%s. (Stack trace not available)", msg); +} + +static int +install_bt_handler(const char *software) +{ + (void) software; + return 0; +} + +static void +remove_bt_handler(void) +{ +} + +void +dump_stack_symbols_to_error_fds(void) +{ +} +#endif /* defined(NO_BACKTRACE_IMPL) */ + +/** Set up code to handle generating error messages on crashes. */ +int +configure_backtrace_handler(const char *tor_version) +{ + char version[128] = "Tor\0"; + + if (tor_version) { + snprintf(version, sizeof(version), "Tor %s", tor_version); + } + + return install_bt_handler(version); +} + +/** Perform end-of-process cleanup for code that generates error messages on + * crashes. */ +void +clean_up_backtrace_handler(void) +{ + remove_bt_handler(); +} diff --git a/src/lib/err/backtrace.h b/src/lib/err/backtrace.h new file mode 100644 index 0000000000..9b313261e6 --- /dev/null +++ b/src/lib/err/backtrace.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2013-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_BACKTRACE_H +#define TOR_BACKTRACE_H + +/** + * \file backtrace.h + * + * \brief Header for backtrace.c + **/ + +#include "orconfig.h" +#include "lib/cc/compat_compiler.h" + +typedef void (*tor_log_fn)(int, unsigned, const char *fmt, ...) + CHECK_PRINTF(3,4); + +void log_backtrace_impl(int severity, int domain, const char *msg, + tor_log_fn logger); +int configure_backtrace_handler(const char *tor_version); +void clean_up_backtrace_handler(void); +void dump_stack_symbols_to_error_fds(void); + +#define log_backtrace(sev, dom, msg) \ + log_backtrace_impl((sev), (dom), (msg), tor_log) + +#ifdef EXPOSE_CLEAN_BACKTRACE +#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && \ + defined(HAVE_BACKTRACE_SYMBOLS_FD) && defined(HAVE_SIGACTION) +void clean_backtrace(void **stack, size_t depth, const ucontext_t *ctx); +#endif +#endif /* defined(EXPOSE_CLEAN_BACKTRACE) */ + +#endif /* !defined(TOR_BACKTRACE_H) */ diff --git a/src/lib/err/include.am b/src/lib/err/include.am new file mode 100644 index 0000000000..f2a409c51e --- /dev/null +++ b/src/lib/err/include.am @@ -0,0 +1,19 @@ + +noinst_LIBRARIES += src/lib/libtor-err.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-err-testing.a +endif + +src_lib_libtor_err_a_SOURCES = \ + src/lib/err/backtrace.c \ + src/lib/err/torerr.c + +src_lib_libtor_err_testing_a_SOURCES = \ + $(src_lib_libtor_err_a_SOURCES) +src_lib_libtor_err_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_err_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/err/backtrace.h \ + src/lib/err/torerr.h diff --git a/src/lib/err/torerr.c b/src/lib/err/torerr.c new file mode 100644 index 0000000000..54acf722aa --- /dev/null +++ b/src/lib/err/torerr.c @@ -0,0 +1,238 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file torerr.c + * + * \brief Handling code for unrecoverable emergencies, at a lower level + * than the logging code. + * + * There are plenty of places that things can go wrong in Tor's backend + * libraries: the allocator can fail, the locking subsystem can fail, and so + * on. But since these subsystems are used themselves by the logging module, + * they can't use the logging code directly to report their errors. + * + * As a workaround, the logging code provides this module with a set of raw + * fds to be used for reporting errors in the lowest-level Tor code. + */ + +#include "orconfig.h" +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef HAVE_TIME_H +#include <time.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include "lib/err/torerr.h" +#include "lib/err/backtrace.h" + +/** Array of fds to log crash-style warnings to. */ +static int sigsafe_log_fds[TOR_SIGSAFE_LOG_MAX_FDS] = { STDERR_FILENO }; +/** The number of elements used in sigsafe_log_fds */ +static int n_sigsafe_log_fds = 1; +/** Log granularity in milliseconds. */ +static int log_granularity = 1000; + +/** Write <b>s</b> to each element of sigsafe_log_fds. Return 0 on success, -1 + * on failure. */ +static int +tor_log_err_sigsafe_write(const char *s) +{ + int i; + ssize_t r; + size_t len = strlen(s); + int err = 0; + for (i=0; i < n_sigsafe_log_fds; ++i) { + r = write(sigsafe_log_fds[i], s, len); + err += (r != (ssize_t)len); + } + return err ? -1 : 0; +} + +/** Given a list of string arguments ending with a NULL, writes them + * to our logs and to stderr (if possible). This function is safe to call + * from within a signal handler. */ +void +tor_log_err_sigsafe(const char *m, ...) +{ + va_list ap; + const char *x; + char timebuf[33]; + time_t now = time(NULL); + + if (!m) + return; + if (log_granularity >= 2000) { + int g = log_granularity / 1000; + now -= now % g; + } + timebuf[0] = now < 0 ? '-' : ' '; + if (now < 0) now = -now; + timebuf[1] = '\0'; + format_dec_number_sigsafe(now, timebuf+1, sizeof(timebuf)-1); + tor_log_err_sigsafe_write("\n==========================================" + "================== T="); + tor_log_err_sigsafe_write(timebuf); + tor_log_err_sigsafe_write("\n"); + tor_log_err_sigsafe_write(m); + va_start(ap, m); + while ((x = va_arg(ap, const char*))) { + tor_log_err_sigsafe_write(x); + } + va_end(ap); +} + +/** Set *<b>out</b> to a pointer to an array of the fds to log errors to from + * inside a signal handler or other emergency condition. Return the number of + * elements in the array. */ +int +tor_log_get_sigsafe_err_fds(const int **out) +{ + *out = sigsafe_log_fds; + return n_sigsafe_log_fds; +} + +/** + * Update the list of fds that get errors from inside a signal handler or + * other emergency condition. Ignore any beyond the first + * TOR_SIGSAFE_LOG_MAX_FDS. + */ +void +tor_log_set_sigsafe_err_fds(const int *fds, int n) +{ + if (n > TOR_SIGSAFE_LOG_MAX_FDS) { + n = TOR_SIGSAFE_LOG_MAX_FDS; + } + + memcpy(sigsafe_log_fds, fds, n * sizeof(int)); + n_sigsafe_log_fds = n; +} + +/** + * Set the granularity (in ms) to use when reporting fatal errors outside + * the logging system. + */ +void +tor_log_sigsafe_err_set_granularity(int ms) +{ + log_granularity = ms; +} + +/** + * Log an emergency assertion failure message. + * + * This kind of message is safe to send from within a log handler, + * a signal handler, or other emergency situation. + */ +void +tor_raw_assertion_failed_msg_(const char *file, int line, const char *expr, + const char *msg) +{ + char linebuf[16]; + format_dec_number_sigsafe(line, linebuf, sizeof(linebuf)); + tor_log_err_sigsafe("INTERNAL ERROR: Raw assertion failed at ", + file, ":", linebuf, ": ", expr, NULL); + if (msg) { + tor_log_err_sigsafe_write(msg); + tor_log_err_sigsafe_write("\n"); + } + + dump_stack_symbols_to_error_fds(); +} + +/* As format_{hex,dex}_number_sigsafe, but takes a <b>radix</b> argument + * in range 2..16 inclusive. */ +static int +format_number_sigsafe(unsigned long x, char *buf, int buf_len, + unsigned int radix) +{ + unsigned long tmp; + int len; + char *cp; + + /* NOT tor_assert. This needs to be safe to run from within a signal + * handler, and from within the 'tor_assert() has failed' code. Not even + * raw_assert(), since raw_assert() calls this function on failure. */ + if (radix < 2 || radix > 16) + return 0; + + /* Count how many digits we need. */ + tmp = x; + len = 1; + while (tmp >= radix) { + tmp /= radix; + ++len; + } + + /* Not long enough */ + if (!buf || len >= buf_len) + return 0; + + cp = buf + len; + *cp = '\0'; + do { + unsigned digit = (unsigned) (x % radix); + if (cp <= buf) { + /* Not tor_assert(); see above. */ + abort(); + } + --cp; + *cp = "0123456789ABCDEF"[digit]; + x /= radix; + } while (x); + + /* NOT tor_assert; see above. */ + if (cp != buf) { + abort(); // LCOV_EXCL_LINE + } + + return len; +} + +/** + * Helper function to output hex numbers from within a signal handler. + * + * Writes the nul-terminated hexadecimal digits of <b>x</b> into a buffer + * <b>buf</b> of size <b>buf_len</b>, and return the actual number of digits + * written, not counting the terminal NUL. + * + * If there is insufficient space, write nothing and return 0. + * + * This accepts an unsigned int because format_helper_exit_status() needs to + * call it with a signed int and an unsigned char, and since the C standard + * does not guarantee that an int is wider than a char (an int must be at + * least 16 bits but it is permitted for a char to be that wide as well), we + * can't assume a signed int is sufficient to accommodate an unsigned char. + * Thus, format_helper_exit_status() will still need to emit any require '-' + * on its own. + * + * For most purposes, you'd want to use tor_snprintf("%x") instead of this + * function; it's designed to be used in code paths where you can't call + * arbitrary C functions. + */ +int +format_hex_number_sigsafe(unsigned long x, char *buf, int buf_len) +{ + return format_number_sigsafe(x, buf, buf_len, 16); +} + +/** As format_hex_number_sigsafe, but format the number in base 10. */ +int +format_dec_number_sigsafe(unsigned long x, char *buf, int buf_len) +{ + return format_number_sigsafe(x, buf, buf_len, 10); +} diff --git a/src/lib/err/torerr.h b/src/lib/err/torerr.h new file mode 100644 index 0000000000..6ae91fbe85 --- /dev/null +++ b/src/lib/err/torerr.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file torerr.h + * + * \brief Headers for torerr.c. + **/ + +#ifndef TOR_TORERR_H +#define TOR_TORERR_H + +#include "lib/cc/compat_compiler.h" + +/* The raw_assert...() variants are for use within code that can't call + * tor_assertion_failed_() because of call circularity issues. */ +#define raw_assert(expr) STMT_BEGIN \ + if (!(expr)) { \ + tor_raw_assertion_failed_msg_(__FILE__, __LINE__, #expr, NULL); \ + abort(); \ + } \ + STMT_END +#define raw_assert_unreached(expr) raw_assert(0) +#define raw_assert_unreached_msg(msg) STMT_BEGIN \ + tor_raw_assertion_failed_msg_(__FILE__, __LINE__, "0", (msg)); \ + abort(); \ + STMT_END + +void tor_raw_assertion_failed_msg_(const char *file, int line, + const char *expr, + const char *msg); + +/** Maximum number of fds that will get notifications if we crash */ +#define TOR_SIGSAFE_LOG_MAX_FDS 8 + +void tor_log_err_sigsafe(const char *m, ...); +int tor_log_get_sigsafe_err_fds(const int **out); +void tor_log_set_sigsafe_err_fds(const int *fds, int n); +void tor_log_sigsafe_err_set_granularity(int ms); + +int format_hex_number_sigsafe(unsigned long x, char *buf, int max_len); +int format_dec_number_sigsafe(unsigned long x, char *buf, int max_len); + +#endif /* !defined(TOR_TORLOG_H) */ diff --git a/src/lib/evloop/.may_include b/src/lib/evloop/.may_include new file mode 100644 index 0000000000..30af508914 --- /dev/null +++ b/src/lib/evloop/.may_include @@ -0,0 +1,16 @@ +orconfig.h + +lib/cc/*.h +lib/crypt_ops/*.h +lib/evloop/*.h +lib/intmath/*.h +lib/log/*.h +lib/malloc/*.h +lib/net/*.h +lib/string/*.h +lib/testsupport/*.h +lib/thread/*.h +lib/time/*.h + +src/ext/timeouts/timeout.c +tor_queue.h
\ No newline at end of file diff --git a/src/lib/evloop/compat_libevent.c b/src/lib/evloop/compat_libevent.c new file mode 100644 index 0000000000..91eacb9938 --- /dev/null +++ b/src/lib/evloop/compat_libevent.c @@ -0,0 +1,535 @@ +/* Copyright (c) 2009-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_libevent.c + * \brief Wrappers and utility functions for Libevent. + */ + +#include "orconfig.h" +#define COMPAT_LIBEVENT_PRIVATE +#include "lib/evloop/compat_libevent.h" + +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/string/compat_string.h" + +#include <event2/event.h> +#include <event2/thread.h> +#include <string.h> + +/** A string which, if it appears in a libevent log, should be ignored. */ +static const char *suppress_msg = NULL; +/** Callback function passed to event_set_log() so we can intercept + * log messages from libevent. */ +STATIC void +libevent_logging_callback(int severity, const char *msg) +{ + char buf[1024]; + size_t n; + if (suppress_msg && strstr(msg, suppress_msg)) + return; + n = strlcpy(buf, msg, sizeof(buf)); + if (n && n < sizeof(buf) && buf[n-1] == '\n') { + buf[n-1] = '\0'; + } + switch (severity) { + case _EVENT_LOG_DEBUG: + log_debug(LD_NOCB|LD_NET, "Message from libevent: %s", buf); + break; + case _EVENT_LOG_MSG: + log_info(LD_NOCB|LD_NET, "Message from libevent: %s", buf); + break; + case _EVENT_LOG_WARN: + log_warn(LD_NOCB|LD_GENERAL, "Warning from libevent: %s", buf); + break; + case _EVENT_LOG_ERR: + log_err(LD_NOCB|LD_GENERAL, "Error from libevent: %s", buf); + break; + default: + log_warn(LD_NOCB|LD_GENERAL, "Message [%d] from libevent: %s", + severity, buf); + break; + } +} +/** Set hook to intercept log messages from libevent. */ +void +configure_libevent_logging(void) +{ + event_set_log_callback(libevent_logging_callback); +} + +/** Ignore any libevent log message that contains <b>msg</b>. */ +void +suppress_libevent_log_msg(const char *msg) +{ + suppress_msg = msg; +} + +/* Wrapper for event_free() that tolerates tor_event_free(NULL) */ +void +tor_event_free_(struct event *ev) +{ + if (ev == NULL) + return; + event_free(ev); +} + +/** Global event base for use by the main thread. */ +static struct event_base *the_event_base = NULL; + +/** + * @defgroup postloop post-loop event helpers + * + * If we're not careful, Libevent can susceptible to infinite event chains: + * one event can activate another, whose callback activates another, whose + * callback activates another, ad infinitum. While this is happening, + * Libevent won't be checking timeouts, socket-based events, signals, and so + * on. + * + * We solve this problem by marking some events as "post-loop". A post-loop + * event behaves like any ordinary event, but any events that _it_ activates + * cannot run until Libevent has checked for other events at least once. + * + * @{ */ + +/** + * An event that stops Libevent from running any more events on the current + * iteration of its loop, until it has re-checked for socket events, signal + * events, timeouts, etc. + */ +static struct event *rescan_mainloop_ev = NULL; + +/** + * Callback to implement rescan_mainloop_ev: it simply exits the mainloop, + * and relies on Tor to re-enter the mainloop since no error has occurred. + */ +static void +rescan_mainloop_cb(evutil_socket_t fd, short events, void *arg) +{ + (void)fd; + (void)events; + struct event_base *the_base = arg; + event_base_loopbreak(the_base); +} + +/** @} */ + +/* This is what passes for version detection on OSX. We set + * MACOSX_KQUEUE_IS_BROKEN to true iff we're on a version of OSX before + * 10.4.0 (aka 1040). */ +#ifdef __APPLE__ +#ifdef __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ +#define MACOSX_KQUEUE_IS_BROKEN \ + (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1040) +#else +#define MACOSX_KQUEUE_IS_BROKEN 0 +#endif /* defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) */ +#endif /* defined(__APPLE__) */ + +/** Initialize the Libevent library and set up the event base. */ +void +tor_libevent_initialize(tor_libevent_cfg *torcfg) +{ + tor_assert(the_event_base == NULL); + /* some paths below don't use torcfg, so avoid unused variable warnings */ + (void)torcfg; + + { + int attempts = 0; + struct event_config *cfg; + + ++attempts; + cfg = event_config_new(); + tor_assert(cfg); + + /* Telling Libevent not to try to turn locking on can avoid a needless + * socketpair() attempt. */ + event_config_set_flag(cfg, EVENT_BASE_FLAG_NOLOCK); + + if (torcfg->num_cpus > 0) + event_config_set_num_cpus_hint(cfg, torcfg->num_cpus); + + /* We can enable changelist support with epoll, since we don't give + * Libevent any dup'd fds. This lets us avoid some syscalls. */ + event_config_set_flag(cfg, EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST); + + the_event_base = event_base_new_with_config(cfg); + + event_config_free(cfg); + } + + if (!the_event_base) { + /* LCOV_EXCL_START */ + log_err(LD_GENERAL, "Unable to initialize Libevent: cannot continue."); + exit(1); // exit ok: libevent is broken. + /* LCOV_EXCL_STOP */ + } + + rescan_mainloop_ev = event_new(the_event_base, -1, 0, + rescan_mainloop_cb, the_event_base); + if (!rescan_mainloop_ev) { + /* LCOV_EXCL_START */ + log_err(LD_GENERAL, "Unable to create rescan event: cannot continue."); + exit(1); // exit ok: libevent is broken. + /* LCOV_EXCL_STOP */ + } + + log_info(LD_GENERAL, + "Initialized libevent version %s using method %s. Good.", + event_get_version(), tor_libevent_get_method()); +} + +/** Return the current Libevent event base that we're set up to use. */ +MOCK_IMPL(struct event_base *, +tor_libevent_get_base, (void)) +{ + tor_assert(the_event_base != NULL); + return the_event_base; +} + +/** Return the name of the Libevent backend we're using. */ +const char * +tor_libevent_get_method(void) +{ + return event_base_get_method(the_event_base); +} + +/** Return a string representation of the version of the currently running + * version of Libevent. */ +const char * +tor_libevent_get_version_str(void) +{ + return event_get_version(); +} + +/** Return a string representation of the version of Libevent that was used +* at compilation time. */ +const char * +tor_libevent_get_header_version_str(void) +{ + return LIBEVENT_VERSION; +} + +/** Represents a timer that's run every N microseconds by Libevent. */ +struct periodic_timer_t { + /** Underlying event used to implement this periodic event. */ + struct event *ev; + /** The callback we'll be invoking whenever the event triggers */ + void (*cb)(struct periodic_timer_t *, void *); + /** User-supplied data for the callback */ + void *data; +}; + +/** Libevent callback to implement a periodic event. */ +static void +periodic_timer_cb(evutil_socket_t fd, short what, void *arg) +{ + periodic_timer_t *timer = arg; + (void) what; + (void) fd; + timer->cb(timer, timer->data); +} + +/** Create and schedule a new timer that will run every <b>tv</b> in + * the event loop of <b>base</b>. When the timer fires, it will + * run the timer in <b>cb</b> with the user-supplied data in <b>data</b>. */ +periodic_timer_t * +periodic_timer_new(struct event_base *base, + const struct timeval *tv, + void (*cb)(periodic_timer_t *timer, void *data), + void *data) +{ + periodic_timer_t *timer; + tor_assert(base); + tor_assert(tv); + tor_assert(cb); + timer = tor_malloc_zero(sizeof(periodic_timer_t)); + if (!(timer->ev = tor_event_new(base, -1, EV_PERSIST, + periodic_timer_cb, timer))) { + tor_free(timer); + return NULL; + } + timer->cb = cb; + timer->data = data; + periodic_timer_launch(timer, tv); + return timer; +} + +/** + * Launch the timer <b>timer</b> to run at <b>tv</b> from now, and every + * <b>tv</b> thereafter. + * + * If the timer is already enabled, this function does nothing. + */ +void +periodic_timer_launch(periodic_timer_t *timer, const struct timeval *tv) +{ + tor_assert(timer); + if (event_pending(timer->ev, EV_TIMEOUT, NULL)) + return; + event_add(timer->ev, tv); +} + +/** + * Disable the provided <b>timer</b>, but do not free it. + * + * You can reenable the same timer later with periodic_timer_launch. + * + * If the timer is already disabled, this function does nothing. + */ +void +periodic_timer_disable(periodic_timer_t *timer) +{ + tor_assert(timer); + (void) event_del(timer->ev); +} + +/** Stop and free a periodic timer */ +void +periodic_timer_free_(periodic_timer_t *timer) +{ + if (!timer) + return; + tor_event_free(timer->ev); + tor_free(timer); +} + +/** + * Type used to represent events that run directly from the main loop, + * either because they are activated from elsewhere in the code, or + * because they have a simple timeout. + * + * We use this type to avoid exposing Libevent's API throughout the rest + * of the codebase. + * + * This type can't be used for all events: it doesn't handle events that + * are triggered by signals or by sockets. + */ +struct mainloop_event_t { + struct event *ev; + void (*cb)(mainloop_event_t *, void *); + void *userdata; +}; + +/** + * Internal: Implements mainloop event using a libevent event. + */ +static void +mainloop_event_cb(evutil_socket_t fd, short what, void *arg) +{ + (void)fd; + (void)what; + mainloop_event_t *mev = arg; + mev->cb(mev, mev->userdata); +} + +/** + * As mainloop_event_cb, but implements a post-loop event. + */ +static void +mainloop_event_postloop_cb(evutil_socket_t fd, short what, void *arg) +{ + (void)fd; + (void)what; + + /* Note that if rescan_mainloop_ev is already activated, + * event_active() will do nothing: only the first post-loop event that + * happens each time through the event loop will cause it to be + * activated. + * + * Because event_active() puts events on a FIFO queue, every event + * that is made active _after_ rescan_mainloop_ev will get its + * callback run after rescan_mainloop_cb is called -- that is, on the + * next iteration of the loop. + */ + event_active(rescan_mainloop_ev, EV_READ, 1); + + mainloop_event_t *mev = arg; + mev->cb(mev, mev->userdata); +} + +/** + * Helper for mainloop_event_new() and mainloop_event_postloop_new(). + */ +static mainloop_event_t * +mainloop_event_new_impl(int postloop, + void (*cb)(mainloop_event_t *, void *), + void *userdata) +{ + tor_assert(cb); + + struct event_base *base = tor_libevent_get_base(); + mainloop_event_t *mev = tor_malloc_zero(sizeof(mainloop_event_t)); + mev->ev = tor_event_new(base, -1, 0, + postloop ? mainloop_event_postloop_cb : mainloop_event_cb, + mev); + tor_assert(mev->ev); + mev->cb = cb; + mev->userdata = userdata; + return mev; +} + +/** + * Create and return a new mainloop_event_t to run the function <b>cb</b>. + * + * When run, the callback function will be passed the mainloop_event_t + * and <b>userdata</b> as its arguments. The <b>userdata</b> pointer + * must remain valid for as long as the mainloop_event_t event exists: + * it is your responsibility to free it. + * + * The event is not scheduled by default: Use mainloop_event_activate() + * or mainloop_event_schedule() to make it run. + */ +mainloop_event_t * +mainloop_event_new(void (*cb)(mainloop_event_t *, void *), + void *userdata) +{ + return mainloop_event_new_impl(0, cb, userdata); +} + +/** + * As mainloop_event_new(), but create a post-loop event. + * + * A post-loop event behaves like any ordinary event, but any events + * that _it_ activates cannot run until Libevent has checked for other + * events at least once. + */ +mainloop_event_t * +mainloop_event_postloop_new(void (*cb)(mainloop_event_t *, void *), + void *userdata) +{ + return mainloop_event_new_impl(1, cb, userdata); +} + +/** + * Schedule <b>event</b> to run in the main loop, immediately. If it is + * not scheduled, it will run anyway. If it is already scheduled to run + * later, it will run now instead. This function will have no effect if + * the event is already scheduled to run. + * + * This function may only be called from the main thread. + */ +void +mainloop_event_activate(mainloop_event_t *event) +{ + tor_assert(event); + event_active(event->ev, EV_READ, 1); +} + +/** Schedule <b>event</b> to run in the main loop, after a delay of <b>tv</b>. + * + * If the event is scheduled for a different time, cancel it and run + * after this delay instead. If the event is currently pending to run + * <em>now</b>, has no effect. + * + * Do not call this function with <b>tv</b> == NULL -- use + * mainloop_event_activate() instead. + * + * This function may only be called from the main thread. + */ +int +mainloop_event_schedule(mainloop_event_t *event, const struct timeval *tv) +{ + tor_assert(event); + if (BUG(tv == NULL)) { + // LCOV_EXCL_START + mainloop_event_activate(event); + return 0; + // LCOV_EXCL_STOP + } + return event_add(event->ev, tv); +} + +/** Cancel <b>event</b> if it is currently active or pending. (Do nothing if + * the event is not currently active or pending.) */ +void +mainloop_event_cancel(mainloop_event_t *event) +{ + if (!event) + return; + (void) event_del(event->ev); +} + +/** Cancel <b>event</b> and release all storage associated with it. */ +void +mainloop_event_free_(mainloop_event_t *event) +{ + if (!event) + return; + tor_event_free(event->ev); + memset(event, 0xb8, sizeof(*event)); + tor_free(event); +} + +int +tor_init_libevent_rng(void) +{ + int rv = 0; + char buf[256]; + if (evutil_secure_rng_init() < 0) { + rv = -1; + } + crypto_rand(buf, 32); +#ifdef HAVE_EVUTIL_SECURE_RNG_ADD_BYTES + evutil_secure_rng_add_bytes(buf, 32); +#endif + evutil_secure_rng_get_bytes(buf, sizeof(buf)); + return rv; +} + +/** + * Un-initialize libevent in preparation for an exit + */ +void +tor_libevent_free_all(void) +{ + tor_event_free(rescan_mainloop_ev); + if (the_event_base) + event_base_free(the_event_base); + the_event_base = NULL; +} + +/** + * Run the event loop for the provided event_base, handling events until + * something stops it. If <b>once</b> is set, then just poll-and-run + * once, then exit. Return 0 on success, -1 if an error occurred, or 1 + * if we exited because no events were pending or active. + * + * This isn't reentrant or multithreaded. + */ +int +tor_libevent_run_event_loop(struct event_base *base, int once) +{ + const int flags = once ? EVLOOP_ONCE : 0; + return event_base_loop(base, flags); +} + +/** Tell the event loop to exit after <b>delay</b>. If <b>delay</b> is NULL, + * instead exit after we're done running the currently active events. */ +void +tor_libevent_exit_loop_after_delay(struct event_base *base, + const struct timeval *delay) +{ + event_base_loopexit(base, delay); +} + +/** Tell the event loop to exit after running whichever callback is currently + * active. */ +void +tor_libevent_exit_loop_after_callback(struct event_base *base) +{ + event_base_loopbreak(base); +} + +#if defined(TOR_UNIT_TESTS) +/** For testing: called post-fork to make libevent reinitialize + * kernel structures. */ +void +tor_libevent_postfork(void) +{ + int r = event_reinit(tor_libevent_get_base()); + tor_assert(r == 0); +} +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/lib/evloop/compat_libevent.h b/src/lib/evloop/compat_libevent.h new file mode 100644 index 0000000000..afe887a013 --- /dev/null +++ b/src/lib/evloop/compat_libevent.h @@ -0,0 +1,104 @@ +/* Copyright (c) 2009-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_libevent.h + * \brief Header for compat_libevent.c + **/ + +#ifndef TOR_COMPAT_LIBEVENT_H +#define TOR_COMPAT_LIBEVENT_H + +#include "orconfig.h" +#include "lib/testsupport/testsupport.h" +#include "lib/malloc/malloc.h" + +void configure_libevent_logging(void); +void suppress_libevent_log_msg(const char *msg); + +#define tor_event_new event_new +#define tor_evtimer_new evtimer_new +#define tor_evsignal_new evsignal_new +#define tor_evdns_add_server_port(sock, tcp, cb, data) \ + evdns_add_server_port_with_base(tor_libevent_get_base(), \ + (sock),(tcp),(cb),(data)); + +struct event; +struct event_base; +struct timeval; + +void tor_event_free_(struct event *ev); +#define tor_event_free(ev) \ + FREE_AND_NULL(struct event, tor_event_free_, (ev)) + +typedef struct periodic_timer_t periodic_timer_t; + +periodic_timer_t *periodic_timer_new(struct event_base *base, + const struct timeval *tv, + void (*cb)(periodic_timer_t *timer, void *data), + void *data); +void periodic_timer_free_(periodic_timer_t *); +void periodic_timer_launch(periodic_timer_t *, const struct timeval *tv); +void periodic_timer_disable(periodic_timer_t *); +#define periodic_timer_free(t) \ + FREE_AND_NULL(periodic_timer_t, periodic_timer_free_, (t)) + +typedef struct mainloop_event_t mainloop_event_t; +mainloop_event_t *mainloop_event_new(void (*cb)(mainloop_event_t *, void *), + void *userdata); +mainloop_event_t * mainloop_event_postloop_new( + void (*cb)(mainloop_event_t *, void *), + void *userdata); +void mainloop_event_activate(mainloop_event_t *event); +int mainloop_event_schedule(mainloop_event_t *event, + const struct timeval *delay); +void mainloop_event_cancel(mainloop_event_t *event); +void mainloop_event_free_(mainloop_event_t *event); +#define mainloop_event_free(event) \ + FREE_AND_NULL(mainloop_event_t, mainloop_event_free_, (event)) + +/** Defines a configuration for using libevent with Tor: passed as an argument + * to tor_libevent_initialize() to describe how we want to set up. */ +typedef struct tor_libevent_cfg { + /** How many CPUs should we use (not currently useful). */ + int num_cpus; + /** How many milliseconds should we allow between updating bandwidth limits? + * (Not currently useful). */ + int msec_per_tick; +} tor_libevent_cfg; + +void tor_libevent_initialize(tor_libevent_cfg *cfg); +MOCK_DECL(struct event_base *, tor_libevent_get_base, (void)); +const char *tor_libevent_get_method(void); +void tor_check_libevent_header_compatibility(void); +const char *tor_libevent_get_version_str(void); +const char *tor_libevent_get_header_version_str(void); +void tor_libevent_free_all(void); + +int tor_init_libevent_rng(void); + +#ifdef TOR_UNIT_TESTS +void tor_libevent_postfork(void); +#endif + +int tor_libevent_run_event_loop(struct event_base *base, int once); +void tor_libevent_exit_loop_after_delay(struct event_base *base, + const struct timeval *delay); +void tor_libevent_exit_loop_after_callback(struct event_base *base); + +#ifdef COMPAT_LIBEVENT_PRIVATE + +/** Macro: returns the number of a Libevent version as a 4-byte number, + with the first three bytes representing the major, minor, and patchlevel + respectively of the library. The fourth byte is unused. + + This is equivalent to the format of LIBEVENT_VERSION_NUMBER on Libevent + 2.0.1 or later. */ +#define V(major, minor, patch) \ + (((major) << 24) | ((minor) << 16) | ((patch) << 8)) + +STATIC void +libevent_logging_callback(int severity, const char *msg); +#endif /* defined(COMPAT_LIBEVENT_PRIVATE) */ + +#endif /* !defined(TOR_COMPAT_LIBEVENT_H) */ diff --git a/src/lib/evloop/include.am b/src/lib/evloop/include.am new file mode 100644 index 0000000000..6b0076272a --- /dev/null +++ b/src/lib/evloop/include.am @@ -0,0 +1,26 @@ + +noinst_LIBRARIES += src/lib/libtor-evloop.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-evloop-testing.a +endif + +src_lib_libtor_evloop_a_SOURCES = \ + src/lib/evloop/compat_libevent.c \ + src/lib/evloop/procmon.c \ + src/lib/evloop/timers.c \ + src/lib/evloop/token_bucket.c \ + src/lib/evloop/workqueue.c + + +src_lib_libtor_evloop_testing_a_SOURCES = \ + $(src_lib_libtor_evloop_a_SOURCES) +src_lib_libtor_evloop_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_evloop_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/evloop/compat_libevent.h \ + src/lib/evloop/procmon.h \ + src/lib/evloop/timers.h \ + src/lib/evloop/token_bucket.h \ + src/lib/evloop/workqueue.h diff --git a/src/lib/evloop/procmon.c b/src/lib/evloop/procmon.c new file mode 100644 index 0000000000..52469fa5fc --- /dev/null +++ b/src/lib/evloop/procmon.c @@ -0,0 +1,339 @@ +/* Copyright (c) 2011-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file procmon.c + * \brief Process-termination monitor functions + **/ + +#include "lib/evloop/procmon.h" + +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/log/win32err.h" +#include "lib/malloc/malloc.h" +#include "lib/string/parse_int.h" + +#ifdef HAVE_SIGNAL_H +#include <signal.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#endif + +#if (0 == SIZEOF_PID_T) && defined(_WIN32) +/* Windows does not define pid_t sometimes, but _getpid() returns an int. + * Everybody else needs to have a pid_t. */ +typedef int pid_t; +#define PID_T_FORMAT "%d" +#elif (SIZEOF_PID_T == SIZEOF_INT) || (SIZEOF_PID_T == SIZEOF_SHORT) +#define PID_T_FORMAT "%d" +#elif (SIZEOF_PID_T == SIZEOF_LONG) +#define PID_T_FORMAT "%ld" +#elif (SIZEOF_PID_T == 8) +#define PID_T_FORMAT "%"PRId64 +#else +#error Unknown: SIZEOF_PID_T +#endif /* (0 == SIZEOF_PID_T) && defined(_WIN32) || ... */ + +/* Define to 1 if process-termination monitors on this OS and Libevent + version must poll for process termination themselves. */ +#define PROCMON_POLLS 1 +/* Currently we need to poll in some way on all systems. */ + +#ifdef PROCMON_POLLS +static void tor_process_monitor_poll_cb(periodic_timer_t *ev, + void *procmon_); +#endif + +/* This struct may contain pointers into the original process + * specifier string, but it should *never* contain anything which + * needs to be freed. */ +/* DOCDOC parsed_process_specifier_t */ +struct parsed_process_specifier_t { + pid_t pid; +}; + +/** Parse the process specifier given in <b>process_spec</b> into + * *<b>ppspec</b>. Return 0 on success; return -1 and store an error + * message into *<b>msg</b> on failure. The caller must not free the + * returned error message. */ +static int +parse_process_specifier(const char *process_spec, + struct parsed_process_specifier_t *ppspec, + const char **msg) +{ + long pid_l; + int pid_ok = 0; + char *pspec_next; + + /* If we're lucky, long will turn out to be large enough to hold a + * PID everywhere that Tor runs. */ + pid_l = tor_parse_long(process_spec, 10, 1, LONG_MAX, &pid_ok, &pspec_next); + + /* Reserve room in the ‘process specifier’ for additional + * (platform-specific) identifying information beyond the PID, to + * make our process-existence checks a bit less racy in a future + * version. */ + if ((*pspec_next != 0) && (*pspec_next != ' ') && (*pspec_next != ':')) { + pid_ok = 0; + } + + ppspec->pid = (pid_t)(pid_l); + if (!pid_ok || (pid_l != (long)(ppspec->pid))) { + *msg = "invalid PID"; + goto err; + } + + return 0; + err: + return -1; +} + +/* DOCDOC tor_process_monitor_t */ +struct tor_process_monitor_t { + /** Log domain for warning messages. */ + log_domain_mask_t log_domain; + + /** All systems: The best we can do in general is poll for the + * process's existence by PID periodically, and hope that the kernel + * doesn't reassign the same PID to another process between our + * polls. */ + pid_t pid; + +#ifdef _WIN32 + /** Windows-only: Should we poll hproc? If false, poll pid + * instead. */ + int poll_hproc; + + /** Windows-only: Get a handle to the process (if possible) and + * periodically check whether the process we have a handle to has + * ended. */ + HANDLE hproc; + /* XXXX We should have Libevent watch hproc for us, + * if/when some version of Libevent can be told to do so. */ +#endif /* defined(_WIN32) */ + + /* XXXX On Linux, we can and should receive the 22nd + * (space-delimited) field (‘starttime’) of /proc/$PID/stat from the + * owning controller and store it, and poll once in a while to see + * whether it has changed -- if so, the kernel has *definitely* + * reassigned the owning controller's PID and we should exit. On + * FreeBSD, we can do the same trick using either the 8th + * space-delimited field of /proc/$PID/status on the seven FBSD + * systems whose admins have mounted procfs, or the start-time field + * of the process-information structure returned by kvmgetprocs() on + * any system. The latter is ickier. */ + + /* XXXX On FreeBSD (and possibly other kqueue systems), we can and + * should arrange to receive EVFILT_PROC NOTE_EXIT notifications for + * pid, so we don't have to do such a heavyweight poll operation in + * order to avoid the PID-reassignment race condition. (We would + * still need to poll our own kqueue periodically until some version + * of Libevent 2.x learns to receive these events for us.) */ + + /** A Libevent event structure, to either poll for the process's + * existence or receive a notification when the process ends. */ + periodic_timer_t *e; + + /** A callback to be called when the process ends. */ + tor_procmon_callback_t cb; + void *cb_arg; /**< A user-specified pointer to be passed to cb. */ +}; + +/** Verify that the process specifier given in <b>process_spec</b> is + * syntactically valid. Return 0 on success; return -1 and store an + * error message into *<b>msg</b> on failure. The caller must not + * free the returned error message. */ +int +tor_validate_process_specifier(const char *process_spec, + const char **msg) +{ + struct parsed_process_specifier_t ppspec; + + tor_assert(msg != NULL); + *msg = NULL; + + return parse_process_specifier(process_spec, &ppspec, msg); +} + +/* DOCDOC poll_interval_tv */ +static const struct timeval poll_interval_tv = {15, 0}; + +/** Create a process-termination monitor for the process specifier + * given in <b>process_spec</b>. Return a newly allocated + * tor_process_monitor_t on success; return NULL and store an error + * message into *<b>msg</b> on failure. The caller must not free + * the returned error message. + * + * When the monitored process terminates, call + * <b>cb</b>(<b>cb_arg</b>). + */ +tor_process_monitor_t * +tor_process_monitor_new(struct event_base *base, + const char *process_spec, + log_domain_mask_t log_domain, + tor_procmon_callback_t cb, void *cb_arg, + const char **msg) +{ + tor_process_monitor_t *procmon = tor_malloc_zero( + sizeof(tor_process_monitor_t)); + struct parsed_process_specifier_t ppspec; + + tor_assert(msg != NULL); + *msg = NULL; + + if (procmon == NULL) { + *msg = "out of memory"; + goto err; + } + + procmon->log_domain = log_domain; + + if (parse_process_specifier(process_spec, &ppspec, msg)) + goto err; + + procmon->pid = ppspec.pid; + +#ifdef _WIN32 + procmon->hproc = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, + FALSE, + procmon->pid); + + if (procmon->hproc != NULL) { + procmon->poll_hproc = 1; + log_info(procmon->log_domain, "Successfully opened handle to process " + PID_T_FORMAT"; " + "monitoring it.", + procmon->pid); + } else { + /* If we couldn't get a handle to the process, we'll try again the + * first time we poll. */ + log_info(procmon->log_domain, "Failed to open handle to process " + PID_T_FORMAT"; will " + "try again later.", + procmon->pid); + } +#endif /* defined(_WIN32) */ + + procmon->cb = cb; + procmon->cb_arg = cb_arg; + +#ifdef PROCMON_POLLS + procmon->e = periodic_timer_new(base, + &poll_interval_tv, + tor_process_monitor_poll_cb, procmon); +#else /* !(defined(PROCMON_POLLS)) */ +#error OOPS? +#endif /* defined(PROCMON_POLLS) */ + + return procmon; + err: + tor_process_monitor_free(procmon); + return NULL; +} + +#ifdef PROCMON_POLLS +/** Libevent callback to poll for the existence of the process + * monitored by <b>procmon_</b>. */ +static void +tor_process_monitor_poll_cb(periodic_timer_t *event, void *procmon_) +{ + (void)event; + tor_process_monitor_t *procmon = (tor_process_monitor_t *)(procmon_); + int its_dead_jim; + + tor_assert(procmon != NULL); + +#ifdef _WIN32 + if (procmon->poll_hproc) { + DWORD exit_code; + if (!GetExitCodeProcess(procmon->hproc, &exit_code)) { + char *errmsg = format_win32_error(GetLastError()); + log_warn(procmon->log_domain, "Error \"%s\" occurred while polling " + "handle for monitored process "PID_T_FORMAT"; assuming " + "it's dead.", + errmsg, procmon->pid); + tor_free(errmsg); + its_dead_jim = 1; + } else { + its_dead_jim = (exit_code != STILL_ACTIVE); + } + } else { + /* All we can do is try to open the process, and look at the error + * code if it fails again. */ + procmon->hproc = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, + FALSE, + procmon->pid); + + if (procmon->hproc != NULL) { + log_info(procmon->log_domain, "Successfully opened handle to monitored " + "process "PID_T_FORMAT".", + procmon->pid); + its_dead_jim = 0; + procmon->poll_hproc = 1; + } else { + DWORD err_code = GetLastError(); + char *errmsg = format_win32_error(err_code); + + /* When I tested OpenProcess's error codes on Windows 7, I + * received error code 5 (ERROR_ACCESS_DENIED) for PIDs of + * existing processes that I could not open and error code 87 + * (ERROR_INVALID_PARAMETER) for PIDs that were not in use. + * Since the nonexistent-process error code is sane, I'm going + * to assume that all errors other than ERROR_INVALID_PARAMETER + * mean that the process we are monitoring is still alive. */ + its_dead_jim = (err_code == ERROR_INVALID_PARAMETER); + + if (!its_dead_jim) + log_info(procmon->log_domain, "Failed to open handle to monitored " + "process "PID_T_FORMAT", and error code %lu (%s) is not " + "'invalid parameter' -- assuming the process is still alive.", + procmon->pid, + err_code, errmsg); + + tor_free(errmsg); + } + } +#else /* !(defined(_WIN32)) */ + /* Unix makes this part easy, if a bit racy. */ + its_dead_jim = kill(procmon->pid, 0); + its_dead_jim = its_dead_jim && (errno == ESRCH); +#endif /* defined(_WIN32) */ + + tor_log(its_dead_jim ? LOG_NOTICE : LOG_INFO, + procmon->log_domain, "Monitored process "PID_T_FORMAT" is %s.", + procmon->pid, + its_dead_jim ? "dead" : "still alive"); + + if (its_dead_jim) { + procmon->cb(procmon->cb_arg); + } +} +#endif /* defined(PROCMON_POLLS) */ + +/** Free the process-termination monitor <b>procmon</b>. */ +void +tor_process_monitor_free_(tor_process_monitor_t *procmon) +{ + if (procmon == NULL) + return; + +#ifdef _WIN32 + if (procmon->hproc != NULL) + CloseHandle(procmon->hproc); +#endif + + if (procmon->e != NULL) + periodic_timer_free(procmon->e); + + tor_free(procmon); +} diff --git a/src/lib/evloop/procmon.h b/src/lib/evloop/procmon.h new file mode 100644 index 0000000000..6caae5be86 --- /dev/null +++ b/src/lib/evloop/procmon.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2011-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file procmon.h + * \brief Headers for procmon.c + **/ + +#ifndef TOR_PROCMON_H +#define TOR_PROCMON_H + +#include "lib/evloop/compat_libevent.h" + +#include "lib/log/log.h" + +typedef struct tor_process_monitor_t tor_process_monitor_t; + +/* DOCDOC tor_procmon_callback_t */ +typedef void (*tor_procmon_callback_t)(void *); + +int tor_validate_process_specifier(const char *process_spec, + const char **msg); +tor_process_monitor_t *tor_process_monitor_new(struct event_base *base, + const char *process_spec, + log_domain_mask_t log_domain, + tor_procmon_callback_t cb, + void *cb_arg, + const char **msg); +void tor_process_monitor_free_(tor_process_monitor_t *procmon); +#define tor_process_monitor_free(procmon) \ + FREE_AND_NULL(tor_process_monitor_t, tor_process_monitor_free_, (procmon)) + +#endif /* !defined(TOR_PROCMON_H) */ + diff --git a/src/lib/evloop/timers.c b/src/lib/evloop/timers.c new file mode 100644 index 0000000000..e46d2635a8 --- /dev/null +++ b/src/lib/evloop/timers.c @@ -0,0 +1,328 @@ +/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file timers.c + * \brief Wrapper around William Ahern's fast hierarchical timer wheel + * implementation, to tie it in with a libevent backend. + * + * Only use these functions from the main thread. + * + * The main advantage of tor_timer_t over using libevent's timers is that + * they're way more efficient if we need to have thousands or millions of + * them. For more information, see + * http://www.25thandclement.com/~william/projects/timeout.c.html + * + * Periodic timers are available in the backend, but I've turned them off. + * We can turn them back on if needed. + */ + +/* Notes: + * + * Having a way to free all timers on shutdown would free people from the + * need to track them. Not sure if that's clever though. + * + * In an ideal world, Libevent would just switch to use this backend, and we + * could throw this file away. But even if Libevent does switch, we'll be + * stuck with legacy libevents for some time. + */ + +#include "orconfig.h" + +#define TOR_TIMERS_PRIVATE + +#include "lib/evloop/compat_libevent.h" +#include "lib/evloop/timers.h" +#include "lib/intmath/muldiv.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/time/compat_time.h" + +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#ifdef _WIN32 +// For struct timeval. +#include <winsock2.h> +#endif + +struct timeout_cb { + timer_cb_fn_t cb; + void *arg; +}; + +/* + * These definitions are for timeouts.c and timeouts.h. + */ +#ifdef __GNUC__ +/* We're not exposing any of the functions outside this file. */ +#define TIMEOUT_PUBLIC __attribute__((__unused__)) static +#else +/* We're not exposing any of the functions outside this file. */ +#define TIMEOUT_PUBLIC static +#endif /* defined(__GNUC__) */ +/* We're not using periodic events. */ +#define TIMEOUT_DISABLE_INTERVALS +/* We always know the global_timeouts object, so we don't need each timeout + * to keep a pointer to it. */ +#define TIMEOUT_DISABLE_RELATIVE_ACCESS +/* We're providing our own struct timeout_cb. */ +#define TIMEOUT_CB_OVERRIDE +/* We're going to support timers that are pretty far out in advance. Making + * this big can be inefficient, but having a significant number of timers + * above TIMEOUT_MAX can also be super-inefficient. Choosing 5 here sets + * timeout_max to 2^30 ticks, or 29 hours with our value for USEC_PER_TICK */ +#define WHEEL_NUM 5 +#if SIZEOF_VOID_P == 4 +/* On 32-bit platforms, we want to override wheel_bit, so that timeout.c will + * use 32-bit math. */ +#define WHEEL_BIT 5 +#endif +#include "src/ext/timeouts/timeout.c" + +static struct timeouts *global_timeouts = NULL; +static struct mainloop_event_t *global_timer_event = NULL; + +static monotime_t start_of_time; + +/** We need to choose this value carefully. Because we're using timer wheels, + * it actually costs us to have extra resolution we don't use. So for now, + * I'm going to define our resolution as .1 msec, and hope that's good enough. + * + * Note that two of the most popular libevent backends (epoll without timerfd, + * and windows select), simply can't support sub-millisecond resolution, + * do this is optimistic for a lot of users. + */ +#define USEC_PER_TICK 100 + +/** One million microseconds in a second */ +#define USEC_PER_SEC 1000000 + +/** Check at least once every N seconds. */ +#define MIN_CHECK_SECONDS 3600 + +/** Check at least once every N ticks. */ +#define MIN_CHECK_TICKS \ + (((timeout_t)MIN_CHECK_SECONDS) * (1000000 / USEC_PER_TICK)) + +/** + * Convert the timeval in <b>tv</b> to a timeout_t, and return it. + * + * The output resolution is set by USEC_PER_TICK. Only use this to convert + * delays to number of ticks; the time represented by 0 is undefined. + */ +static timeout_t +tv_to_timeout(const struct timeval *tv) +{ + uint64_t usec = tv->tv_usec; + usec += ((uint64_t)USEC_PER_SEC) * tv->tv_sec; + return usec / USEC_PER_TICK; +} + +/** + * Convert the timeout in <b>t</b> to a timeval in <b>tv_out</b>. Only + * use this for delays, not absolute times. + */ +static void +timeout_to_tv(timeout_t t, struct timeval *tv_out) +{ + t *= USEC_PER_TICK; + tv_out->tv_usec = (int)(t % USEC_PER_SEC); + tv_out->tv_sec = (time_t)(t / USEC_PER_SEC); +} + +/** + * Update the timer <b>tv</b> to the current time in <b>tv</b>. + */ +static void +timer_advance_to_cur_time(const monotime_t *now) +{ + timeout_t cur_tick = CEIL_DIV(monotime_diff_usec(&start_of_time, now), + USEC_PER_TICK); + timeouts_update(global_timeouts, cur_tick); +} + +/** + * Adjust the time at which the libevent timer should fire based on + * the next-expiring time in <b>global_timeouts</b> + */ +static void +libevent_timer_reschedule(void) +{ + monotime_t now; + monotime_get(&now); + timer_advance_to_cur_time(&now); + + timeout_t delay = timeouts_timeout(global_timeouts); + + struct timeval d; + if (delay > MIN_CHECK_TICKS) + delay = MIN_CHECK_TICKS; + timeout_to_tv(delay, &d); + mainloop_event_schedule(global_timer_event, &d); +} + +/** Run the callback of every timer that has expired, based on the current + * output of monotime_get(). */ +STATIC void +timers_run_pending(void) +{ + monotime_t now; + monotime_get(&now); + timer_advance_to_cur_time(&now); + + tor_timer_t *t; + while ((t = timeouts_get(global_timeouts))) { + t->callback.cb(t, t->callback.arg, &now); + } +} + +/** + * Invoked when the libevent timer has expired: see which tor_timer_t events + * have fired, activate their callbacks, and reschedule the libevent timer. + */ +static void +libevent_timer_callback(mainloop_event_t *ev, void *arg) +{ + (void)ev; + (void)arg; + + timers_run_pending(); + + libevent_timer_reschedule(); +} + +/** + * Initialize the timers subsystem. Requires that libevent has already been + * initialized. + */ +void +timers_initialize(void) +{ + if (BUG(global_timeouts)) + return; // LCOV_EXCL_LINE + + timeout_error_t err = 0; + global_timeouts = timeouts_open(0, &err); + if (!global_timeouts) { + // LCOV_EXCL_START -- this can only fail on malloc failure. + log_err(LD_BUG, "Unable to open timer backend: %s", strerror(err)); + tor_assert(0); + // LCOV_EXCL_STOP + } + + monotime_init(); + monotime_get(&start_of_time); + + mainloop_event_t *timer_event; + timer_event = mainloop_event_new(libevent_timer_callback, NULL); + tor_assert(timer_event); + global_timer_event = timer_event; + + libevent_timer_reschedule(); +} + +/** + * Release all storage held in the timers subsystem. Does not fire timers. + */ +void +timers_shutdown(void) +{ + if (global_timer_event) { + mainloop_event_free(global_timer_event); + global_timer_event = NULL; + } + if (global_timeouts) { + timeouts_close(global_timeouts); + global_timeouts = NULL; + } +} + +/** + * Allocate and return a new timer, with given callback and argument. + */ +tor_timer_t * +timer_new(timer_cb_fn_t cb, void *arg) +{ + tor_timer_t *t = tor_malloc(sizeof(tor_timer_t)); + timeout_init(t, 0); + timer_set_cb(t, cb, arg); + return t; +} + +/** + * Release all storage held by <b>t</b>, and unschedule it if was already + * scheduled. + */ +void +timer_free_(tor_timer_t *t) +{ + if (! t) + return; + + timeouts_del(global_timeouts, t); + tor_free(t); +} + +/** + * Change the callback and argument associated with a timer <b>t</b>. + */ +void +timer_set_cb(tor_timer_t *t, timer_cb_fn_t cb, void *arg) +{ + t->callback.cb = cb; + t->callback.arg = arg; +} + +/** + * Set *<b>cb_out</b> (if provided) to this timer's callback function, + * and *<b>arg_out</b> (if provided) to this timer's callback argument. + */ +void +timer_get_cb(const tor_timer_t *t, + timer_cb_fn_t *cb_out, void **arg_out) +{ + if (cb_out) + *cb_out = t->callback.cb; + if (arg_out) + *arg_out = t->callback.arg; +} + +/** + * Schedule the timer t to fire at the current time plus a delay of + * <b>delay</b> microseconds. All times are relative to monotime_get(). + */ +void +timer_schedule(tor_timer_t *t, const struct timeval *tv) +{ + const timeout_t delay = tv_to_timeout(tv); + + monotime_t now; + monotime_get(&now); + timer_advance_to_cur_time(&now); + + /* Take the old timeout value. */ + timeout_t to = timeouts_timeout(global_timeouts); + + timeouts_add(global_timeouts, t, delay); + + /* Should we update the libevent timer? */ + if (to <= delay) { + return; /* we're already going to fire before this timer would trigger. */ + } + libevent_timer_reschedule(); +} + +/** + * Cancel the timer <b>t</b> if it is currently scheduled. (It's okay to call + * this on an unscheduled timer. + */ +void +timer_disable(tor_timer_t *t) +{ + timeouts_del(global_timeouts, t); + /* We don't reschedule the libevent timer here, since it's okay if it fires + * early. */ +} diff --git a/src/lib/evloop/timers.h b/src/lib/evloop/timers.h new file mode 100644 index 0000000000..7595554204 --- /dev/null +++ b/src/lib/evloop/timers.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2016-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file timers.h + * \brief Header for timers.c + **/ + +#ifndef TOR_TIMERS_H +#define TOR_TIMERS_H + +#include "orconfig.h" +#include "lib/testsupport/testsupport.h" + +struct monotime_t; +typedef struct timeout tor_timer_t; +typedef void (*timer_cb_fn_t)(tor_timer_t *, void *, + const struct monotime_t *); +tor_timer_t *timer_new(timer_cb_fn_t cb, void *arg); +void timer_set_cb(tor_timer_t *t, timer_cb_fn_t cb, void *arg); +void timer_get_cb(const tor_timer_t *t, + timer_cb_fn_t *cb_out, void **arg_out); +void timer_schedule(tor_timer_t *t, const struct timeval *delay); +void timer_disable(tor_timer_t *t); +void timer_free_(tor_timer_t *t); +#define timer_free(t) FREE_AND_NULL(tor_timer_t, timer_free_, (t)) + +void timers_initialize(void); +void timers_shutdown(void); + +#ifdef TOR_TIMERS_PRIVATE +STATIC void timers_run_pending(void); +#endif + +#endif /* !defined(TOR_TIMERS_H) */ diff --git a/src/lib/evloop/token_bucket.c b/src/lib/evloop/token_bucket.c new file mode 100644 index 0000000000..ee6d631e3b --- /dev/null +++ b/src/lib/evloop/token_bucket.c @@ -0,0 +1,258 @@ +/* Copyright (c) 2018-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file token_bucket.c + * \brief Functions to use and manipulate token buckets, used for + * rate-limiting on connections and globally. + * + * Tor uses these token buckets to keep track of bandwidth usage, and + * sometimes other things too. + * + * There are two layers of abstraction here: "raw" token buckets, in which all + * the pieces are decoupled, and "read-write" token buckets, which combine all + * the moving parts into one. + * + * Token buckets may become negative. + **/ + +#define TOKEN_BUCKET_PRIVATE + +#include "lib/evloop/token_bucket.h" +#include "lib/log/util_bug.h" +#include "lib/intmath/cmp.h" +#include "lib/time/compat_time.h" + +#include <string.h> + +/** + * Set the <b>rate</b> and <b>burst</b> value in a token_bucket_cfg. + * + * Note that the <b>rate</b> value is in arbitrary units, but those units will + * determine the units of token_bucket_raw_dec(), token_bucket_raw_refill, and + * so on. + */ +void +token_bucket_cfg_init(token_bucket_cfg_t *cfg, + uint32_t rate, + uint32_t burst) +{ + tor_assert_nonfatal(rate > 0); + tor_assert_nonfatal(burst > 0); + if (burst > TOKEN_BUCKET_MAX_BURST) + burst = TOKEN_BUCKET_MAX_BURST; + + cfg->rate = rate; + cfg->burst = burst; +} + +/** + * Initialize a raw token bucket and its associated timestamp to the "full" + * state, according to <b>cfg</b>. + */ +void +token_bucket_raw_reset(token_bucket_raw_t *bucket, + const token_bucket_cfg_t *cfg) +{ + bucket->bucket = cfg->burst; +} + +/** + * Adust a preexisting token bucket to respect the new configuration + * <b>cfg</b>, by decreasing its current level if needed. */ +void +token_bucket_raw_adjust(token_bucket_raw_t *bucket, + const token_bucket_cfg_t *cfg) +{ + bucket->bucket = MIN(bucket->bucket, cfg->burst); +} + +/** + * Given an amount of <b>elapsed</b> time units, and a bucket configuration + * <b>cfg</b>, refill the level of <b>bucket</b> accordingly. Note that the + * units of time in <b>elapsed</b> must correspond to those used to set the + * rate in <b>cfg</b>, or the result will be illogical. + */ +int +token_bucket_raw_refill_steps(token_bucket_raw_t *bucket, + const token_bucket_cfg_t *cfg, + const uint32_t elapsed) +{ + const int was_empty = (bucket->bucket <= 0); + /* The casts here prevent an underflow. + * + * Note that even if the bucket value is negative, subtracting it from + * "burst" will still produce a correct result. If this result is + * ridiculously high, then the "elapsed > gap / rate" check below + * should catch it. */ + const size_t gap = ((size_t)cfg->burst) - ((size_t)bucket->bucket); + + if (elapsed > gap / cfg->rate) { + bucket->bucket = cfg->burst; + } else { + bucket->bucket += cfg->rate * elapsed; + } + + return was_empty && bucket->bucket > 0; +} + +/** + * Decrement a provided bucket by <b>n</b> units. Note that <b>n</b> + * must be nonnegative. + */ +int +token_bucket_raw_dec(token_bucket_raw_t *bucket, + ssize_t n) +{ + if (BUG(n < 0)) + return 0; + const int becomes_empty = bucket->bucket > 0 && n >= bucket->bucket; + bucket->bucket -= n; + return becomes_empty; +} + +/** Convert a rate in bytes per second to a rate in bytes per step */ +STATIC uint32_t +rate_per_sec_to_rate_per_step(uint32_t rate) +{ + /* + The precise calculation we'd want to do is + + (rate / 1000) * to_approximate_msec(TICKS_PER_STEP). But to minimize + rounding error, we do it this way instead, and divide last. + */ + uint64_t units = (uint64_t) rate * TICKS_PER_STEP; + uint32_t val = (uint32_t) + (monotime_coarse_stamp_units_to_approx_msec(units) / 1000); + return val ? val : 1; +} + +/** + * Initialize a token bucket in *<b>bucket</b>, set up to allow <b>rate</b> + * bytes per second, with a maximum burst of <b>burst</b> bytes. The bucket + * is created such that <b>now_ts</b> is the current timestamp. The bucket + * starts out full. + */ +void +token_bucket_rw_init(token_bucket_rw_t *bucket, + uint32_t rate, + uint32_t burst, + uint32_t now_ts) +{ + memset(bucket, 0, sizeof(token_bucket_rw_t)); + token_bucket_rw_adjust(bucket, rate, burst); + token_bucket_rw_reset(bucket, now_ts); +} + +/** + * Change the configured rate (in bytes per second) and burst (in bytes) + * for the token bucket in *<b>bucket</b>. + */ +void +token_bucket_rw_adjust(token_bucket_rw_t *bucket, + uint32_t rate, + uint32_t burst) +{ + token_bucket_cfg_init(&bucket->cfg, + rate_per_sec_to_rate_per_step(rate), + burst); + token_bucket_raw_adjust(&bucket->read_bucket, &bucket->cfg); + token_bucket_raw_adjust(&bucket->write_bucket, &bucket->cfg); +} + +/** + * Reset <b>bucket</b> to be full, as of timestamp <b>now_ts</b>. + */ +void +token_bucket_rw_reset(token_bucket_rw_t *bucket, + uint32_t now_ts) +{ + token_bucket_raw_reset(&bucket->read_bucket, &bucket->cfg); + token_bucket_raw_reset(&bucket->write_bucket, &bucket->cfg); + bucket->last_refilled_at_timestamp = now_ts; +} + +/** + * Refill <b>bucket</b> as appropriate, given that the current timestamp + * is <b>now_ts</b>. + * + * Return a bitmask containing TB_READ iff read bucket was empty and became + * nonempty, and TB_WRITE iff the write bucket was empty and became nonempty. + */ +int +token_bucket_rw_refill(token_bucket_rw_t *bucket, + uint32_t now_ts) +{ + const uint32_t elapsed_ticks = + (now_ts - bucket->last_refilled_at_timestamp); + if (elapsed_ticks > UINT32_MAX-(300*1000)) { + /* Either about 48 days have passed since the last refill, or the + * monotonic clock has somehow moved backwards. (We're looking at you, + * Windows.). We accept up to a 5 minute jump backwards as + * "unremarkable". + */ + return 0; + } + const uint32_t elapsed_steps = elapsed_ticks / TICKS_PER_STEP; + + if (!elapsed_steps) { + /* Note that if less than one whole step elapsed, we don't advance the + * time in last_refilled_at. That's intentional: we want to make sure + * that we add some bytes to it eventually. */ + return 0; + } + + int flags = 0; + if (token_bucket_raw_refill_steps(&bucket->read_bucket, + &bucket->cfg, elapsed_steps)) + flags |= TB_READ; + if (token_bucket_raw_refill_steps(&bucket->write_bucket, + &bucket->cfg, elapsed_steps)) + flags |= TB_WRITE; + + bucket->last_refilled_at_timestamp = now_ts; + return flags; +} + +/** + * Decrement the read token bucket in <b>bucket</b> by <b>n</b> bytes. + * + * Return true if the bucket was nonempty and became empty; return false + * otherwise. + */ +int +token_bucket_rw_dec_read(token_bucket_rw_t *bucket, + ssize_t n) +{ + return token_bucket_raw_dec(&bucket->read_bucket, n); +} + +/** + * Decrement the write token bucket in <b>bucket</b> by <b>n</b> bytes. + * + * Return true if the bucket was nonempty and became empty; return false + * otherwise. + */ +int +token_bucket_rw_dec_write(token_bucket_rw_t *bucket, + ssize_t n) +{ + return token_bucket_raw_dec(&bucket->write_bucket, n); +} + +/** + * As token_bucket_rw_dec_read and token_bucket_rw_dec_write, in a single + * operation. Return a bitmask of TB_READ and TB_WRITE to indicate + * which buckets became empty. + */ +int +token_bucket_rw_dec(token_bucket_rw_t *bucket, + ssize_t n_read, ssize_t n_written) +{ + int flags = 0; + if (token_bucket_rw_dec_read(bucket, n_read)) + flags |= TB_READ; + if (token_bucket_rw_dec_write(bucket, n_written)) + flags |= TB_WRITE; + return flags; +} diff --git a/src/lib/evloop/token_bucket.h b/src/lib/evloop/token_bucket.h new file mode 100644 index 0000000000..9398d2baa3 --- /dev/null +++ b/src/lib/evloop/token_bucket.h @@ -0,0 +1,117 @@ +/* Copyright (c) 2018-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file token_bucket.h + * \brief Headers for token_bucket.c + **/ + +#ifndef TOR_TOKEN_BUCKET_H +#define TOR_TOKEN_BUCKET_H + +#include "lib/cc/torint.h" +#include "lib/testsupport/testsupport.h" + +/** Largest allowable burst value for a token buffer. */ +#define TOKEN_BUCKET_MAX_BURST INT32_MAX + +/** A generic token buffer configuration: determines the number of tokens + * added to the bucket in each time unit (the "rate"), and the maximum number + * of tokens in the bucket (the "burst") */ +typedef struct token_bucket_cfg_t { + uint32_t rate; + int32_t burst; +} token_bucket_cfg_t; + +/** A raw token bucket, decoupled from its configuration and timestamp. */ +typedef struct token_bucket_raw_t { + int32_t bucket; +} token_bucket_raw_t; + +void token_bucket_cfg_init(token_bucket_cfg_t *cfg, + uint32_t rate, + uint32_t burst); + +void token_bucket_raw_adjust(token_bucket_raw_t *bucket, + const token_bucket_cfg_t *cfg); + +void token_bucket_raw_reset(token_bucket_raw_t *bucket, + const token_bucket_cfg_t *cfg); + +int token_bucket_raw_dec(token_bucket_raw_t *bucket, + ssize_t n); + +int token_bucket_raw_refill_steps(token_bucket_raw_t *bucket, + const token_bucket_cfg_t *cfg, + const uint32_t elapsed_steps); + +static inline size_t token_bucket_raw_get(const token_bucket_raw_t *bucket); +/** Return the current number of bytes set in a token bucket. */ +static inline size_t +token_bucket_raw_get(const token_bucket_raw_t *bucket) +{ + return bucket->bucket >= 0 ? bucket->bucket : 0; +} + +/** A convenience type containing all the pieces needed for a coupled + * read-bucket and write-bucket that have the same rate limit, and which use + * "timestamp units" (see compat_time.h) for their time. */ +typedef struct token_bucket_rw_t { + token_bucket_cfg_t cfg; + token_bucket_raw_t read_bucket; + token_bucket_raw_t write_bucket; + uint32_t last_refilled_at_timestamp; +} token_bucket_rw_t; + +void token_bucket_rw_init(token_bucket_rw_t *bucket, + uint32_t rate, + uint32_t burst, + uint32_t now_ts); + +void token_bucket_rw_adjust(token_bucket_rw_t *bucket, + uint32_t rate, uint32_t burst); + +void token_bucket_rw_reset(token_bucket_rw_t *bucket, + uint32_t now_ts); + +#define TB_READ 1 +#define TB_WRITE 2 + +int token_bucket_rw_refill(token_bucket_rw_t *bucket, + uint32_t now_ts); + +int token_bucket_rw_dec_read(token_bucket_rw_t *bucket, + ssize_t n); +int token_bucket_rw_dec_write(token_bucket_rw_t *bucket, + ssize_t n); + +int token_bucket_rw_dec(token_bucket_rw_t *bucket, + ssize_t n_read, ssize_t n_written); + +static inline size_t token_bucket_rw_get_read(const token_bucket_rw_t *bucket); +static inline size_t +token_bucket_rw_get_read(const token_bucket_rw_t *bucket) +{ + return token_bucket_raw_get(&bucket->read_bucket); +} + +static inline size_t token_bucket_rw_get_write( + const token_bucket_rw_t *bucket); +static inline size_t +token_bucket_rw_get_write(const token_bucket_rw_t *bucket) +{ + return token_bucket_raw_get(&bucket->write_bucket); +} + +#ifdef TOKEN_BUCKET_PRIVATE + +/* To avoid making the rates too small, we consider units of "steps", + * where a "step" is defined as this many timestamp ticks. Keep this + * a power of two if you can. */ +#define TICKS_PER_STEP 16 + +STATIC uint32_t rate_per_sec_to_rate_per_step(uint32_t rate); + +#endif + +#endif /* TOR_TOKEN_BUCKET_H */ diff --git a/src/lib/evloop/workqueue.c b/src/lib/evloop/workqueue.c new file mode 100644 index 0000000000..931f65e710 --- /dev/null +++ b/src/lib/evloop/workqueue.c @@ -0,0 +1,682 @@ + +/* copyright (c) 2013-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file workqueue.c + * + * \brief Implements worker threads, queues of work for them, and mechanisms + * for them to send answers back to the main thread. + * + * The main structure here is a threadpool_t : it manages a set of worker + * threads, a queue of pending work, and a reply queue. Every piece of work + * is a workqueue_entry_t, containing data to process and a function to + * process it with. + * + * The main thread informs the worker threads of pending work by using a + * condition variable. The workers inform the main process of completed work + * by using an alert_sockets_t object, as implemented in compat_threads.c. + * + * The main thread can also queue an "update" that will be handled by all the + * workers. This is useful for updating state that all the workers share. + * + * In Tor today, there is currently only one thread pool, used in cpuworker.c. + */ + +#include "orconfig.h" +#include "lib/evloop/compat_libevent.h" +#include "lib/evloop/workqueue.h" + +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/intmath/weakrng.h" +#include "lib/log/ratelim.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/net/alertsock.h" +#include "lib/net/socket.h" +#include "lib/thread/threads.h" + +#include "tor_queue.h" +#include <event2/event.h> +#include <string.h> + +#define WORKQUEUE_PRIORITY_FIRST WQ_PRI_HIGH +#define WORKQUEUE_PRIORITY_LAST WQ_PRI_LOW +#define WORKQUEUE_N_PRIORITIES (((int) WORKQUEUE_PRIORITY_LAST)+1) + +TOR_TAILQ_HEAD(work_tailq_t, workqueue_entry_s); +typedef struct work_tailq_t work_tailq_t; + +struct threadpool_s { + /** An array of pointers to workerthread_t: one for each running worker + * thread. */ + struct workerthread_s **threads; + + /** Condition variable that we wait on when we have no work, and which + * gets signaled when our queue becomes nonempty. */ + tor_cond_t condition; + /** Queues of pending work that we have to do. The queue with priority + * <b>p</b> is work[p]. */ + work_tailq_t work[WORKQUEUE_N_PRIORITIES]; + + /** Weak RNG, used to decide when to ignore priority. */ + tor_weak_rng_t weak_rng; + + /** The current 'update generation' of the threadpool. Any thread that is + * at an earlier generation needs to run the update function. */ + unsigned generation; + + /** Function that should be run for updates on each thread. */ + workqueue_reply_t (*update_fn)(void *, void *); + /** Function to free update arguments if they can't be run. */ + void (*free_update_arg_fn)(void *); + /** Array of n_threads update arguments. */ + void **update_args; + /** Event to notice when another thread has sent a reply. */ + struct event *reply_event; + void (*reply_cb)(threadpool_t *); + + /** Number of elements in threads. */ + int n_threads; + /** Mutex to protect all the above fields. */ + tor_mutex_t lock; + + /** A reply queue to use when constructing new threads. */ + replyqueue_t *reply_queue; + + /** Functions used to allocate and free thread state. */ + void *(*new_thread_state_fn)(void*); + void (*free_thread_state_fn)(void*); + void *new_thread_state_arg; +}; + +/** Used to put a workqueue_priority_t value into a bitfield. */ +#define workqueue_priority_bitfield_t ENUM_BF(workqueue_priority_t) +/** Number of bits needed to hold all legal values of workqueue_priority_t */ +#define WORKQUEUE_PRIORITY_BITS 2 + +struct workqueue_entry_s { + /** The next workqueue_entry_t that's pending on the same thread or + * reply queue. */ + TOR_TAILQ_ENTRY(workqueue_entry_s) next_work; + /** The threadpool to which this workqueue_entry_t was assigned. This field + * is set when the workqueue_entry_t is created, and won't be cleared until + * after it's handled in the main thread. */ + struct threadpool_s *on_pool; + /** True iff this entry is waiting for a worker to start processing it. */ + uint8_t pending; + /** Priority of this entry. */ + workqueue_priority_bitfield_t priority : WORKQUEUE_PRIORITY_BITS; + /** Function to run in the worker thread. */ + workqueue_reply_t (*fn)(void *state, void *arg); + /** Function to run while processing the reply queue. */ + void (*reply_fn)(void *arg); + /** Argument for the above functions. */ + void *arg; +}; + +struct replyqueue_s { + /** Mutex to protect the answers field */ + tor_mutex_t lock; + /** Doubly-linked list of answers that the reply queue needs to handle. */ + TOR_TAILQ_HEAD(, workqueue_entry_s) answers; + + /** Mechanism to wake up the main thread when it is receiving answers. */ + alert_sockets_t alert; +}; + +/** A worker thread represents a single thread in a thread pool. */ +typedef struct workerthread_s { + /** Which thread it this? In range 0..in_pool->n_threads-1 */ + int index; + /** The pool this thread is a part of. */ + struct threadpool_s *in_pool; + /** User-supplied state field that we pass to the worker functions of each + * work item. */ + void *state; + /** Reply queue to which we pass our results. */ + replyqueue_t *reply_queue; + /** The current update generation of this thread */ + unsigned generation; + /** One over the probability of taking work from a lower-priority queue. */ + int32_t lower_priority_chance; +} workerthread_t; + +static void queue_reply(replyqueue_t *queue, workqueue_entry_t *work); + +/** Allocate and return a new workqueue_entry_t, set up to run the function + * <b>fn</b> in the worker thread, and <b>reply_fn</b> in the main + * thread. See threadpool_queue_work() for full documentation. */ +static workqueue_entry_t * +workqueue_entry_new(workqueue_reply_t (*fn)(void*, void*), + void (*reply_fn)(void*), + void *arg) +{ + workqueue_entry_t *ent = tor_malloc_zero(sizeof(workqueue_entry_t)); + ent->fn = fn; + ent->reply_fn = reply_fn; + ent->arg = arg; + ent->priority = WQ_PRI_HIGH; + return ent; +} + +#define workqueue_entry_free(ent) \ + FREE_AND_NULL(workqueue_entry_t, workqueue_entry_free_, (ent)) + +/** + * Release all storage held in <b>ent</b>. Call only when <b>ent</b> is not on + * any queue. + */ +static void +workqueue_entry_free_(workqueue_entry_t *ent) +{ + if (!ent) + return; + memset(ent, 0xf0, sizeof(*ent)); + tor_free(ent); +} + +/** + * Cancel a workqueue_entry_t that has been returned from + * threadpool_queue_work. + * + * You must not call this function on any work whose reply function has been + * executed in the main thread; that will cause undefined behavior (probably, + * a crash). + * + * If the work is cancelled, this function return the argument passed to the + * work function. It is the caller's responsibility to free this storage. + * + * This function will have no effect if the worker thread has already executed + * or begun to execute the work item. In that case, it will return NULL. + */ +void * +workqueue_entry_cancel(workqueue_entry_t *ent) +{ + int cancelled = 0; + void *result = NULL; + tor_mutex_acquire(&ent->on_pool->lock); + workqueue_priority_t prio = ent->priority; + if (ent->pending) { + TOR_TAILQ_REMOVE(&ent->on_pool->work[prio], ent, next_work); + cancelled = 1; + result = ent->arg; + } + tor_mutex_release(&ent->on_pool->lock); + + if (cancelled) { + workqueue_entry_free(ent); + } + return result; +} + +/**DOCDOC + + must hold lock */ +static int +worker_thread_has_work(workerthread_t *thread) +{ + unsigned i; + for (i = WORKQUEUE_PRIORITY_FIRST; i <= WORKQUEUE_PRIORITY_LAST; ++i) { + if (!TOR_TAILQ_EMPTY(&thread->in_pool->work[i])) + return 1; + } + return thread->generation != thread->in_pool->generation; +} + +/** Extract the next workqueue_entry_t from the the thread's pool, removing + * it from the relevant queues and marking it as non-pending. + * + * The caller must hold the lock. */ +static workqueue_entry_t * +worker_thread_extract_next_work(workerthread_t *thread) +{ + threadpool_t *pool = thread->in_pool; + work_tailq_t *queue = NULL, *this_queue; + unsigned i; + for (i = WORKQUEUE_PRIORITY_FIRST; i <= WORKQUEUE_PRIORITY_LAST; ++i) { + this_queue = &pool->work[i]; + if (!TOR_TAILQ_EMPTY(this_queue)) { + queue = this_queue; + if (! tor_weak_random_one_in_n(&pool->weak_rng, + thread->lower_priority_chance)) { + /* Usually we'll just break now, so that we can get out of the loop + * and use the queue where we found work. But with a small + * probability, we'll keep looking for lower priority work, so that + * we don't ignore our low-priority queues entirely. */ + break; + } + } + } + + if (queue == NULL) + return NULL; + + workqueue_entry_t *work = TOR_TAILQ_FIRST(queue); + TOR_TAILQ_REMOVE(queue, work, next_work); + work->pending = 0; + return work; +} + +/** + * Main function for the worker thread. + */ +static void +worker_thread_main(void *thread_) +{ + workerthread_t *thread = thread_; + threadpool_t *pool = thread->in_pool; + workqueue_entry_t *work; + workqueue_reply_t result; + + tor_mutex_acquire(&pool->lock); + while (1) { + /* lock must be held at this point. */ + while (worker_thread_has_work(thread)) { + /* lock must be held at this point. */ + if (thread->in_pool->generation != thread->generation) { + void *arg = thread->in_pool->update_args[thread->index]; + thread->in_pool->update_args[thread->index] = NULL; + workqueue_reply_t (*update_fn)(void*,void*) = + thread->in_pool->update_fn; + thread->generation = thread->in_pool->generation; + tor_mutex_release(&pool->lock); + + workqueue_reply_t r = update_fn(thread->state, arg); + + if (r != WQ_RPL_REPLY) { + return; + } + + tor_mutex_acquire(&pool->lock); + continue; + } + work = worker_thread_extract_next_work(thread); + if (BUG(work == NULL)) + break; + tor_mutex_release(&pool->lock); + + /* We run the work function without holding the thread lock. This + * is the main thread's first opportunity to give us more work. */ + result = work->fn(thread->state, work->arg); + + /* Queue the reply for the main thread. */ + queue_reply(thread->reply_queue, work); + + /* We may need to exit the thread. */ + if (result != WQ_RPL_REPLY) { + return; + } + tor_mutex_acquire(&pool->lock); + } + /* At this point the lock is held, and there is no work in this thread's + * queue. */ + + /* TODO: support an idle-function */ + + /* Okay. Now, wait till somebody has work for us. */ + if (tor_cond_wait(&pool->condition, &pool->lock, NULL) < 0) { + log_warn(LD_GENERAL, "Fail tor_cond_wait."); + } + } +} + +/** Put a reply on the reply queue. The reply must not currently be on + * any thread's work queue. */ +static void +queue_reply(replyqueue_t *queue, workqueue_entry_t *work) +{ + int was_empty; + tor_mutex_acquire(&queue->lock); + was_empty = TOR_TAILQ_EMPTY(&queue->answers); + TOR_TAILQ_INSERT_TAIL(&queue->answers, work, next_work); + tor_mutex_release(&queue->lock); + + if (was_empty) { + if (queue->alert.alert_fn(queue->alert.write_fd) < 0) { + /* XXXX complain! */ + } + } +} + +/** Allocate and start a new worker thread to use state object <b>state</b>, + * and send responses to <b>replyqueue</b>. */ +static workerthread_t * +workerthread_new(int32_t lower_priority_chance, + void *state, threadpool_t *pool, replyqueue_t *replyqueue) +{ + workerthread_t *thr = tor_malloc_zero(sizeof(workerthread_t)); + thr->state = state; + thr->reply_queue = replyqueue; + thr->in_pool = pool; + thr->lower_priority_chance = lower_priority_chance; + + if (spawn_func(worker_thread_main, thr) < 0) { + //LCOV_EXCL_START + tor_assert_nonfatal_unreached(); + log_err(LD_GENERAL, "Can't launch worker thread."); + tor_free(thr); + return NULL; + //LCOV_EXCL_STOP + } + + return thr; +} + +/** + * Queue an item of work for a thread in a thread pool. The function + * <b>fn</b> will be run in a worker thread, and will receive as arguments the + * thread's state object, and the provided object <b>arg</b>. It must return + * one of WQ_RPL_REPLY, WQ_RPL_ERROR, or WQ_RPL_SHUTDOWN. + * + * Regardless of its return value, the function <b>reply_fn</b> will later be + * run in the main thread when it invokes replyqueue_process(), and will + * receive as its argument the same <b>arg</b> object. It's the reply + * function's responsibility to free the work object. + * + * On success, return a workqueue_entry_t object that can be passed to + * workqueue_entry_cancel(). On failure, return NULL. (Failure is not + * currently possible, but callers should check anyway.) + * + * Items are executed in a loose priority order -- each thread will usually + * take from the queued work with the highest prioirity, but will occasionally + * visit lower-priority queues to keep them from starving completely. + * + * Note that because of priorities and thread behavior, work items may not + * be executed strictly in order. + */ +workqueue_entry_t * +threadpool_queue_work_priority(threadpool_t *pool, + workqueue_priority_t prio, + workqueue_reply_t (*fn)(void *, void *), + void (*reply_fn)(void *), + void *arg) +{ + tor_assert(((int)prio) >= WORKQUEUE_PRIORITY_FIRST && + ((int)prio) <= WORKQUEUE_PRIORITY_LAST); + + workqueue_entry_t *ent = workqueue_entry_new(fn, reply_fn, arg); + ent->on_pool = pool; + ent->pending = 1; + ent->priority = prio; + + tor_mutex_acquire(&pool->lock); + + TOR_TAILQ_INSERT_TAIL(&pool->work[prio], ent, next_work); + + tor_cond_signal_one(&pool->condition); + + tor_mutex_release(&pool->lock); + + return ent; +} + +/** As threadpool_queue_work_priority(), but assumes WQ_PRI_HIGH */ +workqueue_entry_t * +threadpool_queue_work(threadpool_t *pool, + workqueue_reply_t (*fn)(void *, void *), + void (*reply_fn)(void *), + void *arg) +{ + return threadpool_queue_work_priority(pool, WQ_PRI_HIGH, fn, reply_fn, arg); +} + +/** + * Queue a copy of a work item for every thread in a pool. This can be used, + * for example, to tell the threads to update some parameter in their states. + * + * Arguments are as for <b>threadpool_queue_work</b>, except that the + * <b>arg</b> value is passed to <b>dup_fn</b> once per each thread to + * make a copy of it. + * + * UPDATE FUNCTIONS MUST BE IDEMPOTENT. We do not guarantee that every update + * will be run. If a new update is scheduled before the old update finishes + * running, then the new will replace the old in any threads that haven't run + * it yet. + * + * Return 0 on success, -1 on failure. + */ +int +threadpool_queue_update(threadpool_t *pool, + void *(*dup_fn)(void *), + workqueue_reply_t (*fn)(void *, void *), + void (*free_fn)(void *), + void *arg) +{ + int i, n_threads; + void (*old_args_free_fn)(void *arg); + void **old_args; + void **new_args; + + tor_mutex_acquire(&pool->lock); + n_threads = pool->n_threads; + old_args = pool->update_args; + old_args_free_fn = pool->free_update_arg_fn; + + new_args = tor_calloc(n_threads, sizeof(void*)); + for (i = 0; i < n_threads; ++i) { + if (dup_fn) + new_args[i] = dup_fn(arg); + else + new_args[i] = arg; + } + + pool->update_args = new_args; + pool->free_update_arg_fn = free_fn; + pool->update_fn = fn; + ++pool->generation; + + tor_cond_signal_all(&pool->condition); + + tor_mutex_release(&pool->lock); + + if (old_args) { + for (i = 0; i < n_threads; ++i) { + if (old_args[i] && old_args_free_fn) + old_args_free_fn(old_args[i]); + } + tor_free(old_args); + } + + return 0; +} + +/** Don't have more than this many threads per pool. */ +#define MAX_THREADS 1024 + +/** For half of our threads, choose lower priority queues with probability + * 1/N for each of these values. Both are chosen somewhat arbitrarily. If + * CHANCE_PERMISSIVE is too low, then we have a risk of low-priority tasks + * stalling forever. If it's too high, we have a risk of low-priority tasks + * grabbing half of the threads. */ +#define CHANCE_PERMISSIVE 37 +#define CHANCE_STRICT INT32_MAX + +/** Launch threads until we have <b>n</b>. */ +static int +threadpool_start_threads(threadpool_t *pool, int n) +{ + if (BUG(n < 0)) + return -1; // LCOV_EXCL_LINE + if (n > MAX_THREADS) + n = MAX_THREADS; + + tor_mutex_acquire(&pool->lock); + + if (pool->n_threads < n) + pool->threads = tor_reallocarray(pool->threads, + sizeof(workerthread_t*), n); + + while (pool->n_threads < n) { + /* For half of our threads, we'll choose lower priorities permissively; + * for the other half, we'll stick more strictly to higher priorities. + * This keeps slow low-priority tasks from taking over completely. */ + int32_t chance = (pool->n_threads & 1) ? CHANCE_STRICT : CHANCE_PERMISSIVE; + + void *state = pool->new_thread_state_fn(pool->new_thread_state_arg); + workerthread_t *thr = workerthread_new(chance, + state, pool, pool->reply_queue); + + if (!thr) { + //LCOV_EXCL_START + tor_assert_nonfatal_unreached(); + pool->free_thread_state_fn(state); + tor_mutex_release(&pool->lock); + return -1; + //LCOV_EXCL_STOP + } + thr->index = pool->n_threads; + pool->threads[pool->n_threads++] = thr; + } + tor_mutex_release(&pool->lock); + + return 0; +} + +/** + * Construct a new thread pool with <b>n</b> worker threads, configured to + * send their output to <b>replyqueue</b>. The threads' states will be + * constructed with the <b>new_thread_state_fn</b> call, receiving <b>arg</b> + * as its argument. When the threads close, they will call + * <b>free_thread_state_fn</b> on their states. + */ +threadpool_t * +threadpool_new(int n_threads, + replyqueue_t *replyqueue, + void *(*new_thread_state_fn)(void*), + void (*free_thread_state_fn)(void*), + void *arg) +{ + threadpool_t *pool; + pool = tor_malloc_zero(sizeof(threadpool_t)); + tor_mutex_init_nonrecursive(&pool->lock); + tor_cond_init(&pool->condition); + unsigned i; + for (i = WORKQUEUE_PRIORITY_FIRST; i <= WORKQUEUE_PRIORITY_LAST; ++i) { + TOR_TAILQ_INIT(&pool->work[i]); + } + { + unsigned seed; + crypto_rand((void*)&seed, sizeof(seed)); + tor_init_weak_random(&pool->weak_rng, seed); + } + + pool->new_thread_state_fn = new_thread_state_fn; + pool->new_thread_state_arg = arg; + pool->free_thread_state_fn = free_thread_state_fn; + pool->reply_queue = replyqueue; + + if (threadpool_start_threads(pool, n_threads) < 0) { + //LCOV_EXCL_START + tor_assert_nonfatal_unreached(); + tor_cond_uninit(&pool->condition); + tor_mutex_uninit(&pool->lock); + tor_free(pool); + return NULL; + //LCOV_EXCL_STOP + } + + return pool; +} + +/** Return the reply queue associated with a given thread pool. */ +replyqueue_t * +threadpool_get_replyqueue(threadpool_t *tp) +{ + return tp->reply_queue; +} + +/** Allocate a new reply queue. Reply queues are used to pass results from + * worker threads to the main thread. Since the main thread is running an + * IO-centric event loop, it needs to get woken up with means other than a + * condition variable. */ +replyqueue_t * +replyqueue_new(uint32_t alertsocks_flags) +{ + replyqueue_t *rq; + + rq = tor_malloc_zero(sizeof(replyqueue_t)); + if (alert_sockets_create(&rq->alert, alertsocks_flags) < 0) { + //LCOV_EXCL_START + tor_free(rq); + return NULL; + //LCOV_EXCL_STOP + } + + tor_mutex_init(&rq->lock); + TOR_TAILQ_INIT(&rq->answers); + + return rq; +} + +/** Internal: Run from the libevent mainloop when there is work to handle in + * the reply queue handler. */ +static void +reply_event_cb(evutil_socket_t sock, short events, void *arg) +{ + threadpool_t *tp = arg; + (void) sock; + (void) events; + replyqueue_process(tp->reply_queue); + if (tp->reply_cb) + tp->reply_cb(tp); +} + +/** Register the threadpool <b>tp</b>'s reply queue with the libevent + * mainloop of <b>base</b>. If <b>tp</b> is provided, it is run after + * each time there is work to process from the reply queue. Return 0 on + * success, -1 on failure. + */ +int +threadpool_register_reply_event(threadpool_t *tp, + void (*cb)(threadpool_t *tp)) +{ + struct event_base *base = tor_libevent_get_base(); + + if (tp->reply_event) { + tor_event_free(tp->reply_event); + } + tp->reply_event = tor_event_new(base, + tp->reply_queue->alert.read_fd, + EV_READ|EV_PERSIST, + reply_event_cb, + tp); + tor_assert(tp->reply_event); + tp->reply_cb = cb; + return event_add(tp->reply_event, NULL); +} + +/** + * Process all pending replies on a reply queue. The main thread should call + * this function every time the socket returned by replyqueue_get_socket() is + * readable. + */ +void +replyqueue_process(replyqueue_t *queue) +{ + int r = queue->alert.drain_fn(queue->alert.read_fd); + if (r < 0) { + //LCOV_EXCL_START + static ratelim_t warn_limit = RATELIM_INIT(7200); + log_fn_ratelim(&warn_limit, LOG_WARN, LD_GENERAL, + "Failure from drain_fd: %s", + tor_socket_strerror(-r)); + //LCOV_EXCL_STOP + } + + tor_mutex_acquire(&queue->lock); + while (!TOR_TAILQ_EMPTY(&queue->answers)) { + /* lock must be held at this point.*/ + workqueue_entry_t *work = TOR_TAILQ_FIRST(&queue->answers); + TOR_TAILQ_REMOVE(&queue->answers, work, next_work); + tor_mutex_release(&queue->lock); + work->on_pool = NULL; + + work->reply_fn(work->arg); + workqueue_entry_free(work); + + tor_mutex_acquire(&queue->lock); + } + + tor_mutex_release(&queue->lock); +} diff --git a/src/lib/evloop/workqueue.h b/src/lib/evloop/workqueue.h new file mode 100644 index 0000000000..333a3f6dde --- /dev/null +++ b/src/lib/evloop/workqueue.h @@ -0,0 +1,70 @@ +/* Copyright (c) 2013-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file workqueue.h + * \brief Header for workqueue.c + **/ + +#ifndef TOR_WORKQUEUE_H +#define TOR_WORKQUEUE_H + +#include "lib/cc/torint.h" + +/** A replyqueue is used to tell the main thread about the outcome of + * work that we queued for the workers. */ +typedef struct replyqueue_s replyqueue_t; +/** A thread-pool manages starting threads and passing work to them. */ +typedef struct threadpool_s threadpool_t; +/** A workqueue entry represents a request that has been passed to a thread + * pool. */ +typedef struct workqueue_entry_s workqueue_entry_t; + +/** Possible return value from a work function: */ +typedef enum workqueue_reply_t { + WQ_RPL_REPLY = 0, /** indicates success */ + WQ_RPL_ERROR = 1, /** indicates fatal error */ + WQ_RPL_SHUTDOWN = 2, /** indicates thread is shutting down */ +} workqueue_reply_t; + +/** Possible priorities for work. Lower numeric values are more important. */ +typedef enum workqueue_priority_t { + WQ_PRI_HIGH = 0, + WQ_PRI_MED = 1, + WQ_PRI_LOW = 2, +} workqueue_priority_t; + +workqueue_entry_t *threadpool_queue_work_priority(threadpool_t *pool, + workqueue_priority_t prio, + workqueue_reply_t (*fn)(void *, + void *), + void (*reply_fn)(void *), + void *arg); + +workqueue_entry_t *threadpool_queue_work(threadpool_t *pool, + workqueue_reply_t (*fn)(void *, + void *), + void (*reply_fn)(void *), + void *arg); + +int threadpool_queue_update(threadpool_t *pool, + void *(*dup_fn)(void *), + workqueue_reply_t (*fn)(void *, void *), + void (*free_fn)(void *), + void *arg); +void *workqueue_entry_cancel(workqueue_entry_t *pending_work); +threadpool_t *threadpool_new(int n_threads, + replyqueue_t *replyqueue, + void *(*new_thread_state_fn)(void*), + void (*free_thread_state_fn)(void*), + void *arg); +replyqueue_t *threadpool_get_replyqueue(threadpool_t *tp); + +replyqueue_t *replyqueue_new(uint32_t alertsocks_flags); +void replyqueue_process(replyqueue_t *queue); + +struct event_base; +int threadpool_register_reply_event(threadpool_t *tp, + void (*cb)(threadpool_t *tp)); + +#endif /* !defined(TOR_WORKQUEUE_H) */ diff --git a/src/lib/fdio/.may_include b/src/lib/fdio/.may_include new file mode 100644 index 0000000000..30fd277b81 --- /dev/null +++ b/src/lib/fdio/.may_include @@ -0,0 +1,4 @@ +orconfig.h +lib/cc/*.h +lib/err/*.h +lib/fdio/*.h diff --git a/src/lib/fdio/fdio.c b/src/lib/fdio/fdio.c new file mode 100644 index 0000000000..6c87af791d --- /dev/null +++ b/src/lib/fdio/fdio.c @@ -0,0 +1,115 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file fdio.c + * + * \brief Low-level compatibility wrappers for fd-based IO. + **/ + +#include "orconfig.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef _WIN32 +#include <windows.h> +#endif + +#include "lib/fdio/fdio.h" +#include "lib/cc/torint.h" +#include "lib/err/torerr.h" + +#include <stdlib.h> + +/** @{ */ +/** Some old versions of Unix didn't define constants for these values, + * and instead expect you to say 0, 1, or 2. */ +#ifndef SEEK_SET +#define SEEK_SET 0 +#endif +#ifndef SEEK_CUR +#define SEEK_CUR 1 +#endif +#ifndef SEEK_END +#define SEEK_END 2 +#endif +/** @} */ + +/** Return the position of <b>fd</b> with respect to the start of the file. */ +off_t +tor_fd_getpos(int fd) +{ +#ifdef _WIN32 + return (off_t) _lseek(fd, 0, SEEK_CUR); +#else + return (off_t) lseek(fd, 0, SEEK_CUR); +#endif +} + +/** Move <b>fd</b> to the end of the file. Return -1 on error, 0 on success. + * If the file is a pipe, do nothing and succeed. + **/ +int +tor_fd_seekend(int fd) +{ +#ifdef _WIN32 + return _lseek(fd, 0, SEEK_END) < 0 ? -1 : 0; +#else + off_t rc = lseek(fd, 0, SEEK_END) < 0 ? -1 : 0; +#ifdef ESPIPE + /* If we get an error and ESPIPE, then it's a pipe or a socket of a fifo: + * no need to worry. */ + if (rc < 0 && errno == ESPIPE) + rc = 0; +#endif /* defined(ESPIPE) */ + return (rc < 0) ? -1 : 0; +#endif /* defined(_WIN32) */ +} + +/** Move <b>fd</b> to position <b>pos</b> in the file. Return -1 on error, 0 + * on success. */ +int +tor_fd_setpos(int fd, off_t pos) +{ +#ifdef _WIN32 + return _lseek(fd, pos, SEEK_SET) < 0 ? -1 : 0; +#else + return lseek(fd, pos, SEEK_SET) < 0 ? -1 : 0; +#endif +} + +/** Replacement for ftruncate(fd, 0): move to the front of the file and remove + * all the rest of the file. Return -1 on error, 0 on success. */ +int +tor_ftruncate(int fd) +{ + /* Rumor has it that some versions of ftruncate do not move the file pointer. + */ + if (tor_fd_setpos(fd, 0) < 0) + return -1; + +#ifdef _WIN32 + return _chsize(fd, 0); +#else + return ftruncate(fd, 0); +#endif +} + +/** Minimal version of write_all, for use by logging. */ +int +write_all_to_fd_minimal(int fd, const char *buf, size_t count) +{ + size_t written = 0; + raw_assert(count < SSIZE_MAX); + + while (written < count) { + ssize_t result = write(fd, buf+written, count-written); + if (result<0) + return -1; + written += result; + } + return 0; +} diff --git a/src/lib/fdio/fdio.h b/src/lib/fdio/fdio.h new file mode 100644 index 0000000000..8395af353b --- /dev/null +++ b/src/lib/fdio/fdio.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file fdio.h + * + * \brief Header for fdio.c + **/ + +#ifndef TOR_FDIO_H +#define TOR_FDIO_H + +#include <stddef.h> + +off_t tor_fd_getpos(int fd); +int tor_fd_setpos(int fd, off_t pos); +int tor_fd_seekend(int fd); +int tor_ftruncate(int fd); +int write_all_to_fd_minimal(int fd, const char *buf, size_t count); + +#endif /* !defined(TOR_FDIO_H) */ diff --git a/src/lib/fdio/include.am b/src/lib/fdio/include.am new file mode 100644 index 0000000000..6c18f00a0d --- /dev/null +++ b/src/lib/fdio/include.am @@ -0,0 +1,17 @@ + +noinst_LIBRARIES += src/lib/libtor-fdio.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-fdio-testing.a +endif + +src_lib_libtor_fdio_a_SOURCES = \ + src/lib/fdio/fdio.c + +src_lib_libtor_fdio_testing_a_SOURCES = \ + $(src_lib_libtor_fdio_a_SOURCES) +src_lib_libtor_fdio_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_fdio_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/fdio/fdio.h diff --git a/src/lib/fs/.may_include b/src/lib/fs/.may_include new file mode 100644 index 0000000000..b1e49fc891 --- /dev/null +++ b/src/lib/fs/.may_include @@ -0,0 +1,16 @@ +orconfig.h + +ext/getdelim.c + +lib/cc/*.h +lib/container/*.h +lib/encoding/*.h +lib/err/*.h +lib/fdio/*.h +lib/fs/*.h +lib/log/*.h +lib/malloc/*.h +lib/memarea/*.h +lib/sandbox/*.h +lib/string/*.h +lib/testsupport/testsupport.h diff --git a/src/lib/fs/conffile.c b/src/lib/fs/conffile.c new file mode 100644 index 0000000000..7bb2f23931 --- /dev/null +++ b/src/lib/fs/conffile.c @@ -0,0 +1,174 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file conffile.h + * + * \brief Read configuration files from disk, with full `%include` support. + **/ + +#include "lib/fs/conffile.h" + +#include "lib/container/smartlist.h" +#include "lib/encoding/confline.h" +#include "lib/fs/dir.h" +#include "lib/fs/files.h" +#include "lib/fs/path.h" +#include "lib/log/log.h" +#include "lib/malloc/malloc.h" +#include "lib/string/printf.h" + +static smartlist_t *config_get_file_list(const char *path, + smartlist_t *opened_files); +static int config_get_included_config(const char *path, int recursion_level, + int extended, config_line_t **config, + config_line_t **config_last, + smartlist_t *opened_lst); +static int config_process_include(const char *path, int recursion_level, + int extended, config_line_t **list, + config_line_t **list_last, + smartlist_t *opened_lst); + +/** Helper: parse the config string and strdup into key/value + * strings. Set *result to the list, or NULL if parsing the string + * failed. Set *has_include to 1 if <b>result</b> has values from + * %included files. <b>opened_lst</b> will have a list of opened files if + * provided. Return 0 on success, -1 on failure. Warn and ignore any + * misformatted lines. + * + * If <b>extended</b> is set, then treat keys beginning with / and with + as + * indicating "clear" and "append" respectively. */ +int +config_get_lines_include(const char *string, config_line_t **result, + int extended, int *has_include, + smartlist_t *opened_lst) +{ + return config_get_lines_aux(string, result, extended, 1, has_include, + opened_lst, 1, NULL, config_process_include); +} + +/** Adds a list of configuration files present on <b>path</b> to + * <b>file_list</b>. <b>path</b> can be a file or a directory. If it is a file, + * only that file will be added to <b>file_list</b>. If it is a directory, + * all paths for files on that directory root (no recursion) except for files + * whose name starts with a dot will be added to <b>file_list</b>. + * <b>opened_files</b> will have a list of files opened by this function + * if provided. Return 0 on success, -1 on failure. Ignores empty files. + */ +static smartlist_t * +config_get_file_list(const char *path, smartlist_t *opened_files) +{ + smartlist_t *file_list = smartlist_new(); + + if (opened_files) { + smartlist_add_strdup(opened_files, path); + } + + file_status_t file_type = file_status(path); + if (file_type == FN_FILE) { + smartlist_add_strdup(file_list, path); + return file_list; + } else if (file_type == FN_DIR) { + smartlist_t *all_files = tor_listdir(path); + if (!all_files) { + smartlist_free(file_list); + return NULL; + } + smartlist_sort_strings(all_files); + SMARTLIST_FOREACH_BEGIN(all_files, char *, f) { + if (f[0] == '.') { + tor_free(f); + continue; + } + + char *fullname; + tor_asprintf(&fullname, "%s"PATH_SEPARATOR"%s", path, f); + tor_free(f); + + if (opened_files) { + smartlist_add_strdup(opened_files, fullname); + } + + if (file_status(fullname) != FN_FILE) { + tor_free(fullname); + continue; + } + smartlist_add(file_list, fullname); + } SMARTLIST_FOREACH_END(f); + smartlist_free(all_files); + return file_list; + } else if (file_type == FN_EMPTY) { + return file_list; + } else { + smartlist_free(file_list); + return NULL; + } +} + +/** Creates a list of config lines present on included <b>path</b>. + * Set <b>config</b> to the list and <b>config_last</b> to the last element of + * <b>config</b>. <b>opened_lst</b> will have a list of opened files if + * provided. Return 0 on success, -1 on failure. */ +static int +config_get_included_config(const char *path, int recursion_level, int extended, + config_line_t **config, config_line_t **config_last, + smartlist_t *opened_lst) +{ + char *included_conf = read_file_to_str(path, 0, NULL); + if (!included_conf) { + return -1; + } + + if (config_get_lines_aux(included_conf, config, extended, 1, NULL, + opened_lst, recursion_level+1, config_last, + config_process_include) < 0) { + tor_free(included_conf); + return -1; + } + + tor_free(included_conf); + return 0; +} + +/** Process an %include <b>path</b> in a config file. Set <b>list</b> to the + * list of configuration settings obtained and <b>list_last</b> to the last + * element of the same list. <b>opened_lst</b> will have a list of opened + * files if provided. Return 0 on success, -1 on failure. */ +static int +config_process_include(const char *path, int recursion_level, int extended, + config_line_t **list, config_line_t **list_last, + smartlist_t *opened_lst) +{ + config_line_t *ret_list = NULL; + config_line_t **next = &ret_list; + + smartlist_t *config_files = config_get_file_list(path, opened_lst); + if (!config_files) { + return -1; + } + + int rv = -1; + SMARTLIST_FOREACH_BEGIN(config_files, const char *, config_file) { + config_line_t *included_config = NULL; + if (config_get_included_config(config_file, recursion_level, extended, + &included_config, list_last, + opened_lst) < 0) { + goto done; + } + + *next = included_config; + if (*list_last) + next = &(*list_last)->next; + + } SMARTLIST_FOREACH_END(config_file); + *list = ret_list; + rv = 0; + + done: + SMARTLIST_FOREACH(config_files, char *, f, tor_free(f)); + smartlist_free(config_files); + return rv; +} diff --git a/src/lib/fs/conffile.h b/src/lib/fs/conffile.h new file mode 100644 index 0000000000..7af9119dbb --- /dev/null +++ b/src/lib/fs/conffile.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_CONFFILE_H +#define TOR_CONFFILE_H + +/** + * \file conffile.h + * + * \brief Header for conffile.c + **/ + +struct smartlist_t; +struct config_line_t; + +int config_get_lines_include(const char *string, struct config_line_t **result, + int extended, int *has_include, + struct smartlist_t *opened_lst); + +#endif /* !defined(TOR_CONFLINE_H) */ diff --git a/src/lib/fs/dir.c b/src/lib/fs/dir.c new file mode 100644 index 0000000000..3c31e00d99 --- /dev/null +++ b/src/lib/fs/dir.c @@ -0,0 +1,367 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file dir.c + * + * \brief Read directories, and create directories with restrictive + * permissions. + **/ + +#include "lib/fs/dir.h" +#include "lib/fs/path.h" +#include "lib/fs/userdb.h" + +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/log/win32err.h" +#include "lib/container/smartlist.h" +#include "lib/sandbox/sandbox.h" +#include "lib/malloc/malloc.h" +#include "lib/string/printf.h" +#include "lib/string/compat_string.h" + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#ifdef _WIN32 +#include <io.h> +#include <direct.h> +#include <windows.h> +#else /* !(defined(_WIN32)) */ +#include <dirent.h> +#include <pwd.h> +#include <grp.h> +#endif /* defined(_WIN32) */ + +#include <errno.h> +#include <string.h> + +/** Check whether <b>dirname</b> exists and is private. If yes return 0. + * If <b>dirname</b> does not exist: + * - if <b>check</b>&CPD_CREATE, try to create it and return 0 on success. + * - if <b>check</b>&CPD_CHECK, and we think we can create it, return 0. + * - if <b>check</b>&CPD_CHECK is false, and the directory exists, return 0. + * - otherwise, return -1. + * If CPD_GROUP_OK is set, then it's okay if the directory + * is group-readable, but in all cases we create the directory mode 0700. + * If CPD_GROUP_READ is set, existing directory behaves as CPD_GROUP_OK and + * if the directory is created it will use mode 0750 with group read + * permission. Group read privileges also assume execute permission + * as norm for directories. If CPD_CHECK_MODE_ONLY is set, then we don't + * alter the directory permissions if they are too permissive: + * we just return -1. + * When effective_user is not NULL, check permissions against the given user + * and its primary group. + */ +MOCK_IMPL(int, +check_private_dir,(const char *dirname, cpd_check_t check, + const char *effective_user)) +{ + int r; + struct stat st; + + tor_assert(dirname); + +#ifndef _WIN32 + int fd; + const struct passwd *pw = NULL; + uid_t running_uid; + gid_t running_gid; + + /* + * Goal is to harden the implementation by removing any + * potential for race between stat() and chmod(). + * chmod() accepts filename as argument. If an attacker can move + * the file between stat() and chmod(), a potential race exists. + * + * Several suggestions taken from: + * https://developer.apple.com/library/mac/documentation/ + * Security/Conceptual/SecureCodingGuide/Articles/RaceConditions.html + */ + + /* Open directory. + * O_NOFOLLOW to ensure that it does not follow symbolic links */ + fd = open(sandbox_intern_string(dirname), O_NOFOLLOW); + + /* Was there an error? Maybe the directory does not exist? */ + if (fd == -1) { + + if (errno != ENOENT) { + /* Other directory error */ + log_warn(LD_FS, "Directory %s cannot be read: %s", dirname, + strerror(errno)); + return -1; + } + + /* Received ENOENT: Directory does not exist */ + + /* Should we create the directory? */ + if (check & CPD_CREATE) { + log_info(LD_GENERAL, "Creating directory %s", dirname); + if (check & CPD_GROUP_READ) { + r = mkdir(dirname, 0750); + } else { + r = mkdir(dirname, 0700); + } + + /* check for mkdir() error */ + if (r) { + log_warn(LD_FS, "Error creating directory %s: %s", dirname, + strerror(errno)); + return -1; + } + + /* we just created the directory. try to open it again. + * permissions on the directory will be checked again below.*/ + fd = open(sandbox_intern_string(dirname), O_NOFOLLOW); + + if (fd == -1) { + log_warn(LD_FS, "Could not reopen recently created directory %s: %s", + dirname, + strerror(errno)); + return -1; + } else { + close(fd); + } + + } else if (!(check & CPD_CHECK)) { + log_warn(LD_FS, "Directory %s does not exist.", dirname); + return -1; + } + + /* XXXX In the case where check==CPD_CHECK, we should look at the + * parent directory a little harder. */ + return 0; + } + + tor_assert(fd >= 0); + + //f = tor_strdup(dirname); + //clean_name_for_stat(f); + log_debug(LD_FS, "stat()ing %s", dirname); + //r = stat(sandbox_intern_string(f), &st); + r = fstat(fd, &st); + if (r == -1) { + log_warn(LD_FS, "fstat() on directory %s failed.", dirname); + close(fd); + return -1; + } + //tor_free(f); + + /* check that dirname is a directory */ + if (!(st.st_mode & S_IFDIR)) { + log_warn(LD_FS, "%s is not a directory", dirname); + close(fd); + return -1; + } + + if (effective_user) { + /* Look up the user and group information. + * If we have a problem, bail out. */ + pw = tor_getpwnam(effective_user); + if (pw == NULL) { + log_warn(LD_CONFIG, "Error setting configured user: %s not found", + effective_user); + close(fd); + return -1; + } + running_uid = pw->pw_uid; + running_gid = pw->pw_gid; + } else { + running_uid = getuid(); + running_gid = getgid(); + } + if (st.st_uid != running_uid) { + char *process_ownername = NULL, *file_ownername = NULL; + + { + const struct passwd *pw_running = tor_getpwuid(running_uid); + process_ownername = pw_running ? tor_strdup(pw_running->pw_name) : + tor_strdup("<unknown>"); + } + + { + const struct passwd *pw_stat = tor_getpwuid(st.st_uid); + file_ownername = pw_stat ? tor_strdup(pw_stat->pw_name) : + tor_strdup("<unknown>"); + } + + log_warn(LD_FS, "%s is not owned by this user (%s, %d) but by " + "%s (%d). Perhaps you are running Tor as the wrong user?", + dirname, process_ownername, (int)running_uid, + file_ownername, (int)st.st_uid); + + tor_free(process_ownername); + tor_free(file_ownername); + close(fd); + return -1; + } + if ( (check & (CPD_GROUP_OK|CPD_GROUP_READ)) + && (st.st_gid != running_gid) && (st.st_gid != 0)) { + struct group *gr; + char *process_groupname = NULL; + gr = getgrgid(running_gid); + process_groupname = gr ? tor_strdup(gr->gr_name) : tor_strdup("<unknown>"); + gr = getgrgid(st.st_gid); + + log_warn(LD_FS, "%s is not owned by this group (%s, %d) but by group " + "%s (%d). Are you running Tor as the wrong user?", + dirname, process_groupname, (int)running_gid, + gr ? gr->gr_name : "<unknown>", (int)st.st_gid); + + tor_free(process_groupname); + close(fd); + return -1; + } + unsigned unwanted_bits = 0; + if (check & (CPD_GROUP_OK|CPD_GROUP_READ)) { + unwanted_bits = 0027; + } else { + unwanted_bits = 0077; + } + unsigned check_bits_filter = ~0; + if (check & CPD_RELAX_DIRMODE_CHECK) { + check_bits_filter = 0022; + } + if ((st.st_mode & unwanted_bits & check_bits_filter) != 0) { + unsigned new_mode; + if (check & CPD_CHECK_MODE_ONLY) { + log_warn(LD_FS, "Permissions on directory %s are too permissive.", + dirname); + close(fd); + return -1; + } + log_warn(LD_FS, "Fixing permissions on directory %s", dirname); + new_mode = st.st_mode; + new_mode |= 0700; /* Owner should have rwx */ + if (check & CPD_GROUP_READ) { + new_mode |= 0050; /* Group should have rx */ + } + new_mode &= ~unwanted_bits; /* Clear the bits that we didn't want set...*/ + if (fchmod(fd, new_mode)) { + log_warn(LD_FS, "Could not chmod directory %s: %s", dirname, + strerror(errno)); + close(fd); + return -1; + } else { + close(fd); + return 0; + } + } + close(fd); +#else /* !(!defined(_WIN32)) */ + /* Win32 case: we can't open() a directory. */ + (void)effective_user; + + char *f = tor_strdup(dirname); + clean_fname_for_stat(f); + log_debug(LD_FS, "stat()ing %s", f); + r = stat(sandbox_intern_string(f), &st); + tor_free(f); + if (r) { + if (errno != ENOENT) { + log_warn(LD_FS, "Directory %s cannot be read: %s", dirname, + strerror(errno)); + return -1; + } + if (check & CPD_CREATE) { + log_info(LD_GENERAL, "Creating directory %s", dirname); + r = mkdir(dirname); + if (r) { + log_warn(LD_FS, "Error creating directory %s: %s", dirname, + strerror(errno)); + return -1; + } + } else if (!(check & CPD_CHECK)) { + log_warn(LD_FS, "Directory %s does not exist.", dirname); + return -1; + } + return 0; + } + if (!(st.st_mode & S_IFDIR)) { + log_warn(LD_FS, "%s is not a directory", dirname); + return -1; + } + +#endif /* !defined(_WIN32) */ + return 0; +} + +/** Return a new list containing the filenames in the directory <b>dirname</b>. + * Return NULL on error or if <b>dirname</b> is not a directory. + */ +MOCK_IMPL(smartlist_t *, +tor_listdir, (const char *dirname)) +{ + smartlist_t *result; +#ifdef _WIN32 + char *pattern=NULL; + TCHAR tpattern[MAX_PATH] = {0}; + char name[MAX_PATH*2+1] = {0}; + HANDLE handle; + WIN32_FIND_DATA findData; + tor_asprintf(&pattern, "%s\\*", dirname); +#ifdef UNICODE + mbstowcs(tpattern,pattern,MAX_PATH); +#else + strlcpy(tpattern, pattern, MAX_PATH); +#endif + if (INVALID_HANDLE_VALUE == (handle = FindFirstFile(tpattern, &findData))) { + tor_free(pattern); + return NULL; + } + result = smartlist_new(); + while (1) { +#ifdef UNICODE + wcstombs(name,findData.cFileName,MAX_PATH); + name[sizeof(name)-1] = '\0'; +#else + strlcpy(name,findData.cFileName,sizeof(name)); +#endif /* defined(UNICODE) */ + if (strcmp(name, ".") && + strcmp(name, "..")) { + smartlist_add_strdup(result, name); + } + if (!FindNextFile(handle, &findData)) { + DWORD err; + if ((err = GetLastError()) != ERROR_NO_MORE_FILES) { + char *errstr = format_win32_error(err); + log_warn(LD_FS, "Error reading directory '%s': %s", dirname, errstr); + tor_free(errstr); + } + break; + } + } + FindClose(handle); + tor_free(pattern); +#else /* !(defined(_WIN32)) */ + const char *prot_dname = sandbox_intern_string(dirname); + DIR *d; + struct dirent *de; + if (!(d = opendir(prot_dname))) + return NULL; + + result = smartlist_new(); + while ((de = readdir(d))) { + if (!strcmp(de->d_name, ".") || + !strcmp(de->d_name, "..")) + continue; + smartlist_add_strdup(result, de->d_name); + } + closedir(d); +#endif /* defined(_WIN32) */ + return result; +} diff --git a/src/lib/fs/dir.h b/src/lib/fs/dir.h new file mode 100644 index 0000000000..826bc2dfc5 --- /dev/null +++ b/src/lib/fs/dir.h @@ -0,0 +1,33 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_DIR_H +#define TOR_DIR_H + +/** + * \file dir.h + * + * \brief Header for dir.c + **/ + +#include "lib/cc/compat_compiler.h" +#include "lib/testsupport/testsupport.h" + +/** Possible behaviors for check_private_dir() on encountering a nonexistent + * directory; see that function's documentation for details. */ +typedef unsigned int cpd_check_t; +#define CPD_NONE 0 +#define CPD_CREATE (1u << 0) +#define CPD_CHECK (1u << 1) +#define CPD_GROUP_OK (1u << 2) +#define CPD_GROUP_READ (1u << 3) +#define CPD_CHECK_MODE_ONLY (1u << 4) +#define CPD_RELAX_DIRMODE_CHECK (1u << 5) +MOCK_DECL(int, check_private_dir, (const char *dirname, cpd_check_t check, + const char *effective_user)); + +MOCK_DECL(struct smartlist_t *, tor_listdir, (const char *dirname)); + +#endif diff --git a/src/lib/fs/files.c b/src/lib/fs/files.c new file mode 100644 index 0000000000..b98a51a287 --- /dev/null +++ b/src/lib/fs/files.c @@ -0,0 +1,721 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file files.h + * + * \brief Wrappers for reading and writing data to files on disk. + **/ + +#ifdef _WIN32 +#include <windows.h> +#endif + +#include "lib/fs/files.h" +#include "lib/fs/path.h" +#include "lib/container/smartlist.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/log/escape.h" +#include "lib/err/torerr.h" +#include "lib/malloc/malloc.h" +#include "lib/sandbox/sandbox.h" +#include "lib/string/printf.h" +#include "lib/string/util_string.h" +#include "lib/fdio/fdio.h" + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_UTIME_H +#include <utime.h> +#endif +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <errno.h> +#include <stdio.h> +#include <string.h> + +/** As open(path, flags, mode), but return an fd with the close-on-exec mode + * set. */ +int +tor_open_cloexec(const char *path, int flags, unsigned mode) +{ + int fd; + const char *p = sandbox_intern_string(path); +#ifdef O_CLOEXEC + fd = open(p, flags|O_CLOEXEC, mode); + if (fd >= 0) + return fd; + /* If we got an error, see if it is EINVAL. EINVAL might indicate that, + * even though we were built on a system with O_CLOEXEC support, we + * are running on one without. */ + if (errno != EINVAL) + return -1; +#endif /* defined(O_CLOEXEC) */ + + log_debug(LD_FS, "Opening %s with flags %x", p, flags); + fd = open(p, flags, mode); +#ifdef FD_CLOEXEC + if (fd >= 0) { + if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) { + log_warn(LD_FS,"Couldn't set FD_CLOEXEC: %s", strerror(errno)); + close(fd); + return -1; + } + } +#endif /* defined(FD_CLOEXEC) */ + return fd; +} + +/** As fopen(path,mode), but ensures that the O_CLOEXEC bit is set on the + * underlying file handle. */ +FILE * +tor_fopen_cloexec(const char *path, const char *mode) +{ + FILE *result = fopen(path, mode); +#ifdef FD_CLOEXEC + if (result != NULL) { + if (fcntl(fileno(result), F_SETFD, FD_CLOEXEC) == -1) { + log_warn(LD_FS,"Couldn't set FD_CLOEXEC: %s", strerror(errno)); + fclose(result); + return NULL; + } + } +#endif /* defined(FD_CLOEXEC) */ + return result; +} + +/** As rename(), but work correctly with the sandbox. */ +int +tor_rename(const char *path_old, const char *path_new) +{ + log_debug(LD_FS, "Renaming %s to %s", path_old, path_new); + return rename(sandbox_intern_string(path_old), + sandbox_intern_string(path_new)); +} + +/** + * Rename the file <b>from</b> to the file <b>to</b>. On Unix, this is + * the same as rename(2). On windows, this removes <b>to</b> first if + * it already exists. + * Returns 0 on success. Returns -1 and sets errno on failure. + */ +int +replace_file(const char *from, const char *to) +{ +#ifndef _WIN32 + return tor_rename(from, to); +#else + switch (file_status(to)) + { + case FN_NOENT: + break; + case FN_FILE: + case FN_EMPTY: + if (unlink(to)) return -1; + break; + case FN_ERROR: + return -1; + case FN_DIR: + errno = EISDIR; + return -1; + } + return tor_rename(from,to); +#endif /* !defined(_WIN32) */ +} + +/** Change <b>fname</b>'s modification time to now. */ +int +touch_file(const char *fname) +{ + if (utime(fname, NULL)!=0) + return -1; + return 0; +} + +/** Wrapper for unlink() to make it mockable for the test suite; returns 0 + * if unlinking the file succeeded, -1 and sets errno if unlinking fails. + */ + +MOCK_IMPL(int, +tor_unlink,(const char *pathname)) +{ + return unlink(pathname); +} + +/** Write <b>count</b> bytes from <b>buf</b> to <b>fd</b>. Return the number + * of bytes written, or -1 on error. Only use if fd is a blocking fd. */ +ssize_t +write_all_to_fd(int fd, const char *buf, size_t count) +{ + size_t written = 0; + ssize_t result; + raw_assert(count < SSIZE_MAX); + + while (written != count) { + result = write(fd, buf+written, count-written); + if (result<0) + return -1; + written += result; + } + return (ssize_t)count; +} + +/** Read from <b>fd</b> to <b>buf</b>, until we get <b>count</b> bytes or + * reach the end of the file. Return the number of bytes read, or -1 on + * error. Only use if fd is a blocking fd. */ +ssize_t +read_all_from_fd(int fd, char *buf, size_t count) +{ + size_t numread = 0; + ssize_t result; + + if (count > SIZE_T_CEILING || count > SSIZE_MAX) { + errno = EINVAL; + return -1; + } + + while (numread < count) { + result = read(fd, buf+numread, count-numread); + if (result<0) + return -1; + else if (result == 0) + break; + numread += result; + } + return (ssize_t)numread; +} + +/** Return: + * FN_ERROR if filename can't be read, is NULL, or is zero-length, + * FN_NOENT if it doesn't exist, + * FN_FILE if it is a non-empty regular file, or a FIFO on unix-like systems, + * FN_EMPTY for zero-byte regular files, + * FN_DIR if it's a directory, and + * FN_ERROR for any other file type. + * On FN_ERROR and FN_NOENT, sets errno. (errno is not set when FN_ERROR + * is returned due to an unhandled file type.) */ +file_status_t +file_status(const char *fname) +{ + struct stat st; + char *f; + int r; + if (!fname || strlen(fname) == 0) { + return FN_ERROR; + } + f = tor_strdup(fname); + clean_fname_for_stat(f); + log_debug(LD_FS, "stat()ing %s", f); + r = stat(sandbox_intern_string(f), &st); + tor_free(f); + if (r) { + if (errno == ENOENT) { + return FN_NOENT; + } + return FN_ERROR; + } + if (st.st_mode & S_IFDIR) { + return FN_DIR; + } else if (st.st_mode & S_IFREG) { + if (st.st_size > 0) { + return FN_FILE; + } else if (st.st_size == 0) { + return FN_EMPTY; + } else { + return FN_ERROR; + } +#ifndef _WIN32 + } else if (st.st_mode & S_IFIFO) { + return FN_FILE; +#endif + } else { + return FN_ERROR; + } +} + +/** Create a file named <b>fname</b> with the contents <b>str</b>. Overwrite + * the previous <b>fname</b> if possible. Return 0 on success, -1 on failure. + * + * This function replaces the old file atomically, if possible. This + * function, and all other functions in util.c that create files, create them + * with mode 0600. + */ +MOCK_IMPL(int, +write_str_to_file,(const char *fname, const char *str, int bin)) +{ +#ifdef _WIN32 + if (!bin && strchr(str, '\r')) { + log_warn(LD_BUG, + "We're writing a text string that already contains a CR to %s", + escaped(fname)); + } +#endif /* defined(_WIN32) */ + return write_bytes_to_file(fname, str, strlen(str), bin); +} + +/** Represents a file that we're writing to, with support for atomic commit: + * we can write into a temporary file, and either remove the file on + * failure, or replace the original file on success. */ +struct open_file_t { + char *tempname; /**< Name of the temporary file. */ + char *filename; /**< Name of the original file. */ + unsigned rename_on_close:1; /**< Are we using the temporary file or not? */ + unsigned binary:1; /**< Did we open in binary mode? */ + int fd; /**< fd for the open file. */ + FILE *stdio_file; /**< stdio wrapper for <b>fd</b>. */ +}; + +/** Try to start writing to the file in <b>fname</b>, passing the flags + * <b>open_flags</b> to the open() syscall, creating the file (if needed) with + * access value <b>mode</b>. If the O_APPEND flag is set, we append to the + * original file. Otherwise, we open a new temporary file in the same + * directory, and either replace the original or remove the temporary file + * when we're done. + * + * Return the fd for the newly opened file, and store working data in + * *<b>data_out</b>. The caller should not close the fd manually: + * instead, call finish_writing_to_file() or abort_writing_to_file(). + * Returns -1 on failure. + * + * NOTE: When not appending, the flags O_CREAT and O_TRUNC are treated + * as true and the flag O_EXCL is treated as false. + * + * NOTE: Ordinarily, O_APPEND means "seek to the end of the file before each + * write()". We don't do that. + */ +int +start_writing_to_file(const char *fname, int open_flags, int mode, + open_file_t **data_out) +{ + open_file_t *new_file = tor_malloc_zero(sizeof(open_file_t)); + const char *open_name; + int append = 0; + + tor_assert(fname); + tor_assert(data_out); +#if (O_BINARY != 0 && O_TEXT != 0) + tor_assert((open_flags & (O_BINARY|O_TEXT)) != 0); +#endif + new_file->fd = -1; + new_file->filename = tor_strdup(fname); + if (open_flags & O_APPEND) { + open_name = fname; + new_file->rename_on_close = 0; + append = 1; + open_flags &= ~O_APPEND; + } else { + tor_asprintf(&new_file->tempname, "%s.tmp", fname); + open_name = new_file->tempname; + /* We always replace an existing temporary file if there is one. */ + open_flags |= O_CREAT|O_TRUNC; + open_flags &= ~O_EXCL; + new_file->rename_on_close = 1; + } +#if O_BINARY != 0 + if (open_flags & O_BINARY) + new_file->binary = 1; +#endif + + new_file->fd = tor_open_cloexec(open_name, open_flags, mode); + if (new_file->fd < 0) { + log_warn(LD_FS, "Couldn't open \"%s\" (%s) for writing: %s", + open_name, fname, strerror(errno)); + goto err; + } + if (append) { + if (tor_fd_seekend(new_file->fd) < 0) { + log_warn(LD_FS, "Couldn't seek to end of file \"%s\": %s", open_name, + strerror(errno)); + goto err; + } + } + + *data_out = new_file; + + return new_file->fd; + + err: + if (new_file->fd >= 0) + close(new_file->fd); + *data_out = NULL; + tor_free(new_file->filename); + tor_free(new_file->tempname); + tor_free(new_file); + return -1; +} + +/** Given <b>file_data</b> from start_writing_to_file(), return a stdio FILE* + * that can be used to write to the same file. The caller should not mix + * stdio calls with non-stdio calls. */ +FILE * +fdopen_file(open_file_t *file_data) +{ + tor_assert(file_data); + if (file_data->stdio_file) + return file_data->stdio_file; + tor_assert(file_data->fd >= 0); + if (!(file_data->stdio_file = fdopen(file_data->fd, + file_data->binary?"ab":"a"))) { + log_warn(LD_FS, "Couldn't fdopen \"%s\" [%d]: %s", file_data->filename, + file_data->fd, strerror(errno)); + } + return file_data->stdio_file; +} + +/** Combines start_writing_to_file with fdopen_file(): arguments are as + * for start_writing_to_file, but */ +FILE * +start_writing_to_stdio_file(const char *fname, int open_flags, int mode, + open_file_t **data_out) +{ + FILE *res; + if (start_writing_to_file(fname, open_flags, mode, data_out)<0) + return NULL; + if (!(res = fdopen_file(*data_out))) { + abort_writing_to_file(*data_out); + *data_out = NULL; + } + return res; +} + +/** Helper function: close and free the underlying file and memory in + * <b>file_data</b>. If we were writing into a temporary file, then delete + * that file (if abort_write is true) or replaces the target file with + * the temporary file (if abort_write is false). */ +static int +finish_writing_to_file_impl(open_file_t *file_data, int abort_write) +{ + int r = 0; + + tor_assert(file_data && file_data->filename); + if (file_data->stdio_file) { + if (fclose(file_data->stdio_file)) { + log_warn(LD_FS, "Error closing \"%s\": %s", file_data->filename, + strerror(errno)); + abort_write = r = -1; + } + } else if (file_data->fd >= 0 && close(file_data->fd) < 0) { + log_warn(LD_FS, "Error flushing \"%s\": %s", file_data->filename, + strerror(errno)); + abort_write = r = -1; + } + + if (file_data->rename_on_close) { + tor_assert(file_data->tempname && file_data->filename); + if (!abort_write) { + tor_assert(strcmp(file_data->filename, file_data->tempname)); + if (replace_file(file_data->tempname, file_data->filename)) { + log_warn(LD_FS, "Error replacing \"%s\": %s", file_data->filename, + strerror(errno)); + abort_write = r = -1; + } + } + if (abort_write) { + int res = unlink(file_data->tempname); + if (res != 0) { + /* We couldn't unlink and we'll leave a mess behind */ + log_warn(LD_FS, "Failed to unlink %s: %s", + file_data->tempname, strerror(errno)); + r = -1; + } + } + } + + tor_free(file_data->filename); + tor_free(file_data->tempname); + tor_free(file_data); + + return r; +} + +/** Finish writing to <b>file_data</b>: close the file handle, free memory as + * needed, and if using a temporary file, replace the original file with + * the temporary file. */ +int +finish_writing_to_file(open_file_t *file_data) +{ + return finish_writing_to_file_impl(file_data, 0); +} + +/** Finish writing to <b>file_data</b>: close the file handle, free memory as + * needed, and if using a temporary file, delete it. */ +int +abort_writing_to_file(open_file_t *file_data) +{ + return finish_writing_to_file_impl(file_data, 1); +} + +/** Helper: given a set of flags as passed to open(2), open the file + * <b>fname</b> and write all the sized_chunk_t structs in <b>chunks</b> to + * the file. Do so as atomically as possible e.g. by opening temp files and + * renaming. */ +static int +write_chunks_to_file_impl(const char *fname, const smartlist_t *chunks, + int open_flags) +{ + open_file_t *file = NULL; + int fd; + ssize_t result; + fd = start_writing_to_file(fname, open_flags, 0600, &file); + if (fd<0) + return -1; + SMARTLIST_FOREACH(chunks, sized_chunk_t *, chunk, + { + result = write_all_to_fd(fd, chunk->bytes, chunk->len); + if (result < 0) { + log_warn(LD_FS, "Error writing to \"%s\": %s", fname, + strerror(errno)); + goto err; + } + tor_assert((size_t)result == chunk->len); + }); + + return finish_writing_to_file(file); + err: + abort_writing_to_file(file); + return -1; +} + +/** Given a smartlist of sized_chunk_t, write them to a file + * <b>fname</b>, overwriting or creating the file as necessary. + * If <b>no_tempfile</b> is 0 then the file will be written + * atomically. */ +int +write_chunks_to_file(const char *fname, const smartlist_t *chunks, int bin, + int no_tempfile) +{ + int flags = OPEN_FLAGS_REPLACE|(bin?O_BINARY:O_TEXT); + + if (no_tempfile) { + /* O_APPEND stops write_chunks_to_file from using tempfiles */ + flags |= O_APPEND; + } + return write_chunks_to_file_impl(fname, chunks, flags); +} + +/** Write <b>len</b> bytes, starting at <b>str</b>, to <b>fname</b> + using the open() flags passed in <b>flags</b>. */ +static int +write_bytes_to_file_impl(const char *fname, const char *str, size_t len, + int flags) +{ + int r; + sized_chunk_t c = { str, len }; + smartlist_t *chunks = smartlist_new(); + smartlist_add(chunks, &c); + r = write_chunks_to_file_impl(fname, chunks, flags); + smartlist_free(chunks); + return r; +} + +/** As write_str_to_file, but does not assume a NUL-terminated + * string. Instead, we write <b>len</b> bytes, starting at <b>str</b>. */ +MOCK_IMPL(int, +write_bytes_to_file,(const char *fname, const char *str, size_t len, + int bin)) +{ + return write_bytes_to_file_impl(fname, str, len, + OPEN_FLAGS_REPLACE|(bin?O_BINARY:O_TEXT)); +} + +/** As write_bytes_to_file, but if the file already exists, append the bytes + * to the end of the file instead of overwriting it. */ +int +append_bytes_to_file(const char *fname, const char *str, size_t len, + int bin) +{ + return write_bytes_to_file_impl(fname, str, len, + OPEN_FLAGS_APPEND|(bin?O_BINARY:O_TEXT)); +} + +/** Like write_str_to_file(), but also return -1 if there was a file + already residing in <b>fname</b>. */ +int +write_bytes_to_new_file(const char *fname, const char *str, size_t len, + int bin) +{ + return write_bytes_to_file_impl(fname, str, len, + OPEN_FLAGS_DONT_REPLACE| + (bin?O_BINARY:O_TEXT)); +} + +/** + * Read the contents of the open file <b>fd</b> presuming it is a FIFO + * (or similar) file descriptor for which the size of the file isn't + * known ahead of time. Return NULL on failure, and a NUL-terminated + * string on success. On success, set <b>sz_out</b> to the number of + * bytes read. + */ +char * +read_file_to_str_until_eof(int fd, size_t max_bytes_to_read, size_t *sz_out) +{ + ssize_t r; + size_t pos = 0; + char *string = NULL; + size_t string_max = 0; + + if (max_bytes_to_read+1 >= SIZE_T_CEILING) { + errno = EINVAL; + return NULL; + } + + do { + /* XXXX This "add 1K" approach is a little goofy; if we care about + * performance here, we should be doubling. But in practice we shouldn't + * be using this function on big files anyway. */ + string_max = pos + 1024; + if (string_max > max_bytes_to_read) + string_max = max_bytes_to_read + 1; + string = tor_realloc(string, string_max); + r = read(fd, string + pos, string_max - pos - 1); + if (r < 0) { + int save_errno = errno; + tor_free(string); + errno = save_errno; + return NULL; + } + + pos += r; + } while (r > 0 && pos < max_bytes_to_read); + + tor_assert(pos < string_max); + *sz_out = pos; + string[pos] = '\0'; + return string; +} + +/** Read the contents of <b>filename</b> into a newly allocated + * string; return the string on success or NULL on failure. + * + * If <b>stat_out</b> is provided, store the result of stat()ing the + * file into <b>stat_out</b>. + * + * If <b>flags</b> & RFTS_BIN, open the file in binary mode. + * If <b>flags</b> & RFTS_IGNORE_MISSING, don't warn if the file + * doesn't exist. + */ +/* + * This function <em>may</em> return an erroneous result if the file + * is modified while it is running, but must not crash or overflow. + * Right now, the error case occurs when the file length grows between + * the call to stat and the call to read_all: the resulting string will + * be truncated. + */ +MOCK_IMPL(char *, +read_file_to_str, (const char *filename, int flags, struct stat *stat_out)) +{ + int fd; /* router file */ + struct stat statbuf; + char *string; + ssize_t r; + int bin = flags & RFTS_BIN; + + tor_assert(filename); + + fd = tor_open_cloexec(filename,O_RDONLY|(bin?O_BINARY:O_TEXT),0); + if (fd<0) { + int severity = LOG_WARN; + int save_errno = errno; + if (errno == ENOENT && (flags & RFTS_IGNORE_MISSING)) + severity = LOG_INFO; + log_fn(severity, LD_FS,"Could not open \"%s\": %s",filename, + strerror(errno)); + errno = save_errno; + return NULL; + } + + if (fstat(fd, &statbuf)<0) { + int save_errno = errno; + close(fd); + log_warn(LD_FS,"Could not fstat \"%s\".",filename); + errno = save_errno; + return NULL; + } + +#ifndef _WIN32 +/** When we detect that we're reading from a FIFO, don't read more than + * this many bytes. It's insane overkill for most uses. */ +#define FIFO_READ_MAX (1024*1024) + if (S_ISFIFO(statbuf.st_mode)) { + size_t sz = 0; + string = read_file_to_str_until_eof(fd, FIFO_READ_MAX, &sz); + int save_errno = errno; + if (string && stat_out) { + statbuf.st_size = sz; + memcpy(stat_out, &statbuf, sizeof(struct stat)); + } + close(fd); + if (!string) + errno = save_errno; + return string; + } +#endif /* !defined(_WIN32) */ + + if ((uint64_t)(statbuf.st_size)+1 >= SIZE_T_CEILING) { + close(fd); + errno = EINVAL; + return NULL; + } + + string = tor_malloc((size_t)(statbuf.st_size+1)); + + r = read_all_from_fd(fd,string,(size_t)statbuf.st_size); + if (r<0) { + int save_errno = errno; + log_warn(LD_FS,"Error reading from file \"%s\": %s", filename, + strerror(errno)); + tor_free(string); + close(fd); + errno = save_errno; + return NULL; + } + string[r] = '\0'; /* NUL-terminate the result. */ + +#if defined(_WIN32) || defined(__CYGWIN__) + if (!bin && strchr(string, '\r')) { + log_debug(LD_FS, "We didn't convert CRLF to LF as well as we hoped " + "when reading %s. Coping.", + filename); + tor_strstrip(string, "\r"); + r = strlen(string); + } + if (!bin) { + statbuf.st_size = (size_t) r; + } else +#endif /* defined(_WIN32) || defined(__CYGWIN__) */ + if (r != statbuf.st_size) { + /* Unless we're using text mode on win32, we'd better have an exact + * match for size. */ + int save_errno = errno; + log_warn(LD_FS,"Could read only %d of %ld bytes of file \"%s\".", + (int)r, (long)statbuf.st_size,filename); + tor_free(string); + close(fd); + errno = save_errno; + return NULL; + } + close(fd); + if (stat_out) { + memcpy(stat_out, &statbuf, sizeof(struct stat)); + } + + return string; +} + +#if !defined(HAVE_GETDELIM) || defined(TOR_UNIT_TESTS) +#include "ext/getdelim.c" +#endif diff --git a/src/lib/fs/files.h b/src/lib/fs/files.h new file mode 100644 index 0000000000..52c94c914f --- /dev/null +++ b/src/lib/fs/files.h @@ -0,0 +1,145 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file files.h + * + * \brief Header for files.c + **/ + +#ifndef TOR_FS_H +#define TOR_FS_H + +#include "lib/cc/compat_compiler.h" +#include "lib/cc/torint.h" +#include "lib/testsupport/testsupport.h" + +#include <stddef.h> +#include <stdio.h> + +#ifdef _WIN32 +/* We need these for struct stat to work */ +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#endif + +#ifndef O_BINARY +#define O_BINARY 0 +#endif +#ifndef O_TEXT +#define O_TEXT 0 +#endif +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 +#endif + +struct stat; + +int tor_open_cloexec(const char *path, int flags, unsigned mode); +FILE *tor_fopen_cloexec(const char *path, const char *mode); +int tor_rename(const char *path_old, const char *path_new); + +int replace_file(const char *from, const char *to); +int touch_file(const char *fname); + +MOCK_DECL(int,tor_unlink,(const char *pathname)); + +/** Return values from file_status(); see that function's documentation + * for details. */ +typedef enum { FN_ERROR, FN_NOENT, FN_FILE, FN_DIR, FN_EMPTY } file_status_t; + +file_status_t file_status(const char *filename); + +int64_t tor_get_avail_disk_space(const char *path); + +ssize_t write_all_to_fd(int fd, const char *buf, size_t count); +ssize_t read_all_from_fd(int fd, char *buf, size_t count); + +#define OPEN_FLAGS_REPLACE (O_WRONLY|O_CREAT|O_TRUNC) +#define OPEN_FLAGS_APPEND (O_WRONLY|O_CREAT|O_APPEND) +#define OPEN_FLAGS_DONT_REPLACE (O_CREAT|O_EXCL|O_APPEND|O_WRONLY) +typedef struct open_file_t open_file_t; +int start_writing_to_file(const char *fname, int open_flags, int mode, + open_file_t **data_out); +FILE *start_writing_to_stdio_file(const char *fname, int open_flags, int mode, + open_file_t **data_out); +FILE *fdopen_file(open_file_t *file_data); +int finish_writing_to_file(open_file_t *file_data); +int abort_writing_to_file(open_file_t *file_data); +MOCK_DECL(int, write_str_to_file,(const char *fname, const char *str, + int bin)); +MOCK_DECL(int, write_bytes_to_file,(const char *fname, const char *str, + size_t len,int bin)); + +/** An ad-hoc type to hold a string of characters and a count; used by + * write_chunks_to_file. */ +typedef struct sized_chunk_t { + const char *bytes; + size_t len; +} sized_chunk_t; +struct smartlist_t; +int write_chunks_to_file(const char *fname, const struct smartlist_t *chunks, + int bin, int no_tempfile); +int append_bytes_to_file(const char *fname, const char *str, size_t len, + int bin); +int write_bytes_to_new_file(const char *fname, const char *str, size_t len, + int bin); + +/** Flag for read_file_to_str: open the file in binary mode. */ +#define RFTS_BIN 1 +/** Flag for read_file_to_str: it's okay if the file doesn't exist. */ +#define RFTS_IGNORE_MISSING 2 + +MOCK_DECL_ATTR(char *, read_file_to_str,(const char *filename, int flags, + struct stat *stat_out), + ATTR_MALLOC); +char *read_file_to_str_until_eof(int fd, size_t max_bytes_to_read, + size_t *sz_out) + ATTR_MALLOC; + +#if !defined(HAVE_GETDELIM) || defined(TOR_UNIT_TESTS) +/** Internal back-end function to implement getdelim(): only exists when + * Tor is built for unit tests, or when Tor is built on an operating system + * without its own getdelim(). */ +ssize_t compat_getdelim_(char **lineptr, size_t *n, int delim, FILE *stream); +#endif + +#ifdef HAVE_GETDELIM +/** + * Cross-platform wrapper for getdelim(): behaves as the POSIX-standard + * getdelim() function. + * + * See `getdelim(3)` for more information. + * + * Note that this function will use the libc memory allocator -- so any memory + * passed to this function must come from raw_malloc(), and must be freed by + * raw_free() -- don't use tor_malloc() and tor_free() with this. + */ +#define tor_getdelim(lineptr, n, delim, stream) \ + getdelim((lineptr), (n), (delim), (stream)) +#else +#define tor_getdelim(lineptr, n, delim, stream) \ + compat_getdelim_((lineptr), (n), (delim), (stream)) +#endif + +#ifdef HAVE_GETLINE +/** + * Cross-platform wrapper for getline(): behaves as the POSIX-standard + * getline() function. + * + * See tor_getdelim() for usage notes. + */ +#define tor_getline(lineptr, n, stream) \ + getline((lineptr), (n), (stream)) +#else +#define tor_getline(lineptr, n, stream) \ + tor_getdelim((lineptr), (n), '\n', (stream)) +#endif + +#endif diff --git a/src/lib/fs/freespace.c b/src/lib/fs/freespace.c new file mode 100644 index 0000000000..ee0f93073d --- /dev/null +++ b/src/lib/fs/freespace.c @@ -0,0 +1,63 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file freespace.c + * + * \brief Find the available disk space on the current volume. + **/ + +#include "lib/fs/files.h" +#include "lib/cc/torint.h" + +#ifdef HAVE_SYS_STATVFS_H +#include <sys/statvfs.h> +#endif +#ifdef _WIN32 +#include <windows.h> +#endif + +#include <errno.h> +#include <string.h> + +/** Return the amount of free disk space we have permission to use, in + * bytes. Return -1 if the amount of free space can't be determined. */ +int64_t +tor_get_avail_disk_space(const char *path) +{ +#ifdef HAVE_STATVFS + struct statvfs st; + int r; + memset(&st, 0, sizeof(st)); + + r = statvfs(path, &st); + if (r < 0) + return -1; + + int64_t result = st.f_bavail; + if (st.f_frsize) { + result *= st.f_frsize; + } else if (st.f_bsize) { + result *= st.f_bsize; + } else { + return -1; + } + + return result; +#elif defined(_WIN32) + ULARGE_INTEGER freeBytesAvail; + BOOL ok; + + ok = GetDiskFreeSpaceEx(path, &freeBytesAvail, NULL, NULL); + if (!ok) { + return -1; + } + return (int64_t)freeBytesAvail.QuadPart; +#else + (void)path; + errno = ENOSYS; + return -1; +#endif /* defined(HAVE_STATVFS) || ... */ +} diff --git a/src/lib/fs/include.am b/src/lib/fs/include.am new file mode 100644 index 0000000000..f33e4d6430 --- /dev/null +++ b/src/lib/fs/include.am @@ -0,0 +1,37 @@ + +noinst_LIBRARIES += src/lib/libtor-fs.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-fs-testing.a +endif + +src_lib_libtor_fs_a_SOURCES = \ + src/lib/fs/conffile.c \ + src/lib/fs/dir.c \ + src/lib/fs/files.c \ + src/lib/fs/freespace.c \ + src/lib/fs/lockfile.c \ + src/lib/fs/mmap.c \ + src/lib/fs/path.c \ + src/lib/fs/storagedir.c \ + src/lib/fs/userdb.c + +if WIN32 +src_lib_libtor_fs_a_SOURCES += src/lib/fs/winlib.c +endif + +src_lib_libtor_fs_testing_a_SOURCES = \ + $(src_lib_libtor_fs_a_SOURCES) +src_lib_libtor_fs_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_fs_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/fs/conffile.h \ + src/lib/fs/dir.h \ + src/lib/fs/files.h \ + src/lib/fs/lockfile.h \ + src/lib/fs/mmap.h \ + src/lib/fs/path.h \ + src/lib/fs/storagedir.h \ + src/lib/fs/userdb.h \ + src/lib/fs/winlib.h diff --git a/src/lib/fs/lockfile.c b/src/lib/fs/lockfile.c new file mode 100644 index 0000000000..933ff1e02f --- /dev/null +++ b/src/lib/fs/lockfile.c @@ -0,0 +1,145 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file lockfile.c + * + * \brief Implements lock files to prevent two Tor processes from using the + * same data directory at the same time. + **/ + +#include "orconfig.h" +#include "lib/fs/files.h" +#include "lib/fs/lockfile.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" + +#ifdef HAVE_SYS_FILE_H +#include <sys/file.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef _WIN32 +#include <windows.h> +#include <sys/locking.h> +#endif + +#include <errno.h> +#include <string.h> + +/** Represents a lockfile on which we hold the lock. */ +struct tor_lockfile_t { + /** Name of the file */ + char *filename; + /** File descriptor used to hold the file open */ + int fd; +}; + +/** Try to get a lock on the lockfile <b>filename</b>, creating it as + * necessary. If someone else has the lock and <b>blocking</b> is true, + * wait until the lock is available. Otherwise return immediately whether + * we succeeded or not. + * + * Set *<b>locked_out</b> to true if somebody else had the lock, and to false + * otherwise. + * + * Return a <b>tor_lockfile_t</b> on success, NULL on failure. + * + * (Implementation note: because we need to fall back to fcntl on some + * platforms, these locks are per-process, not per-thread. If you want + * to do in-process locking, use tor_mutex_t like a normal person. + * On Windows, when <b>blocking</b> is true, the maximum time that + * is actually waited is 10 seconds, after which NULL is returned + * and <b>locked_out</b> is set to 1.) + */ +tor_lockfile_t * +tor_lockfile_lock(const char *filename, int blocking, int *locked_out) +{ + tor_lockfile_t *result; + int fd; + *locked_out = 0; + + log_info(LD_FS, "Locking \"%s\"", filename); + fd = tor_open_cloexec(filename, O_RDWR|O_CREAT|O_TRUNC, 0600); + if (fd < 0) { + log_warn(LD_FS,"Couldn't open \"%s\" for locking: %s", filename, + strerror(errno)); + return NULL; + } + +#ifdef _WIN32 + _lseek(fd, 0, SEEK_SET); + if (_locking(fd, blocking ? _LK_LOCK : _LK_NBLCK, 1) < 0) { + if (errno != EACCES && errno != EDEADLOCK) + log_warn(LD_FS,"Couldn't lock \"%s\": %s", filename, strerror(errno)); + else + *locked_out = 1; + close(fd); + return NULL; + } +#elif defined(HAVE_FLOCK) + if (flock(fd, LOCK_EX|(blocking ? 0 : LOCK_NB)) < 0) { + if (errno != EWOULDBLOCK) + log_warn(LD_FS,"Couldn't lock \"%s\": %s", filename, strerror(errno)); + else + *locked_out = 1; + close(fd); + return NULL; + } +#else + { + struct flock lock; + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + if (fcntl(fd, blocking ? F_SETLKW : F_SETLK, &lock) < 0) { + if (errno != EACCES && errno != EAGAIN) + log_warn(LD_FS, "Couldn't lock \"%s\": %s", filename, strerror(errno)); + else + *locked_out = 1; + close(fd); + return NULL; + } + } +#endif /* defined(_WIN32) || ... */ + + result = tor_malloc(sizeof(tor_lockfile_t)); + result->filename = tor_strdup(filename); + result->fd = fd; + return result; +} + +/** Release the lock held as <b>lockfile</b>. */ +void +tor_lockfile_unlock(tor_lockfile_t *lockfile) +{ + tor_assert(lockfile); + + log_info(LD_FS, "Unlocking \"%s\"", lockfile->filename); +#ifdef _WIN32 + _lseek(lockfile->fd, 0, SEEK_SET); + if (_locking(lockfile->fd, _LK_UNLCK, 1) < 0) { + log_warn(LD_FS,"Error unlocking \"%s\": %s", lockfile->filename, + strerror(errno)); + } +#elif defined(HAVE_FLOCK) + if (flock(lockfile->fd, LOCK_UN) < 0) { + log_warn(LD_FS, "Error unlocking \"%s\": %s", lockfile->filename, + strerror(errno)); + } +#else + /* Closing the lockfile is sufficient. */ +#endif /* defined(_WIN32) || ... */ + + close(lockfile->fd); + lockfile->fd = -1; + tor_free(lockfile->filename); + tor_free(lockfile); +} diff --git a/src/lib/fs/lockfile.h b/src/lib/fs/lockfile.h new file mode 100644 index 0000000000..8aeee4cc7f --- /dev/null +++ b/src/lib/fs/lockfile.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file lockfile.h + * + * \brief Header for lockfile.c + **/ + +#ifndef TOR_LOCKFILE_H +#define TOR_LOCKFILE_H + +typedef struct tor_lockfile_t tor_lockfile_t; +tor_lockfile_t *tor_lockfile_lock(const char *filename, int blocking, + int *locked_out); +void tor_lockfile_unlock(tor_lockfile_t *lockfile); + +#endif diff --git a/src/lib/fs/mmap.c b/src/lib/fs/mmap.c new file mode 100644 index 0000000000..daaee1f9b1 --- /dev/null +++ b/src/lib/fs/mmap.c @@ -0,0 +1,240 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file mmap.c + * + * \brief Cross-platform support for mapping files into our address space. + **/ + +#include "lib/fs/mmap.h" +#include "lib/fs/files.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/log/win32err.h" +#include "lib/string/compat_string.h" +#include "lib/malloc/malloc.h" + +#ifdef HAVE_MMAP +#include <sys/mman.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#ifdef _WIN32 +#include <windows.h> +#endif + +#include <errno.h> +#include <string.h> + +#if defined(HAVE_MMAP) || defined(RUNNING_DOXYGEN) +/** Try to create a memory mapping for <b>filename</b> and return it. On + * failure, return NULL. Sets errno properly, using ERANGE to mean + * "empty file". Must only be called on trusted Tor-owned files, as changing + * the underlying file's size causes unspecified behavior. */ +tor_mmap_t * +tor_mmap_file(const char *filename) +{ + int fd; /* router file */ + char *string; + int result; + tor_mmap_t *res; + size_t size, filesize; + struct stat st; + + tor_assert(filename); + + fd = tor_open_cloexec(filename, O_RDONLY, 0); + if (fd<0) { + int save_errno = errno; + int severity = (errno == ENOENT) ? LOG_INFO : LOG_WARN; + log_fn(severity, LD_FS,"Could not open \"%s\" for mmap(): %s",filename, + strerror(errno)); + errno = save_errno; + return NULL; + } + + /* Get the size of the file */ + result = fstat(fd, &st); + if (result != 0) { + int save_errno = errno; + log_warn(LD_FS, + "Couldn't fstat opened descriptor for \"%s\" during mmap: %s", + filename, strerror(errno)); + close(fd); + errno = save_errno; + return NULL; + } + size = filesize = (size_t)(st.st_size); + + if (st.st_size > SSIZE_T_CEILING || (off_t)size < st.st_size) { + log_warn(LD_FS, "File \"%s\" is too large. Ignoring.",filename); + errno = EFBIG; + close(fd); + return NULL; + } + if (!size) { + /* Zero-length file. If we call mmap on it, it will succeed but + * return NULL, and bad things will happen. So just fail. */ + log_info(LD_FS,"File \"%s\" is empty. Ignoring.",filename); + errno = ERANGE; + close(fd); + return NULL; + } + + string = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + if (string == MAP_FAILED) { + int save_errno = errno; + log_warn(LD_FS,"Could not mmap file \"%s\": %s", filename, + strerror(errno)); + errno = save_errno; + return NULL; + } + + res = tor_malloc_zero(sizeof(tor_mmap_t)); + res->data = string; + res->size = filesize; + res->mapping_size = size; + + return res; +} +/** Release storage held for a memory mapping; returns 0 on success, + * or -1 on failure (and logs a warning). */ +int +tor_munmap_file(tor_mmap_t *handle) +{ + int res; + + if (handle == NULL) + return 0; + + res = munmap((char*)handle->data, handle->mapping_size); + if (res == 0) { + /* munmap() succeeded */ + tor_free(handle); + } else { + log_warn(LD_FS, "Failed to munmap() in tor_munmap_file(): %s", + strerror(errno)); + res = -1; + } + + return res; +} +#elif defined(_WIN32) +tor_mmap_t * +tor_mmap_file(const char *filename) +{ + TCHAR tfilename[MAX_PATH]= {0}; + tor_mmap_t *res = tor_malloc_zero(sizeof(tor_mmap_t)); + int empty = 0; + HANDLE file_handle = INVALID_HANDLE_VALUE; + DWORD size_low, size_high; + uint64_t real_size; + res->mmap_handle = NULL; +#ifdef UNICODE + mbstowcs(tfilename,filename,MAX_PATH); +#else + strlcpy(tfilename,filename,MAX_PATH); +#endif + file_handle = CreateFile(tfilename, + GENERIC_READ, FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + 0); + + if (file_handle == INVALID_HANDLE_VALUE) + goto win_err; + + size_low = GetFileSize(file_handle, &size_high); + + if (size_low == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) { + log_warn(LD_FS,"Error getting size of \"%s\".",filename); + goto win_err; + } + if (size_low == 0 && size_high == 0) { + log_info(LD_FS,"File \"%s\" is empty. Ignoring.",filename); + empty = 1; + goto err; + } + real_size = (((uint64_t)size_high)<<32) | size_low; + if (real_size > SIZE_MAX) { + log_warn(LD_FS,"File \"%s\" is too big to map; not trying.",filename); + goto err; + } + res->size = real_size; + + res->mmap_handle = CreateFileMapping(file_handle, + NULL, + PAGE_READONLY, + size_high, + size_low, + NULL); + if (res->mmap_handle == NULL) + goto win_err; + res->data = (char*) MapViewOfFile(res->mmap_handle, + FILE_MAP_READ, + 0, 0, 0); + if (!res->data) + goto win_err; + + CloseHandle(file_handle); + return res; + win_err: { + DWORD e = GetLastError(); + int severity = (e == ERROR_FILE_NOT_FOUND || e == ERROR_PATH_NOT_FOUND) ? + LOG_INFO : LOG_WARN; + char *msg = format_win32_error(e); + log_fn(severity, LD_FS, "Couldn't mmap file \"%s\": %s", filename, msg); + tor_free(msg); + if (e == ERROR_FILE_NOT_FOUND || e == ERROR_PATH_NOT_FOUND) + errno = ENOENT; + else + errno = EINVAL; + } + err: + if (empty) + errno = ERANGE; + if (file_handle != INVALID_HANDLE_VALUE) + CloseHandle(file_handle); + tor_munmap_file(res); + return NULL; +} + +/* Unmap the file, and return 0 for success or -1 for failure */ +int +tor_munmap_file(tor_mmap_t *handle) +{ + if (handle == NULL) + return 0; + + if (handle->data) { + /* This is an ugly cast, but without it, "data" in struct tor_mmap_t would + have to be redefined as non-const. */ + BOOL ok = UnmapViewOfFile( (LPVOID) handle->data); + if (!ok) { + log_warn(LD_FS, "Failed to UnmapViewOfFile() in tor_munmap_file(): %d", + (int)GetLastError()); + } + } + + if (handle->mmap_handle != NULL) + CloseHandle(handle->mmap_handle); + tor_free(handle); + + return 0; +} +#else +#error "cannot implement tor_mmap_file" +#endif /* defined(HAVE_MMAP) || ... || ... */ diff --git a/src/lib/fs/mmap.h b/src/lib/fs/mmap.h new file mode 100644 index 0000000000..18fb18a13c --- /dev/null +++ b/src/lib/fs/mmap.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file mmap.h + * + * \brief Header for mmap.c + **/ + +#ifndef TOR_MMAP_H +#define TOR_MMAP_H + +#include "lib/cc/compat_compiler.h" +#include <stddef.h> + +#ifdef _WIN32 +#include <windef.h> +#endif + +/** Represents an mmaped file. Allocated via tor_mmap_file; freed with + * tor_munmap_file. */ +typedef struct tor_mmap_t { + const char *data; /**< Mapping of the file's contents. */ + size_t size; /**< Size of the file. */ + + /* None of the fields below should be accessed from outside compat.c */ +#ifdef HAVE_MMAP + size_t mapping_size; /**< Size of the actual mapping. (This is this file + * size, rounded up to the nearest page.) */ +#elif defined _WIN32 + HANDLE mmap_handle; +#endif /* defined(HAVE_MMAP) || ... */ + +} tor_mmap_t; + +tor_mmap_t *tor_mmap_file(const char *filename); +int tor_munmap_file(tor_mmap_t *handle); + +#endif diff --git a/src/lib/fs/path.c b/src/lib/fs/path.c new file mode 100644 index 0000000000..b3ef61979d --- /dev/null +++ b/src/lib/fs/path.c @@ -0,0 +1,295 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file path.c + * + * \brief Manipulate strings that contain filesystem paths. + **/ + +#include "lib/fs/path.h" +#include "lib/malloc/malloc.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/string/printf.h" +#include "lib/string/util_string.h" +#include "lib/string/compat_ctype.h" +#include "lib/fs/userdb.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <errno.h> +#include <string.h> + +/** Removes enclosing quotes from <b>path</b> and unescapes quotes between the + * enclosing quotes. Backslashes are not unescaped. Return the unquoted + * <b>path</b> on success or 0 if <b>path</b> is not quoted correctly. */ +char * +get_unquoted_path(const char *path) +{ + size_t len = strlen(path); + + if (len == 0) { + return tor_strdup(""); + } + + int has_start_quote = (path[0] == '\"'); + int has_end_quote = (len > 0 && path[len-1] == '\"'); + if (has_start_quote != has_end_quote || (len == 1 && has_start_quote)) { + return NULL; + } + + char *unquoted_path = tor_malloc(len - has_start_quote - has_end_quote + 1); + char *s = unquoted_path; + size_t i; + for (i = has_start_quote; i < len - has_end_quote; i++) { + if (path[i] == '\"' && (i > 0 && path[i-1] == '\\')) { + *(s-1) = path[i]; + } else if (path[i] != '\"') { + *s++ = path[i]; + } else { /* unescaped quote */ + tor_free(unquoted_path); + return NULL; + } + } + *s = '\0'; + return unquoted_path; +} + +/** Expand any homedir prefix on <b>filename</b>; return a newly allocated + * string. */ +char * +expand_filename(const char *filename) +{ + tor_assert(filename); +#ifdef _WIN32 + /* Might consider using GetFullPathName() as described here: + * http://etutorials.org/Programming/secure+programming/ + * Chapter+3.+Input+Validation/3.7+Validating+Filenames+and+Paths/ + */ + return tor_strdup(filename); +#else /* !(defined(_WIN32)) */ + if (*filename == '~') { + char *home, *result=NULL; + const char *rest; + + if (filename[1] == '/' || filename[1] == '\0') { + home = getenv("HOME"); + if (!home) { + log_warn(LD_CONFIG, "Couldn't find $HOME environment variable while " + "expanding \"%s\"; defaulting to \"\".", filename); + home = tor_strdup(""); + } else { + home = tor_strdup(home); + } + rest = strlen(filename)>=2?(filename+2):""; + } else { +#ifdef HAVE_PWD_H + char *username, *slash; + slash = strchr(filename, '/'); + if (slash) + username = tor_strndup(filename+1,slash-filename-1); + else + username = tor_strdup(filename+1); + if (!(home = get_user_homedir(username))) { + log_warn(LD_CONFIG,"Couldn't get homedir for \"%s\"",username); + tor_free(username); + return NULL; + } + tor_free(username); + rest = slash ? (slash+1) : ""; +#else /* !(defined(HAVE_PWD_H)) */ + log_warn(LD_CONFIG, "Couldn't expand homedir on system without pwd.h"); + return tor_strdup(filename); +#endif /* defined(HAVE_PWD_H) */ + } + tor_assert(home); + /* Remove trailing slash. */ + if (strlen(home)>1 && !strcmpend(home,PATH_SEPARATOR)) { + home[strlen(home)-1] = '\0'; + } + tor_asprintf(&result,"%s"PATH_SEPARATOR"%s",home,rest); + tor_free(home); + return result; + } else { + return tor_strdup(filename); + } +#endif /* defined(_WIN32) */ +} + +/** Return true iff <b>filename</b> is a relative path. */ +int +path_is_relative(const char *filename) +{ + if (filename && filename[0] == '/') + return 0; +#ifdef _WIN32 + else if (filename && filename[0] == '\\') + return 0; + else if (filename && strlen(filename)>3 && TOR_ISALPHA(filename[0]) && + filename[1] == ':' && filename[2] == '\\') + return 0; +#endif /* defined(_WIN32) */ + else + return 1; +} + +/** Clean up <b>name</b> so that we can use it in a call to "stat". On Unix, + * we do nothing. On Windows, we remove a trailing slash, unless the path is + * the root of a disk. */ +void +clean_fname_for_stat(char *name) +{ +#ifdef _WIN32 + size_t len = strlen(name); + if (!len) + return; + if (name[len-1]=='\\' || name[len-1]=='/') { + if (len == 1 || (len==3 && name[1]==':')) + return; + name[len-1]='\0'; + } +#else /* !(defined(_WIN32)) */ + (void)name; +#endif /* defined(_WIN32) */ +} + +/** Modify <b>fname</b> to contain the name of its parent directory. Doesn't + * actually examine the filesystem; does a purely syntactic modification. + * + * The parent of the root director is considered to be iteself. + * + * Path separators are the forward slash (/) everywhere and additionally + * the backslash (\) on Win32. + * + * Cuts off any number of trailing path separators but otherwise ignores + * them for purposes of finding the parent directory. + * + * Returns 0 if a parent directory was successfully found, -1 otherwise (fname + * did not have any path separators or only had them at the end). + * */ +int +get_parent_directory(char *fname) +{ + char *cp; + int at_end = 1; + tor_assert(fname); +#ifdef _WIN32 + /* If we start with, say, c:, then don't consider that the start of the path + */ + if (fname[0] && fname[1] == ':') { + fname += 2; + } +#endif /* defined(_WIN32) */ + /* Now we want to remove all path-separators at the end of the string, + * and to remove the end of the string starting with the path separator + * before the last non-path-separator. In perl, this would be + * s#[/]*$##; s#/[^/]*$##; + * on a unixy platform. + */ + cp = fname + strlen(fname); + at_end = 1; + while (--cp >= fname) { + int is_sep = (*cp == '/' +#ifdef _WIN32 + || *cp == '\\' +#endif + ); + if (is_sep) { + if (cp == fname) { + /* This is the first separator in the file name; don't remove it! */ + cp[1] = '\0'; + return 0; + } + *cp = '\0'; + if (! at_end) + return 0; + } else { + at_end = 0; + } + } + return -1; +} + +#ifndef _WIN32 +/** Return a newly allocated string containing the output of getcwd(). Return + * NULL on failure. (We can't just use getcwd() into a PATH_MAX buffer, since + * Hurd hasn't got a PATH_MAX.) + */ +static char * +alloc_getcwd(void) +{ +#ifdef HAVE_GET_CURRENT_DIR_NAME + /* Glibc makes this nice and simple for us. */ + char *cwd = get_current_dir_name(); + char *result = NULL; + if (cwd) { + /* We make a copy here, in case tor_malloc() is not malloc(). */ + result = tor_strdup(cwd); + raw_free(cwd); // alias for free to avoid tripping check-spaces. + } + return result; +#else /* !(defined(HAVE_GET_CURRENT_DIR_NAME)) */ + size_t size = 1024; + char *buf = NULL; + char *ptr = NULL; + + while (ptr == NULL) { + buf = tor_realloc(buf, size); + ptr = getcwd(buf, size); + + if (ptr == NULL && errno != ERANGE) { + tor_free(buf); + return NULL; + } + + size *= 2; + } + return buf; +#endif /* defined(HAVE_GET_CURRENT_DIR_NAME) */ +} +#endif /* !defined(_WIN32) */ + +/** Expand possibly relative path <b>fname</b> to an absolute path. + * Return a newly allocated string, possibly equal to <b>fname</b>. */ +char * +make_path_absolute(char *fname) +{ +#ifdef _WIN32 + char *absfname_malloced = _fullpath(NULL, fname, 1); + + /* We don't want to assume that tor_free can free a string allocated + * with malloc. On failure, return fname (it's better than nothing). */ + char *absfname = tor_strdup(absfname_malloced ? absfname_malloced : fname); + if (absfname_malloced) raw_free(absfname_malloced); + + return absfname; +#else /* !(defined(_WIN32)) */ + char *absfname = NULL, *path = NULL; + + tor_assert(fname); + + if (fname[0] == '/') { + absfname = tor_strdup(fname); + } else { + path = alloc_getcwd(); + if (path) { + tor_asprintf(&absfname, "%s/%s", path, fname); + tor_free(path); + } else { + /* LCOV_EXCL_START Can't make getcwd fail. */ + /* If getcwd failed, the best we can do here is keep using the + * relative path. (Perhaps / isn't readable by this UID/GID.) */ + log_warn(LD_GENERAL, "Unable to find current working directory: %s", + strerror(errno)); + absfname = tor_strdup(fname); + /* LCOV_EXCL_STOP */ + } + } + return absfname; +#endif /* defined(_WIN32) */ +} diff --git a/src/lib/fs/path.h b/src/lib/fs/path.h new file mode 100644 index 0000000000..4675ac84e8 --- /dev/null +++ b/src/lib/fs/path.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file path.h + * + * \brief Header for path.c + **/ + +#ifndef TOR_PATH_H +#define TOR_PATH_H + +#include "lib/cc/compat_compiler.h" + +#ifdef _WIN32 +#define PATH_SEPARATOR "\\" +#else +#define PATH_SEPARATOR "/" +#endif + +char *get_unquoted_path(const char *path); +char *expand_filename(const char *filename); +int path_is_relative(const char *filename); +void clean_fname_for_stat(char *name); +int get_parent_directory(char *fname); +char *make_path_absolute(char *fname); + +#endif diff --git a/src/lib/fs/storagedir.c b/src/lib/fs/storagedir.c new file mode 100644 index 0000000000..2caddf1ad9 --- /dev/null +++ b/src/lib/fs/storagedir.c @@ -0,0 +1,606 @@ +/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file storagedir.c + * + * \brief An abstraction for a directory full of similar files. + * + * Storagedirs are used by our consensus cache code, and may someday also get + * used for unparseable objects. A large part of the need for this type is to + * work around the limitations in our sandbox code, where all filenames need + * to be registered in advance. + **/ + +#include "lib/fs/storagedir.h" + +#include "lib/container/smartlist.h" +#include "lib/encoding/confline.h" +#include "lib/fs/dir.h" +#include "lib/fs/files.h" +#include "lib/fs/mmap.h" +#include "lib/log/escape.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/memarea/memarea.h" +#include "lib/sandbox/sandbox.h" +#include "lib/string/printf.h" +#include "lib/string/util_string.h" + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#define FNAME_MIN_NUM 1000 + +/** A storage_dir_t represents a directory full of similar cached + * files. Filenames are decimal integers. Files can be cleaned as needed + * to limit total disk usage. */ +struct storage_dir_t { + /** Directory holding the files for this storagedir. */ + char *directory; + /** Either NULL, or a directory listing of the directory (as a smartlist + * of strings */ + smartlist_t *contents; + /** The largest number of non-temporary files we'll place in the + * directory. */ + int max_files; + /** If true, then 'usage' has been computed. */ + int usage_known; + /** The total number of bytes used in this directory */ + uint64_t usage; +}; + +/** Create or open a new storage directory at <b>dirname</b>, with + * capacity for up to <b>max_files</b> files. + */ +storage_dir_t * +storage_dir_new(const char *dirname, int max_files) +{ + if (check_private_dir(dirname, CPD_CREATE, NULL) < 0) + return NULL; + + storage_dir_t *d = tor_malloc_zero(sizeof(storage_dir_t)); + d->directory = tor_strdup(dirname); + d->max_files = max_files; + return d; +} + +/** + * Drop all in-RAM storage for <b>d</b>. Does not delete any files. + */ +void +storage_dir_free_(storage_dir_t *d) +{ + if (d == NULL) + return; + tor_free(d->directory); + if (d->contents) { + SMARTLIST_FOREACH(d->contents, char *, cp, tor_free(cp)); + smartlist_free(d->contents); + } + tor_free(d); +} + +/** + * Tell the sandbox (if any) configured by <b>cfg</b> to allow the + * operations that <b>d</b> will need. + * + * The presence of this function is why we need an upper limit on the + * number of files in a storage_dir_t: we need to approve file operations + * one by one. + */ +int +storage_dir_register_with_sandbox(storage_dir_t *d, sandbox_cfg_t **cfg) +{ + int problems = 0; + int idx; + for (idx = FNAME_MIN_NUM; idx < FNAME_MIN_NUM + d->max_files; ++idx) { + char *path = NULL, *tmppath = NULL; + tor_asprintf(&path, "%s/%d", d->directory, idx); + tor_asprintf(&tmppath, "%s/%d.tmp", d->directory, idx); + + problems += sandbox_cfg_allow_open_filename(cfg, tor_strdup(path)); + problems += sandbox_cfg_allow_open_filename(cfg, tor_strdup(tmppath)); + problems += sandbox_cfg_allow_stat_filename(cfg, tor_strdup(path)); + problems += sandbox_cfg_allow_stat_filename(cfg, tor_strdup(tmppath)); + problems += sandbox_cfg_allow_rename(cfg, + tor_strdup(tmppath), tor_strdup(path)); + + tor_free(path); + tor_free(tmppath); + } + + return problems ? -1 : 0; +} + +/** + * Remove all files in <b>d</b> whose names end with ".tmp". + * + * Requires that the contents field of <b>d</b> is set. + */ +static void +storage_dir_clean_tmpfiles(storage_dir_t *d) +{ + if (!d->contents) + return; + SMARTLIST_FOREACH_BEGIN(d->contents, char *, fname) { + if (strcmpend(fname, ".tmp")) + continue; + char *path = NULL; + tor_asprintf(&path, "%s/%s", d->directory, fname); + if (unlink(sandbox_intern_string(path))) { + log_warn(LD_FS, "Unable to unlink %s while cleaning " + "temporary files: %s", escaped(path), strerror(errno)); + tor_free(path); + continue; + } + tor_free(path); + SMARTLIST_DEL_CURRENT(d->contents, fname); + tor_free(fname); + } SMARTLIST_FOREACH_END(fname); + + d->usage_known = 0; +} + +/** + * Re-scan the directory <b>d</b> to learn its contents. + */ +static int +storage_dir_rescan(storage_dir_t *d) +{ + if (d->contents) { + SMARTLIST_FOREACH(d->contents, char *, cp, tor_free(cp)); + smartlist_free(d->contents); + } + d->usage = 0; + d->usage_known = 0; + if (NULL == (d->contents = tor_listdir(d->directory))) { + return -1; + } + storage_dir_clean_tmpfiles(d); + return 0; +} + +/** + * Return a smartlist containing the filenames within <b>d</b>. + */ +const smartlist_t * +storage_dir_list(storage_dir_t *d) +{ + if (! d->contents) + storage_dir_rescan(d); + return d->contents; +} + +/** + * Return the total number of bytes used for storage in <b>d</b>. + */ +uint64_t +storage_dir_get_usage(storage_dir_t *d) +{ + if (d->usage_known) + return d->usage; + + uint64_t total = 0; + SMARTLIST_FOREACH_BEGIN(storage_dir_list(d), const char *, cp) { + char *path = NULL; + struct stat st; + tor_asprintf(&path, "%s/%s", d->directory, cp); + if (stat(sandbox_intern_string(path), &st) == 0) { + total += st.st_size; + } + tor_free(path); + } SMARTLIST_FOREACH_END(cp); + + d->usage = total; + d->usage_known = 1; + return d->usage; +} + +/** Mmap a specified file within <b>d</b>. + * + * On failure, return NULL and set errno as for tor_mmap_file(). */ +tor_mmap_t * +storage_dir_map(storage_dir_t *d, const char *fname) +{ + char *path = NULL; + tor_asprintf(&path, "%s/%s", d->directory, fname); + tor_mmap_t *result = tor_mmap_file(path); + int errval = errno; + tor_free(path); + if (result == NULL) + errno = errval; + return result; +} + +/** Read a file within <b>d</b> into a newly allocated buffer. Set + * *<b>sz_out</b> to its size. */ +uint8_t * +storage_dir_read(storage_dir_t *d, const char *fname, int bin, size_t *sz_out) +{ + const int flags = bin ? RFTS_BIN : 0; + + char *path = NULL; + tor_asprintf(&path, "%s/%s", d->directory, fname); + struct stat st; + char *contents = read_file_to_str(path, flags, &st); + if (contents && sz_out) { + // it fits in RAM, so we know its size is less than SIZE_MAX +#if UINT64_MAX > SIZE_MAX + tor_assert((uint64_t)st.st_size <= SIZE_MAX); +#endif + *sz_out = (size_t) st.st_size; + } + + tor_free(path); + return (uint8_t *) contents; +} + +/** Helper: Find an unused filename within the directory */ +static char * +find_unused_fname(storage_dir_t *d) +{ + if (!d->contents) { + if (storage_dir_rescan(d) < 0) + return NULL; + } + + char buf[16]; + int i; + /* Yuck; this is quadratic. Fortunately, that shouldn't matter much, + * since disk writes are more expensive by a lot. */ + for (i = FNAME_MIN_NUM; i < FNAME_MIN_NUM + d->max_files; ++i) { + tor_snprintf(buf, sizeof(buf), "%d", i); + if (!smartlist_contains_string(d->contents, buf)) { + return tor_strdup(buf); + } + } + return NULL; +} + +/** Helper: As storage_dir_save_bytes_to_file, but store a smartlist of + * sized_chunk_t rather than a single byte array. */ +static int +storage_dir_save_chunks_to_file(storage_dir_t *d, + const smartlist_t *chunks, + int binary, + char **fname_out) +{ + uint64_t total_length = 0; + char *fname = find_unused_fname(d); + if (!fname) + return -1; + + SMARTLIST_FOREACH(chunks, const sized_chunk_t *, ch, + total_length += ch->len); + + char *path = NULL; + tor_asprintf(&path, "%s/%s", d->directory, fname); + + int r = write_chunks_to_file(path, chunks, binary, 0); + if (r == 0) { + if (d->usage_known) + d->usage += total_length; + if (fname_out) { + *fname_out = tor_strdup(fname); + } + if (d->contents) + smartlist_add(d->contents, tor_strdup(fname)); + } + tor_free(fname); + tor_free(path); + return r; +} + +/** Try to write the <b>length</b> bytes at <b>data</b> into a new file + * in <b>d</b>. On success, return 0 and set *<b>fname_out</b> to a + * newly allocated string containing the filename. On failure, return + * -1. */ +int +storage_dir_save_bytes_to_file(storage_dir_t *d, + const uint8_t *data, + size_t length, + int binary, + char **fname_out) +{ + smartlist_t *chunks = smartlist_new(); + sized_chunk_t chunk = { (const char *)data, length }; + smartlist_add(chunks, &chunk); + int r = storage_dir_save_chunks_to_file(d, chunks, binary, fname_out); + smartlist_free(chunks); + return r; +} + +/** + * As storage_dir_save_bytes_to_file, but saves a NUL-terminated string + * <b>str</b>. + */ +int +storage_dir_save_string_to_file(storage_dir_t *d, + const char *str, + int binary, + char **fname_out) +{ + return storage_dir_save_bytes_to_file(d, + (const uint8_t*)str, strlen(str), binary, fname_out); +} + +/** + * As storage_dir_save_bytes_to_file, but associates the data with the + * key-value pairs in <b>labels</b>. Files stored in this format can be + * recovered with storage_dir_map_labeled() or storage_dir_read_labeled(). + */ +int +storage_dir_save_labeled_to_file(storage_dir_t *d, + const config_line_t *labels, + const uint8_t *data, + size_t length, + char **fname_out) +{ + /* + * The storage format is to prefix the data with the key-value pairs in + * <b>labels</b>, and a single NUL separator. But code outside this module + * MUST NOT rely on that format. + */ + + smartlist_t *chunks = smartlist_new(); + memarea_t *area = memarea_new(); + const config_line_t *line; + for (line = labels; line; line = line->next) { + sized_chunk_t *sz = memarea_alloc(area, sizeof(sized_chunk_t)); + sz->len = strlen(line->key) + 1 + strlen(line->value) + 1; + const size_t allocated = sz->len + 1; + char *bytes = memarea_alloc(area, allocated); + tor_snprintf(bytes, allocated, "%s %s\n", line->key, line->value); + sz->bytes = bytes; + smartlist_add(chunks, sz); + } + + sized_chunk_t *nul = memarea_alloc(area, sizeof(sized_chunk_t)); + nul->len = 1; + nul->bytes = "\0"; + smartlist_add(chunks, nul); + + sized_chunk_t *datachunk = memarea_alloc(area, sizeof(sized_chunk_t)); + datachunk->bytes = (const char *)data; + datachunk->len = length; + smartlist_add(chunks, datachunk); + + int r = storage_dir_save_chunks_to_file(d, chunks, 1, fname_out); + smartlist_free(chunks); + memarea_drop_all(area); + return r; +} + +/** + * Map a file that was created with storage_dir_save_labeled_to_file(). On + * failure, return NULL. On success, write a set of newly allocated labels + * into *<b>labels_out</b>, a pointer to the data into *<b>data_out</b>, and + * the data's size into *<b>sz_out</b>. On success, also return a tor_mmap_t + * object whose contents should not be used -- it needs to be kept around, + * though, for as long as <b>data_out</b> is going to be valid. + * + * On failure, set errno as for tor_mmap_file() if the file was missing or + * empty, and set errno to EINVAL if the file was not in the labeled + * format expected. + */ +tor_mmap_t * +storage_dir_map_labeled(storage_dir_t *dir, + const char *fname, + config_line_t **labels_out, + const uint8_t **data_out, + size_t *sz_out) +{ + tor_mmap_t *m = storage_dir_map(dir, fname); + int errval; + if (! m) { + errval = errno; + goto err; + } + const char *nulp = memchr(m->data, '\0', m->size); + if (! nulp) { + errval = EINVAL; + goto err; + } + if (labels_out && config_get_lines(m->data, labels_out, 0) < 0) { + errval = EINVAL; + goto err; + } + size_t offset = nulp - m->data + 1; + tor_assert(offset <= m->size); + *data_out = (const uint8_t *)(m->data + offset); + *sz_out = m->size - offset; + + return m; + err: + tor_munmap_file(m); + errno = errval; + return NULL; +} + +/** As storage_dir_map_labeled, but return a new byte array containing the + * data. */ +uint8_t * +storage_dir_read_labeled(storage_dir_t *dir, + const char *fname, + config_line_t **labels_out, + size_t *sz_out) +{ + const uint8_t *data = NULL; + tor_mmap_t *m = storage_dir_map_labeled(dir, fname, labels_out, + &data, sz_out); + if (m == NULL) + return NULL; + uint8_t *result = tor_memdup(data, *sz_out); + tor_munmap_file(m); + return result; +} + +/* Reduce the cached usage amount in <b>d</b> by <b>removed_file_size</b>. + * This function is a no-op if <b>d->usage_known</b> is 0. */ +static void +storage_dir_reduce_usage(storage_dir_t *d, uint64_t removed_file_size) +{ + if (d->usage_known) { + if (! BUG(d->usage < removed_file_size)) { + /* This bug can also be triggered if an external process resized a file + * between the call to storage_dir_get_usage() that last checked + * actual usage (rather than relaying on cached usage), and the call to + * this function. */ + d->usage -= removed_file_size; + } else { + /* If we underflowed the cached directory size, re-check the sizes of all + * the files in the directory. This makes storage_dir_shrink() quadratic, + * but only if a process is continually changing file sizes in the + * storage directory (in which case, we have bigger issues). + * + * We can't just reset usage_known, because storage_dir_shrink() relies + * on knowing the usage. */ + storage_dir_rescan(d); + (void)storage_dir_get_usage(d); + } + } +} + +/** + * Remove the file called <b>fname</b> from <b>d</b>. + */ +void +storage_dir_remove_file(storage_dir_t *d, + const char *fname) +{ + char *path = NULL; + tor_asprintf(&path, "%s/%s", d->directory, fname); + const char *ipath = sandbox_intern_string(path); + + uint64_t size = 0; + if (d->usage_known) { + struct stat st; + if (stat(ipath, &st) == 0) { + size = st.st_size; + } + } + if (unlink(ipath) == 0) { + storage_dir_reduce_usage(d, size); + } else { + log_warn(LD_FS, "Unable to unlink %s while removing file: %s", + escaped(path), strerror(errno)); + tor_free(path); + return; + } + if (d->contents) { + smartlist_string_remove(d->contents, fname); + } + + tor_free(path); +} + +/** Helper type: used to sort the members of storage directory by mtime. */ +typedef struct shrinking_dir_entry_t { + time_t mtime; + uint64_t size; + char *path; +} shrinking_dir_entry_t; + +/** Helper: use with qsort to sort shrinking_dir_entry_t structs. */ +static int +shrinking_dir_entry_compare(const void *a_, const void *b_) +{ + const shrinking_dir_entry_t *a = a_; + const shrinking_dir_entry_t *b = b_; + + if (a->mtime < b->mtime) + return -1; + else if (a->mtime > b->mtime) + return 1; + else + return 0; +} + +/** + * Try to free space by removing the oldest files in <b>d</b>. Delete + * until no more than <b>target_size</b> bytes are left, and at least + * <b>min_to_remove</b> files have been removed... or until there is + * nothing left to remove. + * + * Return 0 on success; -1 on failure. + */ +int +storage_dir_shrink(storage_dir_t *d, + uint64_t target_size, + int min_to_remove) +{ + if (d->usage_known && d->usage <= target_size && !min_to_remove) { + /* Already small enough. */ + return 0; + } + + if (storage_dir_rescan(d) < 0) + return -1; + + const uint64_t orig_usage = storage_dir_get_usage(d); + if (orig_usage <= target_size && !min_to_remove) { + /* Okay, small enough after rescan! */ + return 0; + } + + const int n = smartlist_len(d->contents); + shrinking_dir_entry_t *ents = tor_calloc(n, sizeof(shrinking_dir_entry_t)); + SMARTLIST_FOREACH_BEGIN(d->contents, const char *, fname) { + shrinking_dir_entry_t *ent = &ents[fname_sl_idx]; + struct stat st; + tor_asprintf(&ent->path, "%s/%s", d->directory, fname); + if (stat(sandbox_intern_string(ent->path), &st) == 0) { + ent->mtime = st.st_mtime; + ent->size = st.st_size; + } + } SMARTLIST_FOREACH_END(fname); + + qsort(ents, n, sizeof(shrinking_dir_entry_t), shrinking_dir_entry_compare); + + int idx = 0; + while ((d->usage > target_size || min_to_remove > 0) && idx < n) { + if (unlink(sandbox_intern_string(ents[idx].path)) == 0) { + storage_dir_reduce_usage(d, ents[idx].size); + --min_to_remove; + } + ++idx; + } + + for (idx = 0; idx < n; ++idx) { + tor_free(ents[idx].path); + } + tor_free(ents); + + storage_dir_rescan(d); + + return 0; +} + +/** Remove all files in <b>d</b>. */ +int +storage_dir_remove_all(storage_dir_t *d) +{ + return storage_dir_shrink(d, 0, d->max_files); +} + +/** + * Return the largest number of non-temporary files we're willing to + * store in <b>d</b>. + */ +int +storage_dir_get_max_files(storage_dir_t *d) +{ + return d->max_files; +} diff --git a/src/lib/fs/storagedir.h b/src/lib/fs/storagedir.h new file mode 100644 index 0000000000..7e6633a0bb --- /dev/null +++ b/src/lib/fs/storagedir.h @@ -0,0 +1,64 @@ +/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file storagedir.h + * + * \brief Header for storagedir.c + **/ + +#ifndef TOR_STORAGEDIR_H +#define TOR_STORAGEDIR_H + +#include "lib/cc/torint.h" +#include <stddef.h> + +typedef struct storage_dir_t storage_dir_t; +struct config_line_t; +struct sandbox_cfg_elem; +struct tor_mmap_t; +struct smartlist_t; + +storage_dir_t * storage_dir_new(const char *dirname, int n_files); +void storage_dir_free_(storage_dir_t *d); +#define storage_dir_free(d) \ + FREE_AND_NULL(storage_dir_t, storage_dir_free_, (d)) + +int storage_dir_register_with_sandbox(storage_dir_t *d, + struct sandbox_cfg_elem **cfg); +const struct smartlist_t *storage_dir_list(storage_dir_t *d); +uint64_t storage_dir_get_usage(storage_dir_t *d); +struct tor_mmap_t *storage_dir_map(storage_dir_t *d, const char *fname); +uint8_t *storage_dir_read(storage_dir_t *d, const char *fname, int bin, + size_t *sz_out); +int storage_dir_save_bytes_to_file(storage_dir_t *d, + const uint8_t *data, + size_t length, + int binary, + char **fname_out); +int storage_dir_save_string_to_file(storage_dir_t *d, + const char *data, + int binary, + char **fname_out); +int storage_dir_save_labeled_to_file(storage_dir_t *d, + const struct config_line_t *labels, + const uint8_t *data, + size_t length, + char **fname_out); +struct tor_mmap_t *storage_dir_map_labeled(storage_dir_t *dir, + const char *fname, + struct config_line_t **labels_out, + const uint8_t **data_out, + size_t *size_out); +uint8_t *storage_dir_read_labeled(storage_dir_t *d, const char *fname, + struct config_line_t **labels_out, + size_t *sz_out); +void storage_dir_remove_file(storage_dir_t *d, + const char *fname); +int storage_dir_shrink(storage_dir_t *d, + uint64_t target_size, + int min_to_remove); +int storage_dir_remove_all(storage_dir_t *d); +int storage_dir_get_max_files(storage_dir_t *d); + +#endif /* !defined(TOR_STORAGEDIR_H) */ diff --git a/src/lib/fs/userdb.c b/src/lib/fs/userdb.c new file mode 100644 index 0000000000..95205c670e --- /dev/null +++ b/src/lib/fs/userdb.c @@ -0,0 +1,138 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file userdb.c + * + * \brief Access the POSIX user database. + **/ + +#include "lib/fs/userdb.h" + +#ifndef _WIN32 +#include "lib/malloc/malloc.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +#include <pwd.h> +#include <stddef.h> +#include <string.h> + +/** Cached struct from the last getpwname() call we did successfully. */ +static struct passwd *passwd_cached = NULL; + +/** Helper: copy a struct passwd object. + * + * We only copy the fields pw_uid, pw_gid, pw_name, pw_dir. Tor doesn't use + * any others, and I don't want to run into incompatibilities. + */ +static struct passwd * +tor_passwd_dup(const struct passwd *pw) +{ + struct passwd *new_pw = tor_malloc_zero(sizeof(struct passwd)); + if (pw->pw_name) + new_pw->pw_name = tor_strdup(pw->pw_name); + if (pw->pw_dir) + new_pw->pw_dir = tor_strdup(pw->pw_dir); + new_pw->pw_uid = pw->pw_uid; + new_pw->pw_gid = pw->pw_gid; + + return new_pw; +} + +#define tor_passwd_free(pw) \ + FREE_AND_NULL(struct passwd, tor_passwd_free_, (pw)) + +/** Helper: free one of our cached 'struct passwd' values. */ +static void +tor_passwd_free_(struct passwd *pw) +{ + if (!pw) + return; + + tor_free(pw->pw_name); + tor_free(pw->pw_dir); + tor_free(pw); +} + +/** Wrapper around getpwnam() that caches result. Used so that we don't need + * to give the sandbox access to /etc/passwd. + * + * The following fields alone will definitely be copied in the output: pw_uid, + * pw_gid, pw_name, pw_dir. Other fields are not present in cached values. + * + * When called with a NULL argument, this function clears storage associated + * with static variables it uses. + **/ +const struct passwd * +tor_getpwnam(const char *username) +{ + struct passwd *pw; + + if (username == NULL) { + tor_passwd_free(passwd_cached); + passwd_cached = NULL; + return NULL; + } + + if ((pw = getpwnam(username))) { + tor_passwd_free(passwd_cached); + passwd_cached = tor_passwd_dup(pw); + log_info(LD_GENERAL, "Caching new entry %s for %s", + passwd_cached->pw_name, username); + return pw; + } + + /* Lookup failed */ + if (! passwd_cached || ! passwd_cached->pw_name) + return NULL; + + if (! strcmp(username, passwd_cached->pw_name)) + return passwd_cached; // LCOV_EXCL_LINE - would need to make getpwnam flaky + + return NULL; +} + +/** Wrapper around getpwnam() that can use cached result from + * tor_getpwnam(). Used so that we don't need to give the sandbox access to + * /etc/passwd. + * + * The following fields alone will definitely be copied in the output: pw_uid, + * pw_gid, pw_name, pw_dir. Other fields are not present in cached values. + */ +const struct passwd * +tor_getpwuid(uid_t uid) +{ + struct passwd *pw; + + if ((pw = getpwuid(uid))) { + return pw; + } + + /* Lookup failed */ + if (! passwd_cached) + return NULL; + + if (uid == passwd_cached->pw_uid) + return passwd_cached; // LCOV_EXCL_LINE - would need to make getpwnam flaky + + return NULL; +} + +/** Allocate and return a string containing the home directory for the + * user <b>username</b>. Only works on posix-like systems. */ +char * +get_user_homedir(const char *username) +{ + const struct passwd *pw; + tor_assert(username); + + if (!(pw = tor_getpwnam(username))) { + log_err(LD_CONFIG,"User \"%s\" not found.", username); + return NULL; + } + return tor_strdup(pw->pw_dir); +} +#endif /* !defined(_WIN32) */ diff --git a/src/lib/fs/userdb.h b/src/lib/fs/userdb.h new file mode 100644 index 0000000000..5c39794873 --- /dev/null +++ b/src/lib/fs/userdb.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file userdb.h + * + * \brief Header for userdb.c + **/ + +#ifndef TOR_USERDB_H +#define TOR_USERDB_H + +#include "orconfig.h" + +#ifndef _WIN32 +#include <sys/types.h> + +struct passwd; +const struct passwd *tor_getpwnam(const char *username); +const struct passwd *tor_getpwuid(uid_t uid); +char *get_user_homedir(const char *username); +#endif + +#endif diff --git a/src/lib/fs/winlib.c b/src/lib/fs/winlib.c new file mode 100644 index 0000000000..b7302bd4ca --- /dev/null +++ b/src/lib/fs/winlib.c @@ -0,0 +1,30 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file winlib.c + * + * \brief Find and load windows system libraries. + * + * We use this function to dynamically load code at runtime that might not be + * available on all versions of Windows that we support. + **/ + +#ifdef _WIN32 +#include "lib/fs/winlib.h" + +HANDLE +load_windows_system_library(const TCHAR *library_name) +{ + TCHAR path[MAX_PATH]; + unsigned n; + n = GetSystemDirectory(path, MAX_PATH); + if (n == 0 || n + _tcslen(library_name) + 2 >= MAX_PATH) + return 0; + _tcscat(path, TEXT("\\")); + _tcscat(path, library_name); + return LoadLibrary(path); +} +#endif /* defined(_WIN32) */ diff --git a/src/lib/fs/winlib.h b/src/lib/fs/winlib.h new file mode 100644 index 0000000000..64a22439e5 --- /dev/null +++ b/src/lib/fs/winlib.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file winlib.h + * + * \brief Header for winlib.c + **/ + +#ifndef TOR_WINLIB_H +#define TOR_WINLIB_H + +#ifdef _WIN32 +#include <windows.h> +#include <tchar.h> + +HANDLE load_windows_system_library(const TCHAR *library_name); +#endif + +#endif diff --git a/src/lib/geoip/.may_include b/src/lib/geoip/.may_include new file mode 100644 index 0000000000..b1ee2dcfe9 --- /dev/null +++ b/src/lib/geoip/.may_include @@ -0,0 +1,13 @@ +orconfig.h +lib/cc/*.h +lib/container/*.h +lib/crypt_ops/*.h +lib/ctime/*.h +lib/encoding/*.h +lib/fs/*.h +lib/geoip/*.h +lib/log/*.h +lib/malloc/*.h +lib/net/*.h +lib/string/*.h +lib/testsupport/*.h diff --git a/src/lib/geoip/country.h b/src/lib/geoip/country.h new file mode 100644 index 0000000000..9a8911d494 --- /dev/null +++ b/src/lib/geoip/country.h @@ -0,0 +1,16 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_COUNTRY_H +#define TOR_COUNTRY_H + +#include "lib/cc/torint.h" +/** A signed integer representing a country code. */ +typedef int16_t country_t; + +#define COUNTRY_MAX INT16_MAX + +#endif diff --git a/src/lib/geoip/geoip.c b/src/lib/geoip/geoip.c new file mode 100644 index 0000000000..70b1c2dc8c --- /dev/null +++ b/src/lib/geoip/geoip.c @@ -0,0 +1,510 @@ +/* Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file geoip.c + * \brief Functions related to maintaining an IP-to-country database; + * to summarizing client connections by country to entry guards, bridges, + * and directory servers; and for statistics on answering network status + * requests. + * + * There are two main kinds of functions in this module: geoip functions, + * which map groups of IPv4 and IPv6 addresses to country codes, and + * statistical functions, which collect statistics about different kinds of + * per-country usage. + * + * The geoip lookup tables are implemented as sorted lists of disjoint address + * ranges, each mapping to a singleton geoip_country_t. These country objects + * are also indexed by their names in a hashtable. + * + * The tables are populated from disk at startup by the geoip_load_file() + * function. For more information on the file format they read, see that + * function. See the scripts and the README file in src/config for more + * information about how those files are generated. + * + * Tor uses GeoIP information in order to implement user requests (such as + * ExcludeNodes {cc}), and to keep track of how much usage relays are getting + * for each country. + */ + +#define GEOIP_PRIVATE +#include "lib/geoip/geoip.h" +#include "lib/container/map.h" +#include "lib/container/order.h" +#include "lib/container/smartlist.h" +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/ctime/di_ops.h" +#include "lib/encoding/binascii.h" +#include "lib/fs/files.h" +#include "lib/log/escape.h" +#include "lib/malloc/malloc.h" +#include "lib/net/address.h" //???? +#include "lib/net/inaddr.h" +#include "lib/string/compat_ctype.h" +#include "lib/string/compat_string.h" +#include "lib/string/scanf.h" +#include "lib/string/util_string.h" + +#include <stdio.h> +#include <string.h> + +static void init_geoip_countries(void); + +/** An entry from the GeoIP IPv4 file: maps an IPv4 range to a country. */ +typedef struct geoip_ipv4_entry_t { + uint32_t ip_low; /**< The lowest IP in the range, in host order */ + uint32_t ip_high; /**< The highest IP in the range, in host order */ + intptr_t country; /**< An index into geoip_countries */ +} geoip_ipv4_entry_t; + +/** An entry from the GeoIP IPv6 file: maps an IPv6 range to a country. */ +typedef struct geoip_ipv6_entry_t { + struct in6_addr ip_low; /**< The lowest IP in the range, in host order */ + struct in6_addr ip_high; /**< The highest IP in the range, in host order */ + intptr_t country; /**< An index into geoip_countries */ +} geoip_ipv6_entry_t; + +/** A list of geoip_country_t */ +static smartlist_t *geoip_countries = NULL; +/** A map from lowercased country codes to their position in geoip_countries. + * The index is encoded in the pointer, and 1 is added so that NULL can mean + * not found. */ +static strmap_t *country_idxplus1_by_lc_code = NULL; +/** Lists of all known geoip_ipv4_entry_t and geoip_ipv6_entry_t, sorted + * by their respective ip_low. */ +static smartlist_t *geoip_ipv4_entries = NULL, *geoip_ipv6_entries = NULL; + +/** SHA1 digest of the GeoIP files to include in extra-info descriptors. */ +static char geoip_digest[DIGEST_LEN]; +static char geoip6_digest[DIGEST_LEN]; + +/** Return a list of geoip_country_t for all known countries. */ +const smartlist_t * +geoip_get_countries(void) +{ + if (geoip_countries == NULL) { + init_geoip_countries(); + } + return geoip_countries; +} + +/** Return the index of the <b>country</b>'s entry in the GeoIP + * country list if it is a valid 2-letter country code, otherwise + * return -1. */ +MOCK_IMPL(country_t, +geoip_get_country,(const char *country)) +{ + void *idxplus1_; + intptr_t idx; + + idxplus1_ = strmap_get_lc(country_idxplus1_by_lc_code, country); + if (!idxplus1_) + return -1; + + idx = ((uintptr_t)idxplus1_)-1; + return (country_t)idx; +} + +/** Add an entry to a GeoIP table, mapping all IP addresses between <b>low</b> + * and <b>high</b>, inclusive, to the 2-letter country code <b>country</b>. */ +static void +geoip_add_entry(const tor_addr_t *low, const tor_addr_t *high, + const char *country) +{ + intptr_t idx; + void *idxplus1_; + + IF_BUG_ONCE(tor_addr_family(low) != tor_addr_family(high)) + return; + IF_BUG_ONCE(tor_addr_compare(high, low, CMP_EXACT) < 0) + return; + + idxplus1_ = strmap_get_lc(country_idxplus1_by_lc_code, country); + + if (!idxplus1_) { + geoip_country_t *c = tor_malloc_zero(sizeof(geoip_country_t)); + strlcpy(c->countrycode, country, sizeof(c->countrycode)); + tor_strlower(c->countrycode); + smartlist_add(geoip_countries, c); + idx = smartlist_len(geoip_countries) - 1; + strmap_set_lc(country_idxplus1_by_lc_code, country, (void*)(idx+1)); + } else { + idx = ((uintptr_t)idxplus1_)-1; + } + { + geoip_country_t *c = smartlist_get(geoip_countries, (int)idx); + tor_assert(!strcasecmp(c->countrycode, country)); + } + + if (tor_addr_family(low) == AF_INET) { + geoip_ipv4_entry_t *ent = tor_malloc_zero(sizeof(geoip_ipv4_entry_t)); + ent->ip_low = tor_addr_to_ipv4h(low); + ent->ip_high = tor_addr_to_ipv4h(high); + ent->country = idx; + smartlist_add(geoip_ipv4_entries, ent); + } else if (tor_addr_family(low) == AF_INET6) { + geoip_ipv6_entry_t *ent = tor_malloc_zero(sizeof(geoip_ipv6_entry_t)); + ent->ip_low = *tor_addr_to_in6_assert(low); + ent->ip_high = *tor_addr_to_in6_assert(high); + ent->country = idx; + smartlist_add(geoip_ipv6_entries, ent); + } +} + +/** Add an entry to the GeoIP table indicated by <b>family</b>, + * parsing it from <b>line</b>. The format is as for geoip_load_file(). */ +STATIC int +geoip_parse_entry(const char *line, sa_family_t family) +{ + tor_addr_t low_addr, high_addr; + char c[3]; + char *country = NULL; + + if (!geoip_countries) + init_geoip_countries(); + if (family == AF_INET) { + if (!geoip_ipv4_entries) + geoip_ipv4_entries = smartlist_new(); + } else if (family == AF_INET6) { + if (!geoip_ipv6_entries) + geoip_ipv6_entries = smartlist_new(); + } else { + log_warn(LD_GENERAL, "Unsupported family: %d", family); + return -1; + } + + while (TOR_ISSPACE(*line)) + ++line; + if (*line == '#') + return 0; + + char buf[512]; + if (family == AF_INET) { + unsigned int low, high; + if (tor_sscanf(line,"%u,%u,%2s", &low, &high, c) == 3 || + tor_sscanf(line,"\"%u\",\"%u\",\"%2s\",", &low, &high, c) == 3) { + tor_addr_from_ipv4h(&low_addr, low); + tor_addr_from_ipv4h(&high_addr, high); + } else + goto fail; + country = c; + } else { /* AF_INET6 */ + char *low_str, *high_str; + struct in6_addr low, high; + char *strtok_state; + strlcpy(buf, line, sizeof(buf)); + low_str = tor_strtok_r(buf, ",", &strtok_state); + if (!low_str) + goto fail; + high_str = tor_strtok_r(NULL, ",", &strtok_state); + if (!high_str) + goto fail; + country = tor_strtok_r(NULL, "\n", &strtok_state); + if (!country) + goto fail; + if (strlen(country) != 2) + goto fail; + if (tor_inet_pton(AF_INET6, low_str, &low) <= 0) + goto fail; + tor_addr_from_in6(&low_addr, &low); + if (tor_inet_pton(AF_INET6, high_str, &high) <= 0) + goto fail; + tor_addr_from_in6(&high_addr, &high); + } + geoip_add_entry(&low_addr, &high_addr, country); + return 0; + + fail: + log_warn(LD_GENERAL, "Unable to parse line from GEOIP %s file: %s", + family == AF_INET ? "IPv4" : "IPv6", escaped(line)); + return -1; +} + +/** Sorting helper: return -1, 1, or 0 based on comparison of two + * geoip_ipv4_entry_t */ +static int +geoip_ipv4_compare_entries_(const void **_a, const void **_b) +{ + const geoip_ipv4_entry_t *a = *_a, *b = *_b; + if (a->ip_low < b->ip_low) + return -1; + else if (a->ip_low > b->ip_low) + return 1; + else + return 0; +} + +/** bsearch helper: return -1, 1, or 0 based on comparison of an IP (a pointer + * to a uint32_t in host order) to a geoip_ipv4_entry_t */ +static int +geoip_ipv4_compare_key_to_entry_(const void *_key, const void **_member) +{ + /* No alignment issue here, since _key really is a pointer to uint32_t */ + const uint32_t addr = *(uint32_t *)_key; + const geoip_ipv4_entry_t *entry = *_member; + if (addr < entry->ip_low) + return -1; + else if (addr > entry->ip_high) + return 1; + else + return 0; +} + +/** Sorting helper: return -1, 1, or 0 based on comparison of two + * geoip_ipv6_entry_t */ +static int +geoip_ipv6_compare_entries_(const void **_a, const void **_b) +{ + const geoip_ipv6_entry_t *a = *_a, *b = *_b; + return fast_memcmp(a->ip_low.s6_addr, b->ip_low.s6_addr, + sizeof(struct in6_addr)); +} + +/** bsearch helper: return -1, 1, or 0 based on comparison of an IPv6 + * (a pointer to a in6_addr) to a geoip_ipv6_entry_t */ +static int +geoip_ipv6_compare_key_to_entry_(const void *_key, const void **_member) +{ + const struct in6_addr *addr = (struct in6_addr *)_key; + const geoip_ipv6_entry_t *entry = *_member; + + if (fast_memcmp(addr->s6_addr, entry->ip_low.s6_addr, + sizeof(struct in6_addr)) < 0) + return -1; + else if (fast_memcmp(addr->s6_addr, entry->ip_high.s6_addr, + sizeof(struct in6_addr)) > 0) + return 1; + else + return 0; +} + +/** Set up a new list of geoip countries with no countries (yet) set in it, + * except for the unknown country. + */ +static void +init_geoip_countries(void) +{ + geoip_country_t *geoip_unresolved; + geoip_countries = smartlist_new(); + /* Add a geoip_country_t for requests that could not be resolved to a + * country as first element (index 0) to geoip_countries. */ + geoip_unresolved = tor_malloc_zero(sizeof(geoip_country_t)); + strlcpy(geoip_unresolved->countrycode, "??", + sizeof(geoip_unresolved->countrycode)); + smartlist_add(geoip_countries, geoip_unresolved); + country_idxplus1_by_lc_code = strmap_new(); + strmap_set_lc(country_idxplus1_by_lc_code, "??", (void*)(1)); +} + +/** Clear appropriate GeoIP database, based on <b>family</b>, and + * reload it from the file <b>filename</b>. Return 0 on success, -1 on + * failure. + * + * Recognized line formats for IPv4 are: + * INTIPLOW,INTIPHIGH,CC + * and + * "INTIPLOW","INTIPHIGH","CC","CC3","COUNTRY NAME" + * where INTIPLOW and INTIPHIGH are IPv4 addresses encoded as 4-byte unsigned + * integers, and CC is a country code. + * + * Recognized line format for IPv6 is: + * IPV6LOW,IPV6HIGH,CC + * where IPV6LOW and IPV6HIGH are IPv6 addresses and CC is a country code. + * + * It also recognizes, and skips over, blank lines and lines that start + * with '#' (comments). + */ +int +geoip_load_file(sa_family_t family, const char *filename, int severity) +{ + FILE *f; + crypto_digest_t *geoip_digest_env = NULL; + + tor_assert(family == AF_INET || family == AF_INET6); + + if (!(f = tor_fopen_cloexec(filename, "r"))) { + log_fn(severity, LD_GENERAL, "Failed to open GEOIP file %s.", + filename); + return -1; + } + if (!geoip_countries) + init_geoip_countries(); + + if (family == AF_INET) { + if (geoip_ipv4_entries) { + SMARTLIST_FOREACH(geoip_ipv4_entries, geoip_ipv4_entry_t *, e, + tor_free(e)); + smartlist_free(geoip_ipv4_entries); + } + geoip_ipv4_entries = smartlist_new(); + } else { /* AF_INET6 */ + if (geoip_ipv6_entries) { + SMARTLIST_FOREACH(geoip_ipv6_entries, geoip_ipv6_entry_t *, e, + tor_free(e)); + smartlist_free(geoip_ipv6_entries); + } + geoip_ipv6_entries = smartlist_new(); + } + geoip_digest_env = crypto_digest_new(); + + log_notice(LD_GENERAL, "Parsing GEOIP %s file %s.", + (family == AF_INET) ? "IPv4" : "IPv6", filename); + while (!feof(f)) { + char buf[512]; + if (fgets(buf, (int)sizeof(buf), f) == NULL) + break; + crypto_digest_add_bytes(geoip_digest_env, buf, strlen(buf)); + /* FFFF track full country name. */ + geoip_parse_entry(buf, family); + } + /*XXXX abort and return -1 if no entries/illformed?*/ + fclose(f); + + /* Sort list and remember file digests so that we can include it in + * our extra-info descriptors. */ + if (family == AF_INET) { + smartlist_sort(geoip_ipv4_entries, geoip_ipv4_compare_entries_); + crypto_digest_get_digest(geoip_digest_env, geoip_digest, DIGEST_LEN); + } else { + /* AF_INET6 */ + smartlist_sort(geoip_ipv6_entries, geoip_ipv6_compare_entries_); + crypto_digest_get_digest(geoip_digest_env, geoip6_digest, DIGEST_LEN); + } + crypto_digest_free(geoip_digest_env); + + return 0; +} + +/** Given an IP address in host order, return a number representing the + * country to which that address belongs, -1 for "No geoip information + * available", or 0 for the 'unknown country'. The return value will always + * be less than geoip_get_n_countries(). To decode it, call + * geoip_get_country_name(). + */ +int +geoip_get_country_by_ipv4(uint32_t ipaddr) +{ + geoip_ipv4_entry_t *ent; + if (!geoip_ipv4_entries) + return -1; + ent = smartlist_bsearch(geoip_ipv4_entries, &ipaddr, + geoip_ipv4_compare_key_to_entry_); + return ent ? (int)ent->country : 0; +} + +/** Given an IPv6 address, return a number representing the country to + * which that address belongs, -1 for "No geoip information available", or + * 0 for the 'unknown country'. The return value will always be less than + * geoip_get_n_countries(). To decode it, call geoip_get_country_name(). + */ +int +geoip_get_country_by_ipv6(const struct in6_addr *addr) +{ + geoip_ipv6_entry_t *ent; + + if (!geoip_ipv6_entries) + return -1; + ent = smartlist_bsearch(geoip_ipv6_entries, addr, + geoip_ipv6_compare_key_to_entry_); + return ent ? (int)ent->country : 0; +} + +/** Given an IP address, return a number representing the country to which + * that address belongs, -1 for "No geoip information available", or 0 for + * the 'unknown country'. The return value will always be less than + * geoip_get_n_countries(). To decode it, call geoip_get_country_name(). + */ +MOCK_IMPL(int, +geoip_get_country_by_addr,(const tor_addr_t *addr)) +{ + if (tor_addr_family(addr) == AF_INET) { + return geoip_get_country_by_ipv4(tor_addr_to_ipv4h(addr)); + } else if (tor_addr_family(addr) == AF_INET6) { + return geoip_get_country_by_ipv6(tor_addr_to_in6(addr)); + } else { + return -1; + } +} + +/** Return the number of countries recognized by the GeoIP country list. */ +MOCK_IMPL(int, +geoip_get_n_countries,(void)) +{ + if (!geoip_countries) + init_geoip_countries(); + return (int) smartlist_len(geoip_countries); +} + +/** Return the two-letter country code associated with the number <b>num</b>, + * or "??" for an unknown value. */ +const char * +geoip_get_country_name(country_t num) +{ + if (geoip_countries && num >= 0 && num < smartlist_len(geoip_countries)) { + geoip_country_t *c = smartlist_get(geoip_countries, num); + return c->countrycode; + } else + return "??"; +} + +/** Return true iff we have loaded a GeoIP database.*/ +MOCK_IMPL(int, +geoip_is_loaded,(sa_family_t family)) +{ + tor_assert(family == AF_INET || family == AF_INET6); + if (geoip_countries == NULL) + return 0; + if (family == AF_INET) + return geoip_ipv4_entries != NULL; + else /* AF_INET6 */ + return geoip_ipv6_entries != NULL; +} + +/** Return the hex-encoded SHA1 digest of the loaded GeoIP file. The + * result does not need to be deallocated, but will be overwritten by the + * next call of hex_str(). */ +const char * +geoip_db_digest(sa_family_t family) +{ + tor_assert(family == AF_INET || family == AF_INET6); + if (family == AF_INET) + return hex_str(geoip_digest, DIGEST_LEN); + else /* AF_INET6 */ + return hex_str(geoip6_digest, DIGEST_LEN); +} + +/** Release all storage held by the GeoIP databases and country list. */ +STATIC void +clear_geoip_db(void) +{ + if (geoip_countries) { + SMARTLIST_FOREACH(geoip_countries, geoip_country_t *, c, tor_free(c)); + smartlist_free(geoip_countries); + } + + strmap_free(country_idxplus1_by_lc_code, NULL); + if (geoip_ipv4_entries) { + SMARTLIST_FOREACH(geoip_ipv4_entries, geoip_ipv4_entry_t *, ent, + tor_free(ent)); + smartlist_free(geoip_ipv4_entries); + } + if (geoip_ipv6_entries) { + SMARTLIST_FOREACH(geoip_ipv6_entries, geoip_ipv6_entry_t *, ent, + tor_free(ent)); + smartlist_free(geoip_ipv6_entries); + } + geoip_countries = NULL; + country_idxplus1_by_lc_code = NULL; + geoip_ipv4_entries = NULL; + geoip_ipv6_entries = NULL; +} + +/** Release all storage held in this file. */ +void +geoip_free_all(void) +{ + clear_geoip_db(); + + memset(geoip_digest, 0, sizeof(geoip_digest)); + memset(geoip6_digest, 0, sizeof(geoip6_digest)); +} diff --git a/src/lib/geoip/geoip.h b/src/lib/geoip/geoip.h new file mode 100644 index 0000000000..f872ebd25f --- /dev/null +++ b/src/lib/geoip/geoip.h @@ -0,0 +1,50 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file geoip.h + * \brief Header file for geoip.c. + **/ + +#ifndef TOR_GEOIP_H +#define TOR_GEOIP_H + +#include "orconfig.h" +#include "lib/net/nettypes.h" +#include "lib/testsupport/testsupport.h" +#include "lib/net/inaddr_st.h" +#include "lib/geoip/country.h" + +#ifdef GEOIP_PRIVATE +STATIC int geoip_parse_entry(const char *line, sa_family_t family); +STATIC void clear_geoip_db(void); +#endif /* defined(GEOIP_PRIVATE) */ + +struct in6_addr; +struct tor_addr_t; + +int geoip_get_country_by_ipv4(uint32_t ipaddr); +int geoip_get_country_by_ipv6(const struct in6_addr *addr); + +/** A per-country GeoIP record. */ +typedef struct geoip_country_t { + char countrycode[3]; +} geoip_country_t; + +struct smartlist_t; +const struct smartlist_t *geoip_get_countries(void); + +int geoip_load_file(sa_family_t family, const char *filename, int severity); +MOCK_DECL(int, geoip_get_country_by_addr, (const struct tor_addr_t *addr)); +MOCK_DECL(int, geoip_get_n_countries, (void)); +const char *geoip_get_country_name(country_t num); +MOCK_DECL(int, geoip_is_loaded, (sa_family_t family)); +const char *geoip_db_digest(sa_family_t family); +MOCK_DECL(country_t, geoip_get_country, (const char *countrycode)); + +void geoip_free_all(void); + +#endif /* !defined(TOR_GEOIP_H) */ diff --git a/src/lib/geoip/include.am b/src/lib/geoip/include.am new file mode 100644 index 0000000000..9710d75ac7 --- /dev/null +++ b/src/lib/geoip/include.am @@ -0,0 +1,17 @@ +noinst_LIBRARIES += src/lib/libtor-geoip.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-geoip-testing.a +endif + +src_lib_libtor_geoip_a_SOURCES = \ + src/lib/geoip/geoip.c + +src_lib_libtor_geoip_testing_a_SOURCES = \ + $(src_lib_libtor_geoip_a_SOURCES) +src_lib_libtor_geoip_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_geoip_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/geoip/geoip.h \ + src/lib/geoip/country.h diff --git a/src/lib/include.libdonna.am b/src/lib/include.libdonna.am new file mode 100644 index 0000000000..60a3f5097e --- /dev/null +++ b/src/lib/include.libdonna.am @@ -0,0 +1,24 @@ +src_lib_libcurve25519_donna_a_CFLAGS=$(TOR_CFLAGS_CRYPTO) + +if BUILD_CURVE25519_DONNA +src_lib_libcurve25519_donna_a_SOURCES=\ + src/ext/curve25519_donna/curve25519-donna.c +# See bug 13538 -- this code is known to have signed overflow issues. +src_lib_libcurve25519_donna_a_CFLAGS+=\ + @F_OMIT_FRAME_POINTER@ @CFLAGS_CONSTTIME@ +noinst_LIBRARIES+=src/lib/libcurve25519_donna.a +LIBDONNA=src/lib/libcurve25519_donna.a +else +if BUILD_CURVE25519_DONNA_C64 +src_lib_libcurve25519_donna_a_CFLAGS+=@CFLAGS_CONSTTIME@ +src_lib_libcurve25519_donna_a_SOURCES=\ + src/ext/curve25519_donna/curve25519-donna-c64.c +noinst_LIBRARIES+=src/lib/libcurve25519_donna.a +LIBDONNA=src/lib/libcurve25519_donna.a +else +LIBDONNA= +endif +endif + +LIBDONNA += $(LIBED25519_REF10) +LIBDONNA += $(LIBED25519_DONNA) diff --git a/src/lib/intmath/.may_include b/src/lib/intmath/.may_include new file mode 100644 index 0000000000..d96c112220 --- /dev/null +++ b/src/lib/intmath/.may_include @@ -0,0 +1,4 @@ +orconfig.h +lib/cc/*.h +lib/err/*.h +lib/intmath/*.h diff --git a/src/lib/intmath/addsub.c b/src/lib/intmath/addsub.c new file mode 100644 index 0000000000..12146f4e72 --- /dev/null +++ b/src/lib/intmath/addsub.c @@ -0,0 +1,28 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file addsub.c + * + * \brief Helpers for addition and subtraction. + * + * Currently limited to non-wrapping (saturating) addition. + **/ + +#include "lib/intmath/addsub.h" +#include "lib/cc/compat_compiler.h" + +/* Helper: safely add two uint32_t's, capping at UINT32_MAX rather + * than overflow */ +uint32_t +tor_add_u32_nowrap(uint32_t a, uint32_t b) +{ + /* a+b > UINT32_MAX check, without overflow */ + if (PREDICT_UNLIKELY(a > UINT32_MAX - b)) { + return UINT32_MAX; + } else { + return a+b; + } +} diff --git a/src/lib/intmath/addsub.h b/src/lib/intmath/addsub.h new file mode 100644 index 0000000000..83efa82919 --- /dev/null +++ b/src/lib/intmath/addsub.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file addsub.h + * + * \brief Header for addsub.c + **/ + +#ifndef TOR_INTMATH_ADDSUB_H +#define TOR_INTMATH_ADDSUB_H + +#include "lib/cc/torint.h" + +uint32_t tor_add_u32_nowrap(uint32_t a, uint32_t b); + +#endif /* !defined(TOR_INTMATH_MULDIV_H) */ diff --git a/src/lib/intmath/bits.c b/src/lib/intmath/bits.c new file mode 100644 index 0000000000..2158790e3f --- /dev/null +++ b/src/lib/intmath/bits.c @@ -0,0 +1,94 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file bits.c + * + * \brief Count the bits in an integer, manipulate powers of 2, etc. + **/ + +#include "lib/intmath/bits.h" + +/** Returns floor(log2(u64)). If u64 is 0, (incorrectly) returns 0. */ +int +tor_log2(uint64_t u64) +{ + int r = 0; + if (u64 >= (UINT64_C(1)<<32)) { + u64 >>= 32; + r = 32; + } + if (u64 >= (UINT64_C(1)<<16)) { + u64 >>= 16; + r += 16; + } + if (u64 >= (UINT64_C(1)<<8)) { + u64 >>= 8; + r += 8; + } + if (u64 >= (UINT64_C(1)<<4)) { + u64 >>= 4; + r += 4; + } + if (u64 >= (UINT64_C(1)<<2)) { + u64 >>= 2; + r += 2; + } + if (u64 >= (UINT64_C(1)<<1)) { + // u64 >>= 1; // not using this any more. + r += 1; + } + return r; +} + +/** Return the power of 2 in range [1,UINT64_MAX] closest to <b>u64</b>. If + * there are two powers of 2 equally close, round down. */ +uint64_t +round_to_power_of_2(uint64_t u64) +{ + int lg2; + uint64_t low; + uint64_t high; + if (u64 == 0) + return 1; + + lg2 = tor_log2(u64); + low = UINT64_C(1) << lg2; + + if (lg2 == 63) + return low; + + high = UINT64_C(1) << (lg2+1); + if (high - u64 < u64 - low) + return high; + else + return low; +} + +/** Return the number of bits set in <b>v</b>. */ +int +n_bits_set_u8(uint8_t v) +{ + static const int nybble_table[] = { + 0, /* 0000 */ + 1, /* 0001 */ + 1, /* 0010 */ + 2, /* 0011 */ + 1, /* 0100 */ + 2, /* 0101 */ + 2, /* 0110 */ + 3, /* 0111 */ + 1, /* 1000 */ + 2, /* 1001 */ + 2, /* 1010 */ + 3, /* 1011 */ + 2, /* 1100 */ + 3, /* 1101 */ + 3, /* 1110 */ + 4, /* 1111 */ + }; + + return nybble_table[v & 15] + nybble_table[v>>4]; +} diff --git a/src/lib/intmath/bits.h b/src/lib/intmath/bits.h new file mode 100644 index 0000000000..c1483a18b8 --- /dev/null +++ b/src/lib/intmath/bits.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file bits.h + * + * \brief Header for bits.c + **/ + +#ifndef TOR_INTMATH_BITS_H +#define TOR_INTMATH_BITS_H + +#include "lib/cc/torint.h" +#include "lib/cc/compat_compiler.h" + +int tor_log2(uint64_t u64) ATTR_CONST; +uint64_t round_to_power_of_2(uint64_t u64); +int n_bits_set_u8(uint8_t v); + +#endif /* !defined(TOR_INTMATH_BITS_H) */ diff --git a/src/lib/intmath/cmp.h b/src/lib/intmath/cmp.h new file mode 100644 index 0000000000..d0b0e8b954 --- /dev/null +++ b/src/lib/intmath/cmp.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file cmp.h + * + * \brief Macro definitions for MIN, MAX, and CLAMP. + **/ + +#ifndef TOR_INTMATH_CMP_H +#define TOR_INTMATH_CMP_H + +/** Macros for MIN/MAX. Never use these when the arguments could have + * side-effects. + * {With GCC extensions we could probably define a safer MIN/MAX. But + * depending on that safety would be dangerous, since not every platform + * has it.} + **/ +#ifndef MAX +#define MAX(a,b) ( ((a)<(b)) ? (b) : (a) ) +#endif +#ifndef MIN +#define MIN(a,b) ( ((a)>(b)) ? (b) : (a) ) +#endif + +/* Return <b>v</b> if it's between <b>min</b> and <b>max</b>. Otherwise + * return <b>min</b> if <b>v</b> is smaller than <b>min</b>, or <b>max</b> if + * <b>b</b> is larger than <b>max</b>. + * + * Requires that <b>min</b> is no more than <b>max</b>. May evaluate any of + * its arguments more than once! */ +#define CLAMP(min,v,max) \ + ( ((v) < (min)) ? (min) : \ + ((v) > (max)) ? (max) : \ + (v) ) + +#endif /* !defined(TOR_INTMATH_CMP_H) */ diff --git a/src/lib/intmath/include.am b/src/lib/intmath/include.am new file mode 100644 index 0000000000..45ee3bd53b --- /dev/null +++ b/src/lib/intmath/include.am @@ -0,0 +1,25 @@ + +noinst_LIBRARIES += src/lib/libtor-intmath.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-intmath-testing.a +endif + +src_lib_libtor_intmath_a_SOURCES = \ + src/lib/intmath/addsub.c \ + src/lib/intmath/bits.c \ + src/lib/intmath/muldiv.c \ + src/lib/intmath/weakrng.c + +src_lib_libtor_intmath_testing_a_SOURCES = \ + $(src_lib_libtor_intmath_a_SOURCES) +src_lib_libtor_intmath_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_intmath_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/intmath/addsub.h \ + src/lib/intmath/cmp.h \ + src/lib/intmath/bits.h \ + src/lib/intmath/logic.h \ + src/lib/intmath/muldiv.h \ + src/lib/intmath/weakrng.h diff --git a/src/lib/intmath/logic.h b/src/lib/intmath/logic.h new file mode 100644 index 0000000000..a4cecd69cc --- /dev/null +++ b/src/lib/intmath/logic.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file logic.h + * + * \brief Macros for comparing the boolean value of integers. + **/ + +#ifndef HAVE_TOR_LOGIC_H +#define HAVE_TOR_LOGIC_H + +/** Macro: true if two values have the same boolean value. */ +#define bool_eq(a,b) (!(a)==!(b)) +/** Macro: true if two values have different boolean values. */ +#define bool_neq(a,b) (!(a)!=!(b)) + +#endif diff --git a/src/lib/intmath/muldiv.c b/src/lib/intmath/muldiv.c new file mode 100644 index 0000000000..6a292db7ba --- /dev/null +++ b/src/lib/intmath/muldiv.c @@ -0,0 +1,81 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file muldiv.c + * + * \brief Integer math related to multiplication, division, and rounding. + **/ + +#include "lib/intmath/muldiv.h" +#include "lib/err/torerr.h" + +#include <stdlib.h> + +/** Return the lowest x such that x is at least <b>number</b>, and x modulo + * <b>divisor</b> == 0. If no such x can be expressed as an unsigned, return + * UINT_MAX. Asserts if divisor is zero. */ +unsigned +round_to_next_multiple_of(unsigned number, unsigned divisor) +{ + raw_assert(divisor > 0); + if (UINT_MAX - divisor + 1 < number) + return UINT_MAX; + number += divisor - 1; + number -= number % divisor; + return number; +} + +/** Return the lowest x such that x is at least <b>number</b>, and x modulo + * <b>divisor</b> == 0. If no such x can be expressed as a uint32_t, return + * UINT32_MAX. Asserts if divisor is zero. */ +uint32_t +round_uint32_to_next_multiple_of(uint32_t number, uint32_t divisor) +{ + raw_assert(divisor > 0); + if (UINT32_MAX - divisor + 1 < number) + return UINT32_MAX; + + number += divisor - 1; + number -= number % divisor; + return number; +} + +/** Return the lowest x such that x is at least <b>number</b>, and x modulo + * <b>divisor</b> == 0. If no such x can be expressed as a uint64_t, return + * UINT64_MAX. Asserts if divisor is zero. */ +uint64_t +round_uint64_to_next_multiple_of(uint64_t number, uint64_t divisor) +{ + raw_assert(divisor > 0); + if (UINT64_MAX - divisor + 1 < number) + return UINT64_MAX; + number += divisor - 1; + number -= number % divisor; + return number; +} + +/* Helper: return greatest common divisor of a,b */ +static uint64_t +gcd64(uint64_t a, uint64_t b) +{ + while (b) { + uint64_t t = b; + b = a % b; + a = t; + } + return a; +} + +/* Given a fraction *<b>numer</b> / *<b>denom</b>, simplify it. + * Requires that the denominator is greater than 0. */ +void +simplify_fraction64(uint64_t *numer, uint64_t *denom) +{ + raw_assert(denom); + uint64_t gcd = gcd64(*numer, *denom); + *numer /= gcd; + *denom /= gcd; +} diff --git a/src/lib/intmath/muldiv.h b/src/lib/intmath/muldiv.h new file mode 100644 index 0000000000..64500b6dce --- /dev/null +++ b/src/lib/intmath/muldiv.h @@ -0,0 +1,28 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file muldiv.h + * + * \brief Header for muldiv.c + **/ + +#ifndef TOR_INTMATH_MULDIV_H +#define TOR_INTMATH_MULDIV_H + +#include "lib/cc/torint.h" + +unsigned round_to_next_multiple_of(unsigned number, unsigned divisor); +uint32_t round_uint32_to_next_multiple_of(uint32_t number, uint32_t divisor); +uint64_t round_uint64_to_next_multiple_of(uint64_t number, uint64_t divisor); + +void simplify_fraction64(uint64_t *numer, uint64_t *denom); + +/* Compute the CEIL of <b>a</b> divided by <b>b</b>, for nonnegative <b>a</b> + * and positive <b>b</b>. Works on integer types only. Not defined if a+(b-1) + * can overflow. */ +#define CEIL_DIV(a,b) (((a)+((b)-1))/(b)) + +#endif /* !defined(TOR_INTMATH_MULDIV_H) */ diff --git a/src/lib/intmath/weakrng.c b/src/lib/intmath/weakrng.c new file mode 100644 index 0000000000..99c9252c2b --- /dev/null +++ b/src/lib/intmath/weakrng.c @@ -0,0 +1,60 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file weakrng.c + * + * \brief A weak but fast PRNG based on a linear congruential generator. + * + * We don't want to use the platform random(), since some of them are even + * worse than this. + **/ + +#include "lib/intmath/weakrng.h" +#include "lib/err/torerr.h" + +#include <stdlib.h> + +/** Initialize the insecure RNG <b>rng</b> from a seed value <b>seed</b>. */ +void +tor_init_weak_random(tor_weak_rng_t *rng, unsigned seed) +{ + rng->state = (uint32_t)(seed & 0x7fffffff); +} + +/** Return a randomly chosen value in the range 0..TOR_WEAK_RANDOM_MAX based + * on the RNG state of <b>rng</b>. This entropy will not be cryptographically + * strong; do not rely on it for anything an adversary should not be able to + * predict. */ +int32_t +tor_weak_random(tor_weak_rng_t *rng) +{ + /* Here's a linear congruential generator. OpenBSD and glibc use these + * parameters; they aren't too bad, and should have maximal period over the + * range 0..INT32_MAX. We don't want to use the platform rand() or random(), + * since some platforms have bad weak RNGs that only return values in the + * range 0..INT16_MAX, which just isn't enough. */ + rng->state = (rng->state * 1103515245 + 12345) & 0x7fffffff; + return (int32_t) rng->state; +} + +/** Return a random number in the range [0 , <b>top</b>). {That is, the range + * of integers i such that 0 <= i < top.} Chooses uniformly. Requires that + * top is greater than 0. This randomness is not cryptographically strong; do + * not rely on it for anything an adversary should not be able to predict. */ +int32_t +tor_weak_random_range(tor_weak_rng_t *rng, int32_t top) +{ + /* We don't want to just do tor_weak_random() % top, since random() is often + * implemented with an LCG whose modulus is a power of 2, and those are + * cyclic in their low-order bits. */ + int divisor, result; + raw_assert(top > 0); + divisor = TOR_WEAK_RANDOM_MAX / top; + do { + result = (int32_t)(tor_weak_random(rng) / divisor); + } while (result >= top); + return result; +} diff --git a/src/lib/intmath/weakrng.h b/src/lib/intmath/weakrng.h new file mode 100644 index 0000000000..e26bf58cbb --- /dev/null +++ b/src/lib/intmath/weakrng.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file weakrng.h + * + * \brief Header for weakrng.c + **/ + +#ifndef TOR_WEAKRNG_H +#define TOR_WEAKRNG_H + +#include "lib/cc/torint.h" + +/* ===== Insecure rng */ +typedef struct tor_weak_rng_t { + uint32_t state; +} tor_weak_rng_t; + +#define TOR_WEAK_RNG_INIT {383745623} +#define TOR_WEAK_RANDOM_MAX (INT_MAX) +void tor_init_weak_random(tor_weak_rng_t *weak_rng, unsigned seed); +int32_t tor_weak_random(tor_weak_rng_t *weak_rng); +int32_t tor_weak_random_range(tor_weak_rng_t *rng, int32_t top); +/** Randomly return true according to <b>rng</b> with probability 1 in + * <b>n</b> */ +#define tor_weak_random_one_in_n(rng, n) (0==tor_weak_random_range((rng),(n))) + +#endif diff --git a/src/lib/lock/.may_include b/src/lib/lock/.may_include new file mode 100644 index 0000000000..9522e3af49 --- /dev/null +++ b/src/lib/lock/.may_include @@ -0,0 +1,5 @@ +orconfig.h +lib/cc/*.h +lib/err/*.h +lib/lock/*.h +lib/malloc/*.h diff --git a/src/lib/lock/compat_mutex.c b/src/lib/lock/compat_mutex.c new file mode 100644 index 0000000000..4ad5929715 --- /dev/null +++ b/src/lib/lock/compat_mutex.c @@ -0,0 +1,40 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_mutex.c + * + * \brief Portable wrapper for platform mutex implementations. + **/ + +#include "lib/lock/compat_mutex.h" +#include "lib/malloc/malloc.h" + +/** Return a newly allocated, ready-for-use mutex. */ +tor_mutex_t * +tor_mutex_new(void) +{ + tor_mutex_t *m = tor_malloc_zero(sizeof(tor_mutex_t)); + tor_mutex_init(m); + return m; +} +/** Return a newly allocated, ready-for-use mutex. This one might be + * non-recursive, if that's faster. */ +tor_mutex_t * +tor_mutex_new_nonrecursive(void) +{ + tor_mutex_t *m = tor_malloc_zero(sizeof(tor_mutex_t)); + tor_mutex_init_nonrecursive(m); + return m; +} +/** Release all storage and system resources held by <b>m</b>. */ +void +tor_mutex_free_(tor_mutex_t *m) +{ + if (!m) + return; + tor_mutex_uninit(m); + tor_free(m); +} diff --git a/src/lib/lock/compat_mutex.h b/src/lib/lock/compat_mutex.h new file mode 100644 index 0000000000..b63ce24024 --- /dev/null +++ b/src/lib/lock/compat_mutex.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_mutex.h + * + * \brief Header for compat_mutex.c + **/ + +#ifndef TOR_COMPAT_MUTEX_H +#define TOR_COMPAT_MUTEX_H + +#include "orconfig.h" +#include "lib/cc/torint.h" +#include "lib/malloc/malloc.h" + +#if defined(HAVE_PTHREAD_H) && !defined(_WIN32) +#include <pthread.h> +#endif + +#if defined(_WIN32) +#include <windows.h> +#endif + +#if defined(_WIN32) +#define USE_WIN32_THREADS +#elif defined(HAVE_PTHREAD_H) && defined(HAVE_PTHREAD_CREATE) +#define USE_PTHREADS +#else +#error "No threading system was found" +#endif /* defined(_WIN32) || ... */ + +/* Because we use threads instead of processes on most platforms (Windows, + * Linux, etc), we need locking for them. On platforms with poor thread + * support or broken gethostbyname_r, these functions are no-ops. */ + +/** A generic lock structure for multithreaded builds. */ +typedef struct tor_mutex_t { +#if defined(USE_WIN32_THREADS) + /** Windows-only: on windows, we implement locks with CRITICAL_SECTIONS. */ + CRITICAL_SECTION mutex; +#elif defined(USE_PTHREADS) + /** Pthreads-only: with pthreads, we implement locks with + * pthread_mutex_t. */ + pthread_mutex_t mutex; +#else + /** No-threads only: Dummy variable so that tor_mutex_t takes up space. */ + int _unused; +#endif /* defined(USE_WIN32_MUTEX) || ... */ +} tor_mutex_t; + +tor_mutex_t *tor_mutex_new(void); +tor_mutex_t *tor_mutex_new_nonrecursive(void); +void tor_mutex_init(tor_mutex_t *m); +void tor_mutex_init_nonrecursive(tor_mutex_t *m); +void tor_mutex_acquire(tor_mutex_t *m); +void tor_mutex_release(tor_mutex_t *m); +void tor_mutex_free_(tor_mutex_t *m); +#define tor_mutex_free(m) FREE_AND_NULL(tor_mutex_t, tor_mutex_free_, (m)) +void tor_mutex_uninit(tor_mutex_t *m); + +void tor_locking_init(void); + +#endif /* !defined(TOR_COMPAT_MUTEX_H) */ diff --git a/src/lib/lock/compat_mutex_pthreads.c b/src/lib/lock/compat_mutex_pthreads.c new file mode 100644 index 0000000000..ee5f520cd0 --- /dev/null +++ b/src/lib/lock/compat_mutex_pthreads.c @@ -0,0 +1,103 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_mutex_pthreads.c + * + * \brief Implement the tor_mutex API using pthread_mutex_t. + **/ + +#include "lib/lock/compat_mutex.h" +#include "lib/cc/compat_compiler.h" +#include "lib/err/torerr.h" + +/** A mutex attribute that we're going to use to tell pthreads that we want + * "recursive" mutexes (i.e., once we can re-lock if we're already holding + * them.) */ +static pthread_mutexattr_t attr_recursive; +static int attr_initialized = 0; + +void +tor_locking_init(void) +{ + if (!attr_initialized) { + pthread_mutexattr_init(&attr_recursive); + pthread_mutexattr_settype(&attr_recursive, PTHREAD_MUTEX_RECURSIVE); + attr_initialized = 1; + } +} + +/** Initialize <b>mutex</b> so it can be locked. Every mutex must be set + * up with tor_mutex_init() or tor_mutex_new(); not both. */ +void +tor_mutex_init(tor_mutex_t *mutex) +{ + if (PREDICT_UNLIKELY(!attr_initialized)) + tor_locking_init(); // LCOV_EXCL_LINE + const int err = pthread_mutex_init(&mutex->mutex, &attr_recursive); + if (PREDICT_UNLIKELY(err)) { + // LCOV_EXCL_START + raw_assert_unreached_msg("Error creating a mutex."); + // LCOV_EXCL_STOP + } +} + +/** As tor_mutex_init, but initialize a mutex suitable that may be + * non-recursive, if the OS supports that. */ +void +tor_mutex_init_nonrecursive(tor_mutex_t *mutex) +{ + int err; + if (!attr_initialized) + tor_locking_init(); // LCOV_EXCL_LINE + err = pthread_mutex_init(&mutex->mutex, NULL); + if (PREDICT_UNLIKELY(err)) { + // LCOV_EXCL_START + raw_assert_unreached_msg("Error creating a mutex."); + // LCOV_EXCL_STOP + } +} + +/** Wait until <b>m</b> is free, then acquire it. */ +void +tor_mutex_acquire(tor_mutex_t *m) +{ + int err; + raw_assert(m); + err = pthread_mutex_lock(&m->mutex); + if (PREDICT_UNLIKELY(err)) { + // LCOV_EXCL_START + raw_assert_unreached_msg("Error locking a mutex."); + // LCOV_EXCL_STOP + } +} +/** Release the lock <b>m</b> so another thread can have it. */ +void +tor_mutex_release(tor_mutex_t *m) +{ + int err; + raw_assert(m); + err = pthread_mutex_unlock(&m->mutex); + if (PREDICT_UNLIKELY(err)) { + // LCOV_EXCL_START + raw_assert_unreached_msg("Error unlocking a mutex."); + // LCOV_EXCL_STOP + } +} +/** Clean up the mutex <b>m</b> so that it no longer uses any system + * resources. Does not free <b>m</b>. This function must only be called on + * mutexes from tor_mutex_init(). */ +void +tor_mutex_uninit(tor_mutex_t *m) +{ + int err; + raw_assert(m); + err = pthread_mutex_destroy(&m->mutex); + if (PREDICT_UNLIKELY(err)) { + // LCOV_EXCL_START + raw_assert_unreached_msg("Error destroying a mutex."); + // LCOV_EXCL_STOP + } +} diff --git a/src/lib/lock/compat_mutex_winthreads.c b/src/lib/lock/compat_mutex_winthreads.c new file mode 100644 index 0000000000..b0f5999e42 --- /dev/null +++ b/src/lib/lock/compat_mutex_winthreads.c @@ -0,0 +1,46 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_mutex_winthreads.c + * + * \brief Implement the tor_mutex API using CRITICAL_SECTION. + **/ + +#include "lib/lock/compat_mutex.h" +#include "lib/err/torerr.h" + +void +tor_locking_init(void) +{ +} + +void +tor_mutex_init(tor_mutex_t *m) +{ + InitializeCriticalSection(&m->mutex); +} +void +tor_mutex_init_nonrecursive(tor_mutex_t *m) +{ + InitializeCriticalSection(&m->mutex); +} + +void +tor_mutex_uninit(tor_mutex_t *m) +{ + DeleteCriticalSection(&m->mutex); +} +void +tor_mutex_acquire(tor_mutex_t *m) +{ + raw_assert(m); + EnterCriticalSection(&m->mutex); +} +void +tor_mutex_release(tor_mutex_t *m) +{ + LeaveCriticalSection(&m->mutex); +} diff --git a/src/lib/lock/include.am b/src/lib/lock/include.am new file mode 100644 index 0000000000..4e6f444347 --- /dev/null +++ b/src/lib/lock/include.am @@ -0,0 +1,24 @@ + +noinst_LIBRARIES += src/lib/libtor-lock.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-lock-testing.a +endif + +src_lib_libtor_lock_a_SOURCES = \ + src/lib/lock/compat_mutex.c + +if THREADS_PTHREADS +src_lib_libtor_lock_a_SOURCES += src/lib/lock/compat_mutex_pthreads.c +endif +if THREADS_WIN32 +src_lib_libtor_lock_a_SOURCES += src/lib/lock/compat_mutex_winthreads.c +endif + +src_lib_libtor_lock_testing_a_SOURCES = \ + $(src_lib_libtor_lock_a_SOURCES) +src_lib_libtor_lock_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_lock_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/lock/compat_mutex.h diff --git a/src/lib/log/.may_include b/src/lib/log/.may_include new file mode 100644 index 0000000000..852173aab3 --- /dev/null +++ b/src/lib/log/.may_include @@ -0,0 +1,15 @@ +orconfig.h + +lib/cc/*.h +lib/smartlist_core/*.h +lib/err/*.h +lib/fdio/*.h +lib/intmath/*.h +lib/lock/*.h +lib/log/*.h +lib/malloc/*.h +lib/string/*.h +lib/testsupport/*.h +lib/wallclock/*.h + +micro-revision.i
\ No newline at end of file diff --git a/src/lib/log/escape.c b/src/lib/log/escape.c new file mode 100644 index 0000000000..6ca01c6963 --- /dev/null +++ b/src/lib/log/escape.c @@ -0,0 +1,137 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file escape.c + * \brief Escape untrusted strings before sending them to the log. + **/ + +#include "lib/log/escape.h" +#include "lib/log/util_bug.h" +#include "lib/string/compat_ctype.h" +#include "lib/string/printf.h" +#include "lib/malloc/malloc.h" + +/** Allocate and return a new string representing the contents of <b>s</b>, + * surrounded by quotes and using standard C escapes. + * + * Generally, we use this for logging values that come in over the network to + * keep them from tricking users, and for sending certain values to the + * controller. + * + * We trust values from the resolver, OS, configuration file, and command line + * to not be maliciously ill-formed. We validate incoming routerdescs and + * SOCKS requests and addresses from BEGIN cells as they're parsed; + * afterwards, we trust them as non-malicious. + */ +char * +esc_for_log(const char *s) +{ + const char *cp; + char *result, *outp; + size_t len = 3; + if (!s) { + return tor_strdup("(null)"); + } + + for (cp = s; *cp; ++cp) { + switch (*cp) { + case '\\': + case '\"': + case '\'': + case '\r': + case '\n': + case '\t': + len += 2; + break; + default: + if (TOR_ISPRINT(*cp) && ((uint8_t)*cp)<127) + ++len; + else + len += 4; + break; + } + } + + tor_assert(len <= SSIZE_MAX); + + result = outp = tor_malloc(len); + *outp++ = '\"'; + for (cp = s; *cp; ++cp) { + /* This assertion should always succeed, since we will write at least + * one char here, and two chars for closing quote and nul later */ + tor_assert((outp-result) < (ssize_t)len-2); + switch (*cp) { + case '\\': + case '\"': + case '\'': + *outp++ = '\\'; + *outp++ = *cp; + break; + case '\n': + *outp++ = '\\'; + *outp++ = 'n'; + break; + case '\t': + *outp++ = '\\'; + *outp++ = 't'; + break; + case '\r': + *outp++ = '\\'; + *outp++ = 'r'; + break; + default: + if (TOR_ISPRINT(*cp) && ((uint8_t)*cp)<127) { + *outp++ = *cp; + } else { + tor_assert((outp-result) < (ssize_t)len-4); + tor_snprintf(outp, 5, "\\%03o", (int)(uint8_t) *cp); + outp += 4; + } + break; + } + } + + tor_assert((outp-result) <= (ssize_t)len-2); + *outp++ = '\"'; + *outp++ = 0; + + return result; +} + +/** Similar to esc_for_log. Allocate and return a new string representing + * the first n characters in <b>chars</b>, surround by quotes and using + * standard C escapes. If a NUL character is encountered in <b>chars</b>, + * the resulting string will be terminated there. + */ +char * +esc_for_log_len(const char *chars, size_t n) +{ + char *string = tor_strndup(chars, n); + char *string_escaped = esc_for_log(string); + tor_free(string); + return string_escaped; +} + +/** Allocate and return a new string representing the contents of <b>s</b>, + * surrounded by quotes and using standard C escapes. + * + * THIS FUNCTION IS NOT REENTRANT. Don't call it from outside the main + * thread. Also, each call invalidates the last-returned value, so don't + * try log_warn(LD_GENERAL, "%s %s", escaped(a), escaped(b)); + */ +const char * +escaped(const char *s) +{ + static char *escaped_val_ = NULL; + tor_free(escaped_val_); + + if (s) + escaped_val_ = esc_for_log(s); + else + escaped_val_ = NULL; + + return escaped_val_; +} diff --git a/src/lib/log/escape.h b/src/lib/log/escape.h new file mode 100644 index 0000000000..2f726186c5 --- /dev/null +++ b/src/lib/log/escape.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file escape.h + * \brief Header for escape.c + **/ + +#ifndef TOR_ESCAPE_H +#define TOR_ESCAPE_H + +#include "orconfig.h" +#include "lib/cc/compat_compiler.h" +#include <stddef.h> + +char *esc_for_log(const char *string) ATTR_MALLOC; +char *esc_for_log_len(const char *chars, size_t n) ATTR_MALLOC; +const char *escaped(const char *string); + +#endif /* !defined(TOR_TORLOG_H) */ diff --git a/src/lib/log/git_revision.c b/src/lib/log/git_revision.c new file mode 100644 index 0000000000..7d27549cad --- /dev/null +++ b/src/lib/log/git_revision.c @@ -0,0 +1,24 @@ +/* Copyright 2001-2004 Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" +#include "lib/log/git_revision.h" + +/** String describing which Tor Git repository version the source was + * built from. This string is generated by a bit of shell kludging in + * src/core/include.am, and is usually right. + */ +const char tor_git_revision[] = +#ifndef _MSC_VER +#include "micro-revision.i" +#endif + ""; + +const char tor_bug_suffix[] = " (on Tor " VERSION +#ifndef _MSC_VER + " " +#include "micro-revision.i" +#endif + ")"; diff --git a/src/lib/log/git_revision.h b/src/lib/log/git_revision.h new file mode 100644 index 0000000000..79e3c6684b --- /dev/null +++ b/src/lib/log/git_revision.h @@ -0,0 +1,12 @@ +/* Copyright 2001-2004 Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_GIT_REVISION_H +#define TOR_GIT_REVISION_H + +extern const char tor_git_revision[]; +extern const char tor_bug_suffix[]; + +#endif /* !defined(TOR_GIT_REVISION_H) */ diff --git a/src/lib/log/include.am b/src/lib/log/include.am new file mode 100644 index 0000000000..4a6c9b3686 --- /dev/null +++ b/src/lib/log/include.am @@ -0,0 +1,36 @@ + +noinst_LIBRARIES += src/lib/libtor-log.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-log-testing.a +endif + +src_lib_libtor_log_a_SOURCES = \ + src/lib/log/escape.c \ + src/lib/log/git_revision.c \ + src/lib/log/ratelim.c \ + src/lib/log/log.c \ + src/lib/log/util_bug.c + +if WIN32 +src_lib_libtor_log_a_SOURCES += src/lib/log/win32err.c +endif + +src_lib_libtor_log_testing_a_SOURCES = \ + $(src_lib_libtor_log_a_SOURCES) +src_lib_libtor_log_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_log_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +# Declare that these object files depend on micro-revision.i. Without this +# rule, we could try to build them before micro-revision.i was created. +src/lib/log/git_revision.$(OBJEXT) \ + src/lib/log/src_lib_libtor_log_testing_a-git_revision.$(OBJEXT): \ + micro-revision.i + +noinst_HEADERS += \ + src/lib/log/escape.h \ + src/lib/log/git_revision.h \ + src/lib/log/ratelim.h \ + src/lib/log/log.h \ + src/lib/log/util_bug.h \ + src/lib/log/win32err.h diff --git a/src/lib/log/log.c b/src/lib/log/log.c new file mode 100644 index 0000000000..a9ad38fb25 --- /dev/null +++ b/src/lib/log/log.c @@ -0,0 +1,1483 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file log.c + * \brief Functions to send messages to log files or the console. + **/ + +#include "orconfig.h" +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef HAVE_TIME_H +#include <time.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#define LOG_PRIVATE +#include "lib/log/log.h" +#include "lib/log/git_revision.h" +#include "lib/log/ratelim.h" +#include "lib/lock/compat_mutex.h" +#include "lib/smartlist_core/smartlist_core.h" +#include "lib/smartlist_core/smartlist_foreach.h" +#include "lib/smartlist_core/smartlist_split.h" +#include "lib/err/torerr.h" +#include "lib/intmath/bits.h" +#include "lib/string/compat_string.h" +#include "lib/string/printf.h" +#include "lib/malloc/malloc.h" +#include "lib/string/util_string.h" +#include "lib/wallclock/tor_gettimeofday.h" +#include "lib/wallclock/approx_time.h" +#include "lib/wallclock/time_to_tm.h" +#include "lib/fdio/fdio.h" + +#ifdef HAVE_ANDROID_LOG_H +#include <android/log.h> +#endif // HAVE_ANDROID_LOG_H. + +/** Given a severity, yields an index into log_severity_list_t.masks to use + * for that severity. */ +#define SEVERITY_MASK_IDX(sev) ((sev) - LOG_ERR) + +/** @{ */ +/** The string we stick at the end of a log message when it is too long, + * and its length. */ +#define TRUNCATED_STR "[...truncated]" +#define TRUNCATED_STR_LEN 14 +/** @} */ + +/** Defining compile-time constants for Tor log levels (used by the Rust + * log wrapper at src/rust/tor_log) */ +const int LOG_WARN_ = LOG_WARN; +const int LOG_NOTICE_ = LOG_NOTICE; +const log_domain_mask_t LD_GENERAL_ = LD_GENERAL; +const log_domain_mask_t LD_NET_ = LD_NET; + +/** Information for a single logfile; only used in log.c */ +typedef struct logfile_t { + struct logfile_t *next; /**< Next logfile_t in the linked list. */ + char *filename; /**< Filename to open. */ + int fd; /**< fd to receive log messages, or -1 for none. */ + int seems_dead; /**< Boolean: true if the stream seems to be kaput. */ + int needs_close; /**< Boolean: true if the stream gets closed on shutdown. */ + int is_temporary; /**< Boolean: close after initializing logging subsystem.*/ + int is_syslog; /**< Boolean: send messages to syslog. */ + int is_android; /**< Boolean: send messages to Android's log subsystem. */ + char *android_tag; /**< Identity Tag used in Android's log subsystem. */ + log_callback callback; /**< If not NULL, send messages to this function. */ + log_severity_list_t *severities; /**< Which severity of messages should we + * log for each log domain? */ +} logfile_t; + +static void log_free_(logfile_t *victim); +#define log_free(lg) \ + FREE_AND_NULL(logfile_t, log_free_, (lg)) + +/** Helper: map a log severity to descriptive string. */ +static inline const char * +sev_to_string(int severity) +{ + switch (severity) { + case LOG_DEBUG: return "debug"; + case LOG_INFO: return "info"; + case LOG_NOTICE: return "notice"; + case LOG_WARN: return "warn"; + case LOG_ERR: return "err"; + default: /* Call raw_assert, not tor_assert, since tor_assert + * calls log on failure. */ + raw_assert_unreached(); return "UNKNOWN"; // LCOV_EXCL_LINE + } +} + +/** Helper: decide whether to include the function name in the log message. */ +static inline int +should_log_function_name(log_domain_mask_t domain, int severity) +{ + switch (severity) { + case LOG_DEBUG: + case LOG_INFO: + /* All debugging messages occur in interesting places. */ + return (domain & LD_NOFUNCNAME) == 0; + case LOG_NOTICE: + case LOG_WARN: + case LOG_ERR: + /* We care about places where bugs occur. */ + return (domain & (LD_BUG|LD_NOFUNCNAME)) == LD_BUG; + default: + /* Call raw_assert, not tor_assert, since tor_assert calls + * log on failure. */ + raw_assert(0); return 0; // LCOV_EXCL_LINE + } +} + +#ifdef HAVE_ANDROID_LOG_H +/** Helper function to convert Tor's log severity into the matching + * Android log priority. + */ +static int +severity_to_android_log_priority(int severity) +{ + switch (severity) { + case LOG_DEBUG: + return ANDROID_LOG_VERBOSE; + case LOG_INFO: + return ANDROID_LOG_DEBUG; + case LOG_NOTICE: + return ANDROID_LOG_INFO; + case LOG_WARN: + return ANDROID_LOG_WARN; + case LOG_ERR: + return ANDROID_LOG_ERROR; + default: + // LCOV_EXCL_START + raw_assert(0); + return 0; + // LCOV_EXCL_STOP + } +} +#endif // HAVE_ANDROID_LOG_H. + +/** A mutex to guard changes to logfiles and logging. */ +static tor_mutex_t log_mutex; +/** True iff we have initialized log_mutex */ +static int log_mutex_initialized = 0; + +/** Linked list of logfile_t. */ +static logfile_t *logfiles = NULL; +/** Boolean: do we report logging domains? */ +static int log_domains_are_logged = 0; + +#ifdef HAVE_SYSLOG_H +/** The number of open syslog log handlers that we have. When this reaches 0, + * we can close our connection to the syslog facility. */ +static int syslog_count = 0; +#endif + +/** Represents a log message that we are going to send to callback-driven + * loggers once we can do so in a non-reentrant way. */ +typedef struct pending_log_message_t { + int severity; /**< The severity of the message */ + log_domain_mask_t domain; /**< The domain of the message */ + char *fullmsg; /**< The message, with all decorations */ + char *msg; /**< The content of the message */ +} pending_log_message_t; + +/** Log messages waiting to be replayed onto callback-based logs */ +static smartlist_t *pending_cb_messages = NULL; + +/** Callback to invoke when pending_cb_messages becomes nonempty. */ +static pending_callback_callback pending_cb_cb = NULL; + +/** Log messages waiting to be replayed once the logging system is initialized. + */ +static smartlist_t *pending_startup_messages = NULL; + +/** Number of bytes of messages queued in pending_startup_messages. (This is + * the length of the messages, not the number of bytes used to store + * them.) */ +static size_t pending_startup_messages_len; + +/** True iff we should store messages while waiting for the logs to get + * configured. */ +static int queue_startup_messages = 1; + +/** True iff __PRETTY_FUNCTION__ includes parenthesized arguments. */ +static int pretty_fn_has_parens = 0; + +/** Don't store more than this many bytes of messages while waiting for the + * logs to get configured. */ +#define MAX_STARTUP_MSG_LEN (1<<16) + +/** Lock the log_mutex to prevent others from changing the logfile_t list */ +#define LOCK_LOGS() STMT_BEGIN \ + raw_assert(log_mutex_initialized); \ + tor_mutex_acquire(&log_mutex); \ + STMT_END +/** Unlock the log_mutex */ +#define UNLOCK_LOGS() STMT_BEGIN \ + raw_assert(log_mutex_initialized); \ + tor_mutex_release(&log_mutex); \ + STMT_END + +/** What's the lowest log level anybody cares about? Checking this lets us + * bail out early from log_debug if we aren't debugging. */ +int log_global_min_severity_ = LOG_NOTICE; + +static void delete_log(logfile_t *victim); +static void close_log(logfile_t *victim); + +static char *domain_to_string(log_domain_mask_t domain, + char *buf, size_t buflen); +static inline char *format_msg(char *buf, size_t buf_len, + log_domain_mask_t domain, int severity, const char *funcname, + const char *suffix, + const char *format, va_list ap, size_t *msg_len_out) + CHECK_PRINTF(7,0); + +/** Name of the application: used to generate the message we write at the + * start of each new log. */ +static char *appname = NULL; + +/** Set the "application name" for the logs to <b>name</b>: we'll use this + * name in the message we write when starting up, and at the start of each new + * log. + * + * Tor uses this string to write the version number to the log file. */ +void +log_set_application_name(const char *name) +{ + tor_free(appname); + appname = name ? tor_strdup(name) : NULL; +} + +/** Return true if some of the running logs might be interested in a log + * message of the given severity in the given domains. If this function + * returns true, the log message might be ignored anyway, but if it returns + * false, it is definitely_ safe not to log the message. */ +int +log_message_is_interesting(int severity, log_domain_mask_t domain) +{ + (void) domain; + return (severity <= log_global_min_severity_); +} + +/** + * As tor_log, but takes an optional function name, and does not treat its + * <b>string</b> as a printf format. + * + * For use by Rust integration. + */ +void +tor_log_string(int severity, log_domain_mask_t domain, + const char *function, const char *string) +{ + log_fn_(severity, domain, function, "%s", string); +} + +/** Log time granularity in milliseconds. */ +static int log_time_granularity = 1; + +/** Define log time granularity for all logs to be <b>granularity_msec</b> + * milliseconds. */ +void +set_log_time_granularity(int granularity_msec) +{ + log_time_granularity = granularity_msec; + tor_log_sigsafe_err_set_granularity(granularity_msec); +} + +/** Helper: Write the standard prefix for log lines to a + * <b>buf_len</b> character buffer in <b>buf</b>. + */ +static inline size_t +log_prefix_(char *buf, size_t buf_len, int severity) +{ + time_t t; + struct timeval now; + struct tm tm; + size_t n; + int r, ms; + + tor_gettimeofday(&now); + t = (time_t)now.tv_sec; + ms = (int)now.tv_usec / 1000; + if (log_time_granularity >= 1000) { + t -= t % (log_time_granularity / 1000); + ms = 0; + } else { + ms -= ((int)now.tv_usec / 1000) % log_time_granularity; + } + + n = strftime(buf, buf_len, "%b %d %H:%M:%S", + tor_localtime_r_msg(&t, &tm, NULL)); + r = tor_snprintf(buf+n, buf_len-n, ".%.3i [%s] ", ms, + sev_to_string(severity)); + + if (r<0) + return buf_len-1; + else + return n+r; +} + +/** If lf refers to an actual file that we have just opened, and the file + * contains no data, log an "opening new logfile" message at the top. + * + * Return -1 if the log is broken and needs to be deleted, else return 0. + */ +static int +log_tor_version(logfile_t *lf, int reset) +{ + char buf[256]; + size_t n; + int is_new; + + if (!lf->needs_close) + /* If it doesn't get closed, it isn't really a file. */ + return 0; + if (lf->is_temporary) + /* If it's temporary, it isn't really a file. */ + return 0; + + is_new = lf->fd >= 0 && tor_fd_getpos(lf->fd) == 0; + + if (reset && !is_new) + /* We are resetting, but we aren't at the start of the file; no + * need to log again. */ + return 0; + n = log_prefix_(buf, sizeof(buf), LOG_NOTICE); + if (appname) { + tor_snprintf(buf+n, sizeof(buf)-n, + "%s opening %slog file.\n", appname, is_new?"new ":""); + } else { + tor_snprintf(buf+n, sizeof(buf)-n, + "Tor %s opening %slog file.\n", VERSION, is_new?"new ":""); + } + if (write_all_to_fd_minimal(lf->fd, buf, strlen(buf)) < 0) /* error */ + return -1; /* failed */ + return 0; +} + +/** Helper: Format a log message into a fixed-sized buffer. (This is + * factored out of <b>logv</b> so that we never format a message more + * than once.) Return a pointer to the first character of the message + * portion of the formatted string. + */ +static inline char * +format_msg(char *buf, size_t buf_len, + log_domain_mask_t domain, int severity, const char *funcname, + const char *suffix, + const char *format, va_list ap, size_t *msg_len_out) +{ + size_t n; + int r; + char *end_of_prefix; + char *buf_end; + + raw_assert(buf_len >= 16); /* prevent integer underflow and stupidity */ + buf_len -= 2; /* subtract 2 characters so we have room for \n\0 */ + buf_end = buf+buf_len; /* point *after* the last char we can write to */ + + n = log_prefix_(buf, buf_len, severity); + end_of_prefix = buf+n; + + if (log_domains_are_logged) { + char *cp = buf+n; + if (cp == buf_end) goto format_msg_no_room_for_domains; + *cp++ = '{'; + if (cp == buf_end) goto format_msg_no_room_for_domains; + cp = domain_to_string(domain, cp, (buf+buf_len-cp)); + if (cp == buf_end) goto format_msg_no_room_for_domains; + *cp++ = '}'; + if (cp == buf_end) goto format_msg_no_room_for_domains; + *cp++ = ' '; + if (cp == buf_end) goto format_msg_no_room_for_domains; + end_of_prefix = cp; + n = cp-buf; + format_msg_no_room_for_domains: + /* This will leave end_of_prefix and n unchanged, and thus cause + * whatever log domain string we had written to be clobbered. */ + ; + } + + if (funcname && should_log_function_name(domain, severity)) { + r = tor_snprintf(buf+n, buf_len-n, + pretty_fn_has_parens ? "%s: " : "%s(): ", + funcname); + if (r<0) + n = strlen(buf); + else + n += r; + } + + if (domain == LD_BUG && buf_len-n > 6) { + memcpy(buf+n, "Bug: ", 6); + n += 5; + } + + r = tor_vsnprintf(buf+n,buf_len-n,format,ap); + if (r < 0) { + /* The message was too long; overwrite the end of the buffer with + * "[...truncated]" */ + if (buf_len >= TRUNCATED_STR_LEN) { + size_t offset = buf_len-TRUNCATED_STR_LEN; + /* We have an extra 2 characters after buf_len to hold the \n\0, + * so it's safe to add 1 to the size here. */ + strlcpy(buf+offset, TRUNCATED_STR, buf_len-offset+1); + } + /* Set 'n' to the end of the buffer, where we'll be writing \n\0. + * Since we already subtracted 2 from buf_len, this is safe.*/ + n = buf_len; + } else { + n += r; + if (suffix) { + size_t suffix_len = strlen(suffix); + if (buf_len-n >= suffix_len) { + memcpy(buf+n, suffix, suffix_len); + n += suffix_len; + } + } + } + + if (domain == LD_BUG && + buf_len - n > strlen(tor_bug_suffix)+1) { + memcpy(buf+n, tor_bug_suffix, strlen(tor_bug_suffix)); + n += strlen(tor_bug_suffix); + } + + buf[n]='\n'; + buf[n+1]='\0'; + *msg_len_out = n+1; + return end_of_prefix; +} + +/* Create a new pending_log_message_t with appropriate values */ +static pending_log_message_t * +pending_log_message_new(int severity, log_domain_mask_t domain, + const char *fullmsg, const char *shortmsg) +{ + pending_log_message_t *m = tor_malloc(sizeof(pending_log_message_t)); + m->severity = severity; + m->domain = domain; + m->fullmsg = fullmsg ? tor_strdup(fullmsg) : NULL; + m->msg = tor_strdup(shortmsg); + return m; +} + +#define pending_log_message_free(msg) \ + FREE_AND_NULL(pending_log_message_t, pending_log_message_free_, (msg)) + +/** Release all storage held by <b>msg</b>. */ +static void +pending_log_message_free_(pending_log_message_t *msg) +{ + if (!msg) + return; + tor_free(msg->msg); + tor_free(msg->fullmsg); + tor_free(msg); +} + +/** Helper function: returns true iff the log file, given in <b>lf</b>, is + * handled externally via the system log API, the Android logging API, or is an + * external callback function. */ +static inline int +logfile_is_external(const logfile_t *lf) +{ + raw_assert(lf); + return lf->is_syslog || lf->is_android || lf->callback; +} + +/** Return true iff <b>lf</b> would like to receive a message with the + * specified <b>severity</b> in the specified <b>domain</b>. + */ +static inline int +logfile_wants_message(const logfile_t *lf, int severity, + log_domain_mask_t domain) +{ + if (! (lf->severities->masks[SEVERITY_MASK_IDX(severity)] & domain)) { + return 0; + } + if (! (lf->fd >= 0 || logfile_is_external(lf))) { + return 0; + } + if (lf->seems_dead) { + return 0; + } + + return 1; +} + +/** Send a message to <b>lf</b>. The full message, with time prefix and + * severity, is in <b>buf</b>. The message itself is in + * <b>msg_after_prefix</b>. If <b>callbacks_deferred</b> points to true, then + * we already deferred this message for pending callbacks and don't need to do + * it again. Otherwise, if we need to do it, do it, and set + * <b>callbacks_deferred</b> to 1. */ +static inline void +logfile_deliver(logfile_t *lf, const char *buf, size_t msg_len, + const char *msg_after_prefix, log_domain_mask_t domain, + int severity, int *callbacks_deferred) +{ + + if (lf->is_syslog) { +#ifdef HAVE_SYSLOG_H +#ifdef MAXLINE + /* Some syslog implementations have limits on the length of what you can + * pass them, and some very old ones do not detect overflow so well. + * Regrettably, they call their maximum line length MAXLINE. */ +#if MAXLINE < 64 +#warn "MAXLINE is a very low number; it might not be from syslog.h after all" +#endif + char *m = msg_after_prefix; + if (msg_len >= MAXLINE) + m = tor_strndup(msg_after_prefix, MAXLINE-1); + syslog(severity, "%s", m); + if (m != msg_after_prefix) { + tor_free(m); + } +#else /* !(defined(MAXLINE)) */ + /* We have syslog but not MAXLINE. That's promising! */ + syslog(severity, "%s", msg_after_prefix); +#endif /* defined(MAXLINE) */ +#endif /* defined(HAVE_SYSLOG_H) */ + } else if (lf->is_android) { +#ifdef HAVE_ANDROID_LOG_H + int priority = severity_to_android_log_priority(severity); + __android_log_write(priority, lf->android_tag, msg_after_prefix); +#endif // HAVE_ANDROID_LOG_H. + } else if (lf->callback) { + if (domain & LD_NOCB) { + if (!*callbacks_deferred && pending_cb_messages) { + smartlist_add(pending_cb_messages, + pending_log_message_new(severity,domain,NULL,msg_after_prefix)); + *callbacks_deferred = 1; + if (smartlist_len(pending_cb_messages) == 1 && pending_cb_cb) { + pending_cb_cb(); + } + } + } else { + lf->callback(severity, domain, msg_after_prefix); + } + } else { + if (write_all_to_fd_minimal(lf->fd, buf, msg_len) < 0) { /* error */ + /* don't log the error! mark this log entry to be blown away, and + * continue. */ + lf->seems_dead = 1; + } + } +} + +/** Helper: sends a message to the appropriate logfiles, at loglevel + * <b>severity</b>. If provided, <b>funcname</b> is prepended to the + * message. The actual message is derived as from tor_snprintf(format,ap). + */ +MOCK_IMPL(STATIC void, +logv,(int severity, log_domain_mask_t domain, const char *funcname, + const char *suffix, const char *format, va_list ap)) +{ + char buf[10240]; + size_t msg_len = 0; + int formatted = 0; + logfile_t *lf; + char *end_of_prefix=NULL; + int callbacks_deferred = 0; + + /* Call raw_assert, not tor_assert, since tor_assert calls log on failure. */ + raw_assert(format); + /* check that severity is sane. Overrunning the masks array leads to + * interesting and hard to diagnose effects */ + raw_assert(severity >= LOG_ERR && severity <= LOG_DEBUG); + /* check that we've initialised the log mutex before we try to lock it */ + raw_assert(log_mutex_initialized); + LOCK_LOGS(); + + if ((! (domain & LD_NOCB)) && pending_cb_messages + && smartlist_len(pending_cb_messages)) + flush_pending_log_callbacks(); + + if (queue_startup_messages && + pending_startup_messages_len < MAX_STARTUP_MSG_LEN) { + end_of_prefix = + format_msg(buf, sizeof(buf), domain, severity, funcname, suffix, + format, ap, &msg_len); + formatted = 1; + + smartlist_add(pending_startup_messages, + pending_log_message_new(severity,domain,buf,end_of_prefix)); + pending_startup_messages_len += msg_len; + } + + for (lf = logfiles; lf; lf = lf->next) { + if (! logfile_wants_message(lf, severity, domain)) + continue; + + if (!formatted) { + end_of_prefix = + format_msg(buf, sizeof(buf), domain, severity, funcname, suffix, + format, ap, &msg_len); + formatted = 1; + } + + logfile_deliver(lf, buf, msg_len, end_of_prefix, domain, severity, + &callbacks_deferred); + } + UNLOCK_LOGS(); +} + +/** Output a message to the log. It gets logged to all logfiles that + * care about messages with <b>severity</b> in <b>domain</b>. The content + * is formatted printf-style based on <b>format</b> and extra arguments. + * */ +void +tor_log(int severity, log_domain_mask_t domain, const char *format, ...) +{ + va_list ap; + if (severity > log_global_min_severity_) + return; + va_start(ap,format); +#ifdef TOR_UNIT_TESTS + if (domain & LD_NO_MOCK) + logv__real(severity, domain, NULL, NULL, format, ap); + else +#endif + logv(severity, domain, NULL, NULL, format, ap); + va_end(ap); +} + +/** Helper function; return true iff the <b>n</b>-element array <b>array</b> + * contains <b>item</b>. */ +static int +int_array_contains(const int *array, int n, int item) +{ + int j; + for (j = 0; j < n; ++j) { + if (array[j] == item) + return 1; + } + return 0; +} + +/** Function to call whenever the list of logs changes to get ready to log + * from signal handlers. */ +void +tor_log_update_sigsafe_err_fds(void) +{ + const logfile_t *lf; + int found_real_stderr = 0; + + int fds[TOR_SIGSAFE_LOG_MAX_FDS]; + int n_fds; + + LOCK_LOGS(); + /* Reserve the first one for stderr. This is safe because when we daemonize, + * we dup2 /dev/null to stderr, */ + fds[0] = STDERR_FILENO; + n_fds = 1; + + for (lf = logfiles; lf; lf = lf->next) { + /* Don't try callback to the control port, or syslogs: We can't + * do them from a signal handler. Don't try stdout: we always do stderr. + */ + if (lf->is_temporary || logfile_is_external(lf) + || lf->seems_dead || lf->fd < 0) + continue; + if (lf->severities->masks[SEVERITY_MASK_IDX(LOG_ERR)] & + (LD_BUG|LD_GENERAL)) { + if (lf->fd == STDERR_FILENO) + found_real_stderr = 1; + /* Avoid duplicates */ + if (int_array_contains(fds, n_fds, lf->fd)) + continue; + fds[n_fds++] = lf->fd; + if (n_fds == TOR_SIGSAFE_LOG_MAX_FDS) + break; + } + } + + if (!found_real_stderr && + int_array_contains(fds, n_fds, STDOUT_FILENO)) { + /* Don't use a virtual stderr when we're also logging to stdout. */ + raw_assert(n_fds >= 2); /* Don't tor_assert inside log fns */ + fds[0] = fds[--n_fds]; + } + + UNLOCK_LOGS(); + + tor_log_set_sigsafe_err_fds(fds, n_fds); +} + +/** Add to <b>out</b> a copy of every currently configured log file name. Used + * to enable access to these filenames with the sandbox code. */ +void +tor_log_get_logfile_names(smartlist_t *out) +{ + logfile_t *lf; + raw_assert(out); + + LOCK_LOGS(); + + for (lf = logfiles; lf; lf = lf->next) { + if (lf->is_temporary || logfile_is_external(lf)) + continue; + if (lf->filename == NULL) + continue; + smartlist_add_strdup(out, lf->filename); + } + + UNLOCK_LOGS(); +} + +/** Implementation of the log_fn backend, used when we have + * variadic macros. All arguments are as for log_fn, except for + * <b>fn</b>, which is the name of the calling functions. */ +void +log_fn_(int severity, log_domain_mask_t domain, const char *fn, + const char *format, ...) +{ + va_list ap; + if (severity > log_global_min_severity_) + return; + va_start(ap,format); + logv(severity, domain, fn, NULL, format, ap); + va_end(ap); +} +void +log_fn_ratelim_(ratelim_t *ratelim, int severity, log_domain_mask_t domain, + const char *fn, const char *format, ...) +{ + va_list ap; + char *m; + if (severity > log_global_min_severity_) + return; + m = rate_limit_log(ratelim, approx_time()); + if (m == NULL) + return; + va_start(ap, format); + logv(severity, domain, fn, m, format, ap); + va_end(ap); + tor_free(m); +} + +/** Free all storage held by <b>victim</b>. */ +static void +log_free_(logfile_t *victim) +{ + if (!victim) + return; + tor_free(victim->severities); + tor_free(victim->filename); + tor_free(victim->android_tag); + tor_free(victim); +} + +/** Close all open log files, and free other static memory. */ +void +logs_free_all(void) +{ + logfile_t *victim, *next; + smartlist_t *messages, *messages2; + LOCK_LOGS(); + next = logfiles; + logfiles = NULL; + messages = pending_cb_messages; + pending_cb_messages = NULL; + pending_cb_cb = NULL; + messages2 = pending_startup_messages; + pending_startup_messages = NULL; + UNLOCK_LOGS(); + while (next) { + victim = next; + next = next->next; + close_log(victim); + log_free(victim); + } + tor_free(appname); + + SMARTLIST_FOREACH(messages, pending_log_message_t *, msg, { + pending_log_message_free(msg); + }); + smartlist_free(messages); + + if (messages2) { + SMARTLIST_FOREACH(messages2, pending_log_message_t *, msg, { + pending_log_message_free(msg); + }); + smartlist_free(messages2); + } + + /* We _could_ destroy the log mutex here, but that would screw up any logs + * that happened between here and the end of execution. */ +} + +/** Remove and free the log entry <b>victim</b> from the linked-list + * logfiles (it is probably present, but it might not be due to thread + * racing issues). After this function is called, the caller shouldn't + * refer to <b>victim</b> anymore. + * + * Long-term, we need to do something about races in the log subsystem + * in general. See bug 222 for more details. + */ +static void +delete_log(logfile_t *victim) +{ + logfile_t *tmpl; + if (victim == logfiles) + logfiles = victim->next; + else { + for (tmpl = logfiles; tmpl && tmpl->next != victim; tmpl=tmpl->next) ; +// raw_assert(tmpl); +// raw_assert(tmpl->next == victim); + if (!tmpl) + return; + tmpl->next = victim->next; + } + log_free(victim); +} + +/** Helper: release system resources (but not memory) held by a single + * logfile_t. */ +static void +close_log(logfile_t *victim) +{ + if (victim->needs_close && victim->fd >= 0) { + close(victim->fd); + victim->fd = -1; + } else if (victim->is_syslog) { +#ifdef HAVE_SYSLOG_H + if (--syslog_count == 0) { + /* There are no other syslogs; close the logging facility. */ + closelog(); + } +#endif /* defined(HAVE_SYSLOG_H) */ + } +} + +/** Adjust a log severity configuration in <b>severity_out</b> to contain + * every domain between <b>loglevelMin</b> and <b>loglevelMax</b>, inclusive. + */ +void +set_log_severity_config(int loglevelMin, int loglevelMax, + log_severity_list_t *severity_out) +{ + int i; + raw_assert(loglevelMin >= loglevelMax); + raw_assert(loglevelMin >= LOG_ERR && loglevelMin <= LOG_DEBUG); + raw_assert(loglevelMax >= LOG_ERR && loglevelMax <= LOG_DEBUG); + memset(severity_out, 0, sizeof(log_severity_list_t)); + for (i = loglevelMin; i >= loglevelMax; --i) { + severity_out->masks[SEVERITY_MASK_IDX(i)] = ~0u; + } +} + +/** Add a log handler named <b>name</b> to send all messages in <b>severity</b> + * to <b>fd</b>. Copies <b>severity</b>. Helper: does no locking. */ +static void +add_stream_log_impl(const log_severity_list_t *severity, + const char *name, int fd) +{ + logfile_t *lf; + lf = tor_malloc_zero(sizeof(logfile_t)); + lf->fd = fd; + lf->filename = tor_strdup(name); + lf->severities = tor_memdup(severity, sizeof(log_severity_list_t)); + lf->next = logfiles; + + logfiles = lf; + log_global_min_severity_ = get_min_log_level(); +} + +/** Add a log handler named <b>name</b> to send all messages in <b>severity</b> + * to <b>fd</b>. Steals a reference to <b>severity</b>; the caller must + * not use it after calling this function. */ +void +add_stream_log(const log_severity_list_t *severity, const char *name, int fd) +{ + LOCK_LOGS(); + add_stream_log_impl(severity, name, fd); + UNLOCK_LOGS(); +} + +/** Initialize the global logging facility */ +void +init_logging(int disable_startup_queue) +{ + if (!log_mutex_initialized) { + tor_mutex_init(&log_mutex); + log_mutex_initialized = 1; + } +#ifdef __GNUC__ + if (strchr(__PRETTY_FUNCTION__, '(')) { + pretty_fn_has_parens = 1; + } +#endif + if (pending_cb_messages == NULL) + pending_cb_messages = smartlist_new(); + if (disable_startup_queue) + queue_startup_messages = 0; + if (pending_startup_messages == NULL && queue_startup_messages) { + pending_startup_messages = smartlist_new(); + } +} + +/** Set whether we report logging domains as a part of our log messages. + */ +void +logs_set_domain_logging(int enabled) +{ + LOCK_LOGS(); + log_domains_are_logged = enabled; + UNLOCK_LOGS(); +} + +/** Add a log handler to receive messages during startup (before the real + * logs are initialized). + */ +void +add_temp_log(int min_severity) +{ + log_severity_list_t *s = tor_malloc_zero(sizeof(log_severity_list_t)); + set_log_severity_config(min_severity, LOG_ERR, s); + LOCK_LOGS(); + add_stream_log_impl(s, "<temp>", fileno(stdout)); + tor_free(s); + logfiles->is_temporary = 1; + UNLOCK_LOGS(); +} + +/** + * Register "cb" as the callback to call when there are new pending log + * callbacks to be flushed with flush_pending_log_callbacks(). + * + * Note that this callback, if present, can be invoked from any thread. + * + * This callback must not log. + * + * It is intentional that this function contains the name "callback" twice: it + * sets a "callback" to be called on the condition that there is a "pending + * callback". + **/ +void +logs_set_pending_callback_callback(pending_callback_callback cb) +{ + pending_cb_cb = cb; +} + +/** + * Add a log handler to send messages in <b>severity</b> + * to the function <b>cb</b>. + */ +int +add_callback_log(const log_severity_list_t *severity, log_callback cb) +{ + logfile_t *lf; + lf = tor_malloc_zero(sizeof(logfile_t)); + lf->fd = -1; + lf->severities = tor_memdup(severity, sizeof(log_severity_list_t)); + lf->filename = tor_strdup("<callback>"); + lf->callback = cb; + lf->next = logfiles; + + LOCK_LOGS(); + logfiles = lf; + log_global_min_severity_ = get_min_log_level(); + UNLOCK_LOGS(); + return 0; +} + +/** Adjust the configured severity of any logs whose callback function is + * <b>cb</b>. */ +void +change_callback_log_severity(int loglevelMin, int loglevelMax, + log_callback cb) +{ + logfile_t *lf; + log_severity_list_t severities; + set_log_severity_config(loglevelMin, loglevelMax, &severities); + LOCK_LOGS(); + for (lf = logfiles; lf; lf = lf->next) { + if (lf->callback == cb) { + memcpy(lf->severities, &severities, sizeof(severities)); + } + } + log_global_min_severity_ = get_min_log_level(); + UNLOCK_LOGS(); +} + +/** If there are any log messages that were generated with LD_NOCB waiting to + * be sent to callback-based loggers, send them now. */ +void +flush_pending_log_callbacks(void) +{ + logfile_t *lf; + smartlist_t *messages, *messages_tmp; + + LOCK_LOGS(); + if (!pending_cb_messages || 0 == smartlist_len(pending_cb_messages)) { + UNLOCK_LOGS(); + return; + } + + messages = pending_cb_messages; + pending_cb_messages = smartlist_new(); + do { + SMARTLIST_FOREACH_BEGIN(messages, pending_log_message_t *, msg) { + const int severity = msg->severity; + const int domain = msg->domain; + for (lf = logfiles; lf; lf = lf->next) { + if (! lf->callback || lf->seems_dead || + ! (lf->severities->masks[SEVERITY_MASK_IDX(severity)] & domain)) { + continue; + } + lf->callback(severity, domain, msg->msg); + } + pending_log_message_free(msg); + } SMARTLIST_FOREACH_END(msg); + smartlist_clear(messages); + + messages_tmp = pending_cb_messages; + pending_cb_messages = messages; + messages = messages_tmp; + } while (smartlist_len(messages)); + + smartlist_free(messages); + + UNLOCK_LOGS(); +} + +/** Flush all the messages we stored from startup while waiting for log + * initialization. + */ +void +flush_log_messages_from_startup(void) +{ + logfile_t *lf; + + LOCK_LOGS(); + queue_startup_messages = 0; + pending_startup_messages_len = 0; + if (! pending_startup_messages) + goto out; + + SMARTLIST_FOREACH_BEGIN(pending_startup_messages, pending_log_message_t *, + msg) { + int callbacks_deferred = 0; + for (lf = logfiles; lf; lf = lf->next) { + if (! logfile_wants_message(lf, msg->severity, msg->domain)) + continue; + + /* We configure a temporary startup log that goes to stdout, so we + * shouldn't replay to stdout/stderr*/ + if (lf->fd == STDOUT_FILENO || lf->fd == STDERR_FILENO) { + continue; + } + + logfile_deliver(lf, msg->fullmsg, strlen(msg->fullmsg), msg->msg, + msg->domain, msg->severity, &callbacks_deferred); + } + pending_log_message_free(msg); + } SMARTLIST_FOREACH_END(msg); + smartlist_free(pending_startup_messages); + pending_startup_messages = NULL; + + out: + UNLOCK_LOGS(); +} + +/** Close any log handlers added by add_temp_log() or marked by + * mark_logs_temp(). */ +void +close_temp_logs(void) +{ + logfile_t *lf, **p; + + LOCK_LOGS(); + for (p = &logfiles; *p; ) { + if ((*p)->is_temporary) { + lf = *p; + /* we use *p here to handle the edge case of the head of the list */ + *p = (*p)->next; + close_log(lf); + log_free(lf); + } else { + p = &((*p)->next); + } + } + + log_global_min_severity_ = get_min_log_level(); + UNLOCK_LOGS(); +} + +/** Make all currently temporary logs (set to be closed by close_temp_logs) + * live again, and close all non-temporary logs. */ +void +rollback_log_changes(void) +{ + logfile_t *lf; + LOCK_LOGS(); + for (lf = logfiles; lf; lf = lf->next) + lf->is_temporary = ! lf->is_temporary; + UNLOCK_LOGS(); + close_temp_logs(); +} + +/** Configure all log handles to be closed by close_temp_logs(). */ +void +mark_logs_temp(void) +{ + logfile_t *lf; + LOCK_LOGS(); + for (lf = logfiles; lf; lf = lf->next) + lf->is_temporary = 1; + UNLOCK_LOGS(); +} + +/** + * Add a log handler to send messages to <b>filename</b> via <b>fd</b>. If + * opening the logfile failed, -1 is returned and errno is set appropriately + * (by open(2)). Takes ownership of fd. + */ +int +add_file_log(const log_severity_list_t *severity, + const char *filename, + int fd) +{ + logfile_t *lf; + + if (fd<0) + return -1; + if (tor_fd_seekend(fd)<0) { + close(fd); + return -1; + } + + LOCK_LOGS(); + add_stream_log_impl(severity, filename, fd); + logfiles->needs_close = 1; + lf = logfiles; + log_global_min_severity_ = get_min_log_level(); + + if (log_tor_version(lf, 0) < 0) { + delete_log(lf); + } + UNLOCK_LOGS(); + + return 0; +} + +#ifdef HAVE_SYSLOG_H +/** + * Add a log handler to send messages to they system log facility. + * + * If this is the first log handler, opens syslog with ident Tor or + * Tor-<syslog_identity_tag> if that is not NULL. + */ +int +add_syslog_log(const log_severity_list_t *severity, + const char* syslog_identity_tag) +{ + logfile_t *lf; + if (syslog_count++ == 0) { + /* This is the first syslog. */ + static char buf[256]; + if (syslog_identity_tag) { + tor_snprintf(buf, sizeof(buf), "Tor-%s", syslog_identity_tag); + } else { + tor_snprintf(buf, sizeof(buf), "Tor"); + } + openlog(buf, LOG_PID | LOG_NDELAY, LOGFACILITY); + } + + lf = tor_malloc_zero(sizeof(logfile_t)); + lf->fd = -1; + lf->severities = tor_memdup(severity, sizeof(log_severity_list_t)); + lf->filename = tor_strdup("<syslog>"); + lf->is_syslog = 1; + + LOCK_LOGS(); + lf->next = logfiles; + logfiles = lf; + log_global_min_severity_ = get_min_log_level(); + UNLOCK_LOGS(); + return 0; +} +#endif /* defined(HAVE_SYSLOG_H) */ + +#ifdef HAVE_ANDROID_LOG_H +/** + * Add a log handler to send messages to the Android platform log facility. + */ +int +add_android_log(const log_severity_list_t *severity, + const char *android_tag) +{ + logfile_t *lf = NULL; + + lf = tor_malloc_zero(sizeof(logfile_t)); + lf->fd = -1; + lf->severities = tor_memdup(severity, sizeof(log_severity_list_t)); + lf->filename = tor_strdup("<android>"); + lf->is_android = 1; + + if (android_tag == NULL) + lf->android_tag = tor_strdup("Tor"); + else { + char buf[256]; + tor_snprintf(buf, sizeof(buf), "Tor-%s", android_tag); + lf->android_tag = tor_strdup(buf); + } + + LOCK_LOGS(); + lf->next = logfiles; + logfiles = lf; + log_global_min_severity_ = get_min_log_level(); + UNLOCK_LOGS(); + return 0; +} +#endif // HAVE_ANDROID_LOG_H. + +/** If <b>level</b> is a valid log severity, return the corresponding + * numeric value. Otherwise, return -1. */ +int +parse_log_level(const char *level) +{ + if (!strcasecmp(level, "err")) + return LOG_ERR; + if (!strcasecmp(level, "warn")) + return LOG_WARN; + if (!strcasecmp(level, "notice")) + return LOG_NOTICE; + if (!strcasecmp(level, "info")) + return LOG_INFO; + if (!strcasecmp(level, "debug")) + return LOG_DEBUG; + return -1; +} + +/** Return the string equivalent of a given log level. */ +const char * +log_level_to_string(int level) +{ + return sev_to_string(level); +} + +/** NULL-terminated array of names for log domains such that domain_list[dom] + * is a description of <b>dom</b>. + * + * Remember to update doc/tor.1.txt if you modify this list. + * */ +static const char *domain_list[] = { + "GENERAL", "CRYPTO", "NET", "CONFIG", "FS", "PROTOCOL", "MM", + "HTTP", "APP", "CONTROL", "CIRC", "REND", "BUG", "DIR", "DIRSERV", + "OR", "EDGE", "ACCT", "HIST", "HANDSHAKE", "HEARTBEAT", "CHANNEL", + "SCHED", "GUARD", "CONSDIFF", "DOS", NULL +}; + +/** Return a bitmask for the log domain for which <b>domain</b> is the name, + * or 0 if there is no such name. */ +static log_domain_mask_t +parse_log_domain(const char *domain) +{ + int i; + for (i=0; domain_list[i]; ++i) { + if (!strcasecmp(domain, domain_list[i])) + return (1u<<i); + } + return 0; +} + +/** Translate a bitmask of log domains to a string. */ +static char * +domain_to_string(log_domain_mask_t domain, char *buf, size_t buflen) +{ + char *cp = buf; + char *eos = buf+buflen; + + buf[0] = '\0'; + if (! domain) + return buf; + while (1) { + const char *d; + int bit = tor_log2(domain); + size_t n; + if ((unsigned)bit >= ARRAY_LENGTH(domain_list)-1 || + bit >= N_LOGGING_DOMAINS) { + tor_snprintf(buf, buflen, "<BUG:Unknown domain %lx>", (long)domain); + return buf+strlen(buf); + } + d = domain_list[bit]; + n = strlcpy(cp, d, eos-cp); + if (n >= buflen) { + tor_snprintf(buf, buflen, "<BUG:Truncating domain %lx>", (long)domain); + return buf+strlen(buf); + } + cp += n; + domain &= ~(1<<bit); + + if (domain == 0 || (eos-cp) < 2) + return cp; + + memcpy(cp, ",", 2); /*Nul-terminated ,"*/ + cp++; + } +} + +/** Parse a log severity pattern in *<b>cfg_ptr</b>. Advance cfg_ptr after + * the end of the severityPattern. Set the value of <b>severity_out</b> to + * the parsed pattern. Return 0 on success, -1 on failure. + * + * The syntax for a SeverityPattern is: + * <pre> + * SeverityPattern = *(DomainSeverity SP)* DomainSeverity + * DomainSeverity = (DomainList SP)? SeverityRange + * SeverityRange = MinSeverity ("-" MaxSeverity )? + * DomainList = "[" (SP? DomainSpec SP? ",") SP? DomainSpec "]" + * DomainSpec = "*" | Domain | "~" Domain + * </pre> + * A missing MaxSeverity defaults to ERR. Severities and domains are + * case-insensitive. "~" indicates negation for a domain; negation happens + * last inside a DomainList. Only one SeverityRange without a DomainList is + * allowed per line. + */ +int +parse_log_severity_config(const char **cfg_ptr, + log_severity_list_t *severity_out) +{ + const char *cfg = *cfg_ptr; + int got_anything = 0; + int got_an_unqualified_range = 0; + memset(severity_out, 0, sizeof(*severity_out)); + + cfg = eat_whitespace(cfg); + while (*cfg) { + const char *dash, *space; + char *sev_lo, *sev_hi; + int low, high, i; + log_domain_mask_t domains = ~0u; + + if (*cfg == '[') { + int err = 0; + char *domains_str; + smartlist_t *domains_list; + log_domain_mask_t neg_domains = 0; + const char *closebracket = strchr(cfg, ']'); + if (!closebracket) + return -1; + domains = 0; + domains_str = tor_strndup(cfg+1, closebracket-cfg-1); + domains_list = smartlist_new(); + smartlist_split_string(domains_list, domains_str, ",", SPLIT_SKIP_SPACE, + -1); + tor_free(domains_str); + SMARTLIST_FOREACH_BEGIN(domains_list, const char *, domain) { + if (!strcmp(domain, "*")) { + domains = ~0u; + } else { + int d; + int negate=0; + if (*domain == '~') { + negate = 1; + ++domain; + } + d = parse_log_domain(domain); + if (!d) { + log_warn(LD_CONFIG, "No such logging domain as %s", domain); + err = 1; + } else { + if (negate) + neg_domains |= d; + else + domains |= d; + } + } + } SMARTLIST_FOREACH_END(domain); + SMARTLIST_FOREACH(domains_list, char *, d, tor_free(d)); + smartlist_free(domains_list); + if (err) + return -1; + if (domains == 0 && neg_domains) + domains = ~neg_domains; + else + domains &= ~neg_domains; + cfg = eat_whitespace(closebracket+1); + } else { + ++got_an_unqualified_range; + } + if (!strcasecmpstart(cfg, "file") || + !strcasecmpstart(cfg, "stderr") || + !strcasecmpstart(cfg, "stdout") || + !strcasecmpstart(cfg, "syslog") || + !strcasecmpstart(cfg, "android")) { + goto done; + } + if (got_an_unqualified_range > 1) + return -1; + + space = find_whitespace(cfg); + dash = strchr(cfg, '-'); + if (dash && dash < space) { + sev_lo = tor_strndup(cfg, dash-cfg); + sev_hi = tor_strndup(dash+1, space-(dash+1)); + } else { + sev_lo = tor_strndup(cfg, space-cfg); + sev_hi = tor_strdup("ERR"); + } + low = parse_log_level(sev_lo); + high = parse_log_level(sev_hi); + tor_free(sev_lo); + tor_free(sev_hi); + if (low == -1) + return -1; + if (high == -1) + return -1; + + got_anything = 1; + for (i=low; i >= high; --i) + severity_out->masks[SEVERITY_MASK_IDX(i)] |= domains; + + cfg = eat_whitespace(space); + } + + done: + *cfg_ptr = cfg; + return got_anything ? 0 : -1; +} + +/** Return the least severe log level that any current log is interested in. */ +int +get_min_log_level(void) +{ + logfile_t *lf; + int i; + int min = LOG_ERR; + for (lf = logfiles; lf; lf = lf->next) { + for (i = LOG_DEBUG; i > min; --i) + if (lf->severities->masks[SEVERITY_MASK_IDX(i)]) + min = i; + } + return min; +} + +/** Switch all logs to output at most verbose level. */ +void +switch_logs_debug(void) +{ + logfile_t *lf; + int i; + LOCK_LOGS(); + for (lf = logfiles; lf; lf=lf->next) { + for (i = LOG_DEBUG; i >= LOG_ERR; --i) + lf->severities->masks[SEVERITY_MASK_IDX(i)] = ~0u; + } + log_global_min_severity_ = get_min_log_level(); + UNLOCK_LOGS(); +} + +/** Truncate all the log files. */ +void +truncate_logs(void) +{ + logfile_t *lf; + for (lf = logfiles; lf; lf = lf->next) { + if (lf->fd >= 0) { + tor_ftruncate(lf->fd); + } + } +} diff --git a/src/lib/log/log.h b/src/lib/log/log.h new file mode 100644 index 0000000000..d7a5070610 --- /dev/null +++ b/src/lib/log/log.h @@ -0,0 +1,276 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file log.h + * + * \brief Headers for log.c + **/ + +#ifndef TOR_TORLOG_H + +#include <stdarg.h> +#include "lib/cc/torint.h" +#include "lib/cc/compat_compiler.h" +#include "lib/testsupport/testsupport.h" + +#ifdef HAVE_SYSLOG_H +#include <syslog.h> +#define LOG_WARN LOG_WARNING +#if LOG_DEBUG < LOG_ERR +#error "Your syslog.h thinks high numbers are more important. " \ + "We aren't prepared to deal with that." +#endif +#else /* !(defined(HAVE_SYSLOG_H)) */ +/* Note: Syslog's logging code refers to priorities, with 0 being the most + * important. Thus, all our comparisons needed to be reversed when we added + * syslog support. + * + * The upshot of this is that comments about log levels may be messed up: for + * "maximum severity" read "most severe" and "numerically *lowest* severity". + */ + +/** Debug-level severity: for hyper-verbose messages of no interest to + * anybody but developers. */ +#define LOG_DEBUG 7 +/** Info-level severity: for messages that appear frequently during normal + * operation. */ +#define LOG_INFO 6 +/** Notice-level severity: for messages that appear infrequently + * during normal operation; that the user will probably care about; + * and that are not errors. + */ +#define LOG_NOTICE 5 +/** Warn-level severity: for messages that only appear when something has gone + * wrong. */ +#define LOG_WARN 4 +/** Error-level severity: for messages that only appear when something has gone + * very wrong, and the Tor process can no longer proceed. */ +#define LOG_ERR 3 +#endif /* defined(HAVE_SYSLOG_H) */ + +/* Logging domains */ + +/** Catch-all for miscellaneous events and fatal errors. */ +#define LD_GENERAL (1u<<0) +/** The cryptography subsystem. */ +#define LD_CRYPTO (1u<<1) +/** Networking. */ +#define LD_NET (1u<<2) +/** Parsing and acting on our configuration. */ +#define LD_CONFIG (1u<<3) +/** Reading and writing from the filesystem. */ +#define LD_FS (1u<<4) +/** Other servers' (non)compliance with the Tor protocol. */ +#define LD_PROTOCOL (1u<<5) +/** Memory management. */ +#define LD_MM (1u<<6) +/** HTTP implementation. */ +#define LD_HTTP (1u<<7) +/** Application (socks) requests. */ +#define LD_APP (1u<<8) +/** Communication via the controller protocol. */ +#define LD_CONTROL (1u<<9) +/** Building, using, and managing circuits. */ +#define LD_CIRC (1u<<10) +/** Hidden services. */ +#define LD_REND (1u<<11) +/** Internal errors in this Tor process. */ +#define LD_BUG (1u<<12) +/** Learning and using information about Tor servers. */ +#define LD_DIR (1u<<13) +/** Learning and using information about Tor servers. */ +#define LD_DIRSERV (1u<<14) +/** Onion routing protocol. */ +#define LD_OR (1u<<15) +/** Generic edge-connection functionality. */ +#define LD_EDGE (1u<<16) +#define LD_EXIT LD_EDGE +/** Bandwidth accounting. */ +#define LD_ACCT (1u<<17) +/** Router history */ +#define LD_HIST (1u<<18) +/** OR handshaking */ +#define LD_HANDSHAKE (1u<<19) +/** Heartbeat messages */ +#define LD_HEARTBEAT (1u<<20) +/** Abstract channel_t code */ +#define LD_CHANNEL (1u<<21) +/** Scheduler */ +#define LD_SCHED (1u<<22) +/** Guard nodes */ +#define LD_GUARD (1u<<23) +/** Generation and application of consensus diffs. */ +#define LD_CONSDIFF (1u<<24) +/** Denial of Service mitigation. */ +#define LD_DOS (1u<<25) +/** Number of logging domains in the code. */ +#define N_LOGGING_DOMAINS 26 + +/** This log message is not safe to send to a callback-based logger + * immediately. Used as a flag, not a log domain. */ +#define LD_NOCB (1u<<31) +/** This log message should not include a function name, even if it otherwise + * would. Used as a flag, not a log domain. */ +#define LD_NOFUNCNAME (1u<<30) + +#ifdef TOR_UNIT_TESTS +/** This log message should not be intercepted by mock_saving_logv */ +#define LD_NO_MOCK (1u<<29) +#endif + +/** Mask of zero or more log domains, OR'd together. */ +typedef uint32_t log_domain_mask_t; + +/** Configures which severities are logged for each logging domain for a given + * log target. */ +typedef struct log_severity_list_t { + /** For each log severity, a bitmask of which domains a given logger is + * logging. */ + log_domain_mask_t masks[LOG_DEBUG-LOG_ERR+1]; +} log_severity_list_t; + +/** Callback type used for add_callback_log. */ +typedef void (*log_callback)(int severity, uint32_t domain, const char *msg); + +void init_logging(int disable_startup_queue); +int parse_log_level(const char *level); +const char *log_level_to_string(int level); +int parse_log_severity_config(const char **cfg, + log_severity_list_t *severity_out); +void set_log_severity_config(int minSeverity, int maxSeverity, + log_severity_list_t *severity_out); +void add_stream_log(const log_severity_list_t *severity, const char *name, + int fd); +int add_file_log(const log_severity_list_t *severity, + const char *filename, + int fd); + +#ifdef HAVE_SYSLOG_H +int add_syslog_log(const log_severity_list_t *severity, + const char* syslog_identity_tag); +#endif // HAVE_SYSLOG_H. +#ifdef HAVE_ANDROID_LOG_H +int add_android_log(const log_severity_list_t *severity, + const char *android_identity_tag); +#endif // HAVE_ANDROID_LOG_H. +int add_callback_log(const log_severity_list_t *severity, log_callback cb); +typedef void (*pending_callback_callback)(void); +void logs_set_pending_callback_callback(pending_callback_callback cb); +void logs_set_domain_logging(int enabled); +int get_min_log_level(void); +void switch_logs_debug(void); +void logs_free_all(void); +void add_temp_log(int min_severity); +void close_temp_logs(void); +void rollback_log_changes(void); +void mark_logs_temp(void); +void change_callback_log_severity(int loglevelMin, int loglevelMax, + log_callback cb); +void flush_pending_log_callbacks(void); +void flush_log_messages_from_startup(void); +void log_set_application_name(const char *name); +void set_log_time_granularity(int granularity_msec); +void truncate_logs(void); + +void tor_log(int severity, log_domain_mask_t domain, const char *format, ...) + CHECK_PRINTF(3,4); + +void tor_log_update_sigsafe_err_fds(void); + +struct smartlist_t; +void tor_log_get_logfile_names(struct smartlist_t *out); + +extern int log_global_min_severity_; + +void log_fn_(int severity, log_domain_mask_t domain, + const char *funcname, const char *format, ...) + CHECK_PRINTF(4,5); +struct ratelim_t; +void log_fn_ratelim_(struct ratelim_t *ratelim, int severity, + log_domain_mask_t domain, const char *funcname, + const char *format, ...) + CHECK_PRINTF(5,6); + +int log_message_is_interesting(int severity, log_domain_mask_t domain); +void tor_log_string(int severity, log_domain_mask_t domain, + const char *function, const char *string); + +#if defined(__GNUC__) && __GNUC__ <= 3 + +/* These are the GCC varidaic macros, so that older versions of GCC don't + * break. */ + +/** Log a message at level <b>severity</b>, using a pretty-printed version + * of the current function name. */ +#define log_fn(severity, domain, args...) \ + log_fn_(severity, domain, __FUNCTION__, args) +/** As log_fn, but use <b>ratelim</b> (an instance of ratelim_t) to control + * the frequency at which messages can appear. + */ +#define log_fn_ratelim(ratelim, severity, domain, args...) \ + log_fn_ratelim_(ratelim, severity, domain, __FUNCTION__, args) +#define log_debug(domain, args...) \ + STMT_BEGIN \ + if (PREDICT_UNLIKELY(log_global_min_severity_ == LOG_DEBUG)) \ + log_fn_(LOG_DEBUG, domain, __FUNCTION__, args); \ + STMT_END +#define log_info(domain, args...) \ + log_fn_(LOG_INFO, domain, __FUNCTION__, args) +#define log_notice(domain, args...) \ + log_fn_(LOG_NOTICE, domain, __FUNCTION__, args) +#define log_warn(domain, args...) \ + log_fn_(LOG_WARN, domain, __FUNCTION__, args) +#define log_err(domain, args...) \ + log_fn_(LOG_ERR, domain, __FUNCTION__, args) + +#else /* !(defined(__GNUC__) && __GNUC__ <= 3) */ + +/* Here are the c99 variadic macros, to work with non-GCC compilers */ + +#define log_debug(domain, args, ...) \ + STMT_BEGIN \ + if (PREDICT_UNLIKELY(log_global_min_severity_ == LOG_DEBUG)) \ + log_fn_(LOG_DEBUG, domain, __FUNCTION__, args, ##__VA_ARGS__); \ + STMT_END +#define log_info(domain, args,...) \ + log_fn_(LOG_INFO, domain, __FUNCTION__, args, ##__VA_ARGS__) +#define log_notice(domain, args,...) \ + log_fn_(LOG_NOTICE, domain, __FUNCTION__, args, ##__VA_ARGS__) +#define log_warn(domain, args,...) \ + log_fn_(LOG_WARN, domain, __FUNCTION__, args, ##__VA_ARGS__) +#define log_err(domain, args,...) \ + log_fn_(LOG_ERR, domain, __FUNCTION__, args, ##__VA_ARGS__) +/** Log a message at level <b>severity</b>, using a pretty-printed version + * of the current function name. */ +#define log_fn(severity, domain, args,...) \ + log_fn_(severity, domain, __FUNCTION__, args, ##__VA_ARGS__) +/** As log_fn, but use <b>ratelim</b> (an instance of ratelim_t) to control + * the frequency at which messages can appear. + */ +#define log_fn_ratelim(ratelim, severity, domain, args,...) \ + log_fn_ratelim_(ratelim, severity, domain, __FUNCTION__, \ + args, ##__VA_ARGS__) +#endif /* defined(__GNUC__) && __GNUC__ <= 3 */ + +/** This defines log levels that are linked in the Rust log module, rather + * than re-defining these in both Rust and C. + * + * C_RUST_COUPLED src/rust/tor_log LogSeverity, LogDomain + */ +extern const int LOG_WARN_; +extern const int LOG_NOTICE_; +extern const log_domain_mask_t LD_NET_; +extern const log_domain_mask_t LD_GENERAL_; + +#ifdef LOG_PRIVATE +MOCK_DECL(STATIC void, logv, (int severity, log_domain_mask_t domain, + const char *funcname, const char *suffix, const char *format, + va_list ap) CHECK_PRINTF(5,0)); +#endif + +# define TOR_TORLOG_H +#endif /* !defined(TOR_TORLOG_H) */ diff --git a/src/lib/log/ratelim.c b/src/lib/log/ratelim.c new file mode 100644 index 0000000000..5eec742aa7 --- /dev/null +++ b/src/lib/log/ratelim.c @@ -0,0 +1,60 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file ratelim.c + * \brief Summarize similar messages that would otherwise flood the logs. + **/ + +#include "lib/log/ratelim.h" +#include "lib/malloc/malloc.h" +#include "lib/string/printf.h" + +/** If the rate-limiter <b>lim</b> is ready at <b>now</b>, return the number + * of calls to rate_limit_is_ready (including this one!) since the last time + * rate_limit_is_ready returned nonzero. Otherwise return 0. + * If the call number hits <b>RATELIM_TOOMANY</b> limit, drop a warning + * about this event and stop counting. */ +static int +rate_limit_is_ready(ratelim_t *lim, time_t now) +{ + if (lim->rate + lim->last_allowed <= now) { + int res = lim->n_calls_since_last_time + 1; + lim->last_allowed = now; + lim->n_calls_since_last_time = 0; + return res; + } else { + if (lim->n_calls_since_last_time <= RATELIM_TOOMANY) { + ++lim->n_calls_since_last_time; + } + + return 0; + } +} + +/** If the rate-limiter <b>lim</b> is ready at <b>now</b>, return a newly + * allocated string indicating how many messages were suppressed, suitable to + * append to a log message. Otherwise return NULL. */ +char * +rate_limit_log(ratelim_t *lim, time_t now) +{ + int n; + if ((n = rate_limit_is_ready(lim, now))) { + if (n == 1) { + return tor_strdup(""); + } else { + char *cp=NULL; + const char *opt_over = (n >= RATELIM_TOOMANY) ? "over " : ""; + /* XXXX this is not exactly correct: the messages could have occurred + * any time between the old value of lim->allowed and now. */ + tor_asprintf(&cp, + " [%s%d similar message(s) suppressed in last %d seconds]", + opt_over, n-1, lim->rate); + return cp; + } + } else { + return NULL; + } +} diff --git a/src/lib/log/ratelim.h b/src/lib/log/ratelim.h new file mode 100644 index 0000000000..48edd7c849 --- /dev/null +++ b/src/lib/log/ratelim.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file ratelim.h + * \brief Summarize similar messages that would otherwise flood the logs. + **/ + +#ifndef TOR_RATELIM_H +#define TOR_RATELIM_H + +#include <time.h> + +/* Rate-limiter */ + +/** A ratelim_t remembers how often an event is occurring, and how often + * it's allowed to occur. Typical usage is something like: + * + <pre> + if (possibly_very_frequent_event()) { + const int INTERVAL = 300; + static ratelim_t warning_limit = RATELIM_INIT(INTERVAL); + char *m; + if ((m = rate_limit_log(&warning_limit, approx_time()))) { + log_warn(LD_GENERAL, "The event occurred!%s", m); + tor_free(m); + } + } + </pre> + + As a convenience wrapper for logging, you can replace the above with: + <pre> + if (possibly_very_frequent_event()) { + static ratelim_t warning_limit = RATELIM_INIT(300); + log_fn_ratelim(&warning_limit, LOG_WARN, LD_GENERAL, + "The event occurred!"); + } + </pre> + */ +typedef struct ratelim_t { + int rate; + time_t last_allowed; + int n_calls_since_last_time; +} ratelim_t; + +#define RATELIM_INIT(r) { (r), 0, 0 } +#define RATELIM_TOOMANY (16*1000*1000) + +char *rate_limit_log(ratelim_t *lim, time_t now); + +#endif diff --git a/src/lib/log/util_bug.c b/src/lib/log/util_bug.c new file mode 100644 index 0000000000..f42d2d2ab4 --- /dev/null +++ b/src/lib/log/util_bug.c @@ -0,0 +1,147 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file util_bug.c + **/ + +#include "orconfig.h" +#include "lib/log/util_bug.h" +#include "lib/log/log.h" +#include "lib/err/backtrace.h" +#ifdef TOR_UNIT_TESTS +#include "lib/smartlist_core/smartlist_core.h" +#include "lib/smartlist_core/smartlist_foreach.h" +#endif +#include "lib/malloc/malloc.h" +#include "lib/string/printf.h" + +#include <string.h> + +#ifdef TOR_UNIT_TESTS +static void (*failed_assertion_cb)(void) = NULL; +static int n_bugs_to_capture = 0; +static smartlist_t *bug_messages = NULL; +#define capturing_bugs() (bug_messages != NULL && n_bugs_to_capture) +void +tor_capture_bugs_(int n) +{ + tor_end_capture_bugs_(); + bug_messages = smartlist_new(); + n_bugs_to_capture = n; +} +void +tor_end_capture_bugs_(void) +{ + n_bugs_to_capture = 0; + if (!bug_messages) + return; + SMARTLIST_FOREACH(bug_messages, char *, cp, tor_free(cp)); + smartlist_free(bug_messages); + bug_messages = NULL; +} +const smartlist_t * +tor_get_captured_bug_log_(void) +{ + return bug_messages; +} +static void +add_captured_bug(const char *s) +{ + --n_bugs_to_capture; + smartlist_add_strdup(bug_messages, s); +} +/** Set a callback to be invoked when we get any tor_bug_occurred_ + * invocation. We use this in the unit tests so that a nonfatal + * assertion failure can also count as a test failure. + */ +void +tor_set_failed_assertion_callback(void (*fn)(void)) +{ + failed_assertion_cb = fn; +} +#else /* !(defined(TOR_UNIT_TESTS)) */ +#define capturing_bugs() (0) +#define add_captured_bug(s) do { } while (0) +#endif /* defined(TOR_UNIT_TESTS) */ + +/** Helper for tor_assert: report the assertion failure. */ +void +tor_assertion_failed_(const char *fname, unsigned int line, + const char *func, const char *expr) +{ + char buf[256]; + log_err(LD_BUG, "%s:%u: %s: Assertion %s failed; aborting.", + fname, line, func, expr); + tor_snprintf(buf, sizeof(buf), + "Assertion %s failed in %s at %s:%u", + expr, func, fname, line); + log_backtrace(LOG_ERR, LD_BUG, buf); +} + +/** Helper for tor_assert_nonfatal: report the assertion failure. */ +void +tor_bug_occurred_(const char *fname, unsigned int line, + const char *func, const char *expr, + int once) +{ + char buf[256]; + const char *once_str = once ? + " (Future instances of this warning will be silenced.)": ""; + if (! expr) { + if (capturing_bugs()) { + add_captured_bug("This line should not have been reached."); + return; + } + log_warn(LD_BUG, "%s:%u: %s: This line should not have been reached.%s", + fname, line, func, once_str); + tor_snprintf(buf, sizeof(buf), + "Line unexpectedly reached at %s at %s:%u", + func, fname, line); + } else { + if (capturing_bugs()) { + add_captured_bug(expr); + return; + } + log_warn(LD_BUG, "%s:%u: %s: Non-fatal assertion %s failed.%s", + fname, line, func, expr, once_str); + tor_snprintf(buf, sizeof(buf), + "Non-fatal assertion %s failed in %s at %s:%u", + expr, func, fname, line); + } + log_backtrace(LOG_WARN, LD_BUG, buf); + +#ifdef TOR_UNIT_TESTS + if (failed_assertion_cb) { + failed_assertion_cb(); + } +#endif +} + +#ifdef _WIN32 +/** Take a filename and return a pointer to its final element. This + * function is called on __FILE__ to fix a MSVC nit where __FILE__ + * contains the full path to the file. This is bad, because it + * confuses users to find the home directory of the person who + * compiled the binary in their warning messages. + */ +const char * +tor_fix_source_file(const char *fname) +{ + const char *cp1, *cp2, *r; + cp1 = strrchr(fname, '/'); + cp2 = strrchr(fname, '\\'); + if (cp1 && cp2) { + r = (cp1<cp2)?(cp2+1):(cp1+1); + } else if (cp1) { + r = cp1+1; + } else if (cp2) { + r = cp2+1; + } else { + r = fname; + } + return r; +} +#endif /* defined(_WIN32) */ diff --git a/src/lib/log/util_bug.h b/src/lib/log/util_bug.h new file mode 100644 index 0000000000..18d40bbf39 --- /dev/null +++ b/src/lib/log/util_bug.h @@ -0,0 +1,244 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file util_bug.h + * + * \brief Macros to manage assertions, fatal and non-fatal. + * + * Guidelines: All the different kinds of assertion in this file are for + * bug-checking only. Don't write code that can assert based on bad inputs. + * + * We provide two kinds of assertion here: "fatal" and "nonfatal". Use + * nonfatal assertions for any bug you can reasonably recover from -- and + * please, try to recover! Many severe bugs in Tor have been caused by using + * a regular assertion when a nonfatal assertion would have been better. + * + * If you need to check a condition with a nonfatal assertion, AND recover + * from that same condition, consider using the BUG() macro inside a + * conditional. For example: + * + * <code> + * // wrong -- use tor_assert_nonfatal() if you just want an assertion. + * BUG(ptr == NULL); + * + * // okay, but needlessly verbose + * tor_assert_nonfatal(ptr != NULL); + * if (ptr == NULL) { ... } + * + * // this is how we do it: + * if (BUG(ptr == NULL)) { ... } + * </code> + **/ + +#ifndef TOR_UTIL_BUG_H +#define TOR_UTIL_BUG_H + +#include "orconfig.h" +#include "lib/cc/compat_compiler.h" +#include "lib/log/log.h" +#include "lib/testsupport/testsupport.h" + +/* Replace assert() with a variant that sends failures to the log before + * calling assert() normally. + */ +#ifdef NDEBUG +/* Nobody should ever want to build with NDEBUG set. 99% of our asserts will + * be outside the critical path anyway, so it's silly to disable bug-checking + * throughout the entire program just because a few asserts are slowing you + * down. Profile, optimize the critical path, and keep debugging on. + * + * And I'm not just saying that because some of our asserts check + * security-critical properties. + */ +#error "Sorry; we don't support building with NDEBUG." +#endif /* defined(NDEBUG) */ + +#if defined(TOR_UNIT_TESTS) && defined(__GNUC__) +/* We define this GCC macro as a replacement for PREDICT_UNLIKELY() in this + * header, so that in our unit test builds, we'll get compiler warnings about + * stuff like tor_assert(n = 5). + * + * The key here is that (e) is wrapped in exactly one layer of parentheses, + * and then passed right to a conditional. If you do anything else to the + * expression here, or introduce any more parentheses, the compiler won't + * help you. + * + * We only do this for the unit-test build case because it interferes with + * the likely-branch labeling. Note below that in the other case, we define + * these macros to just be synonyms for PREDICT_(UN)LIKELY. + */ +#define ASSERT_PREDICT_UNLIKELY_(e) \ + ( { \ + int tor__assert_tmp_value__; \ + if (e) \ + tor__assert_tmp_value__ = 1; \ + else \ + tor__assert_tmp_value__ = 0; \ + tor__assert_tmp_value__; \ + } ) +#define ASSERT_PREDICT_LIKELY_(e) ASSERT_PREDICT_UNLIKELY_(e) +#else +#define ASSERT_PREDICT_UNLIKELY_(e) PREDICT_UNLIKELY(e) +#define ASSERT_PREDICT_LIKELY_(e) PREDICT_LIKELY(e) +#endif + +/* Sometimes we don't want to use assertions during branch coverage tests; it + * leads to tons of unreached branches which in reality are only assertions we + * didn't hit. */ +#if defined(TOR_UNIT_TESTS) && defined(DISABLE_ASSERTS_IN_UNIT_TESTS) +#define tor_assert(a) STMT_BEGIN \ + (void)(a); \ + STMT_END +#else +/** Like assert(3), but send assertion failures to the log as well as to + * stderr. */ +#define tor_assert(expr) STMT_BEGIN \ + if (ASSERT_PREDICT_LIKELY_(expr)) { \ + } else { \ + tor_assertion_failed_(SHORT_FILE__, __LINE__, __func__, #expr); \ + abort(); \ + } STMT_END +#endif /* defined(TOR_UNIT_TESTS) && defined(DISABLE_ASSERTS_IN_UNIT_TESTS) */ + +#define tor_assert_unreached() \ + STMT_BEGIN { \ + tor_assertion_failed_(SHORT_FILE__, __LINE__, __func__, \ + "line should be unreached"); \ + abort(); \ + } STMT_END + +/* Non-fatal bug assertions. The "unreached" variants mean "this line should + * never be reached." The "once" variants mean "Don't log a warning more than + * once". + * + * The 'BUG' macro checks a boolean condition and logs an error message if it + * is true. Example usage: + * if (BUG(x == NULL)) + * return -1; + */ + +#ifdef __COVERITY__ +#undef BUG +// Coverity defines this in global headers; let's override it. This is a +// magic coverity-only preprocessor thing. +#nodef BUG(x) (x) +#endif /* defined(__COVERITY__) */ + +#if defined(__COVERITY__) || defined(__clang_analyzer__) +// We're running with a static analysis tool: let's treat even nonfatal +// assertion failures as something that we need to avoid. +#define ALL_BUGS_ARE_FATAL +#endif + +#ifdef ALL_BUGS_ARE_FATAL +#define tor_assert_nonfatal_unreached() tor_assert(0) +#define tor_assert_nonfatal(cond) tor_assert((cond)) +#define tor_assert_nonfatal_unreached_once() tor_assert(0) +#define tor_assert_nonfatal_once(cond) tor_assert((cond)) +#define BUG(cond) \ + (ASSERT_PREDICT_UNLIKELY_(cond) ? \ + (tor_assertion_failed_(SHORT_FILE__,__LINE__,__func__,"!("#cond")"), \ + abort(), 1) \ + : 0) +#elif defined(TOR_UNIT_TESTS) && defined(DISABLE_ASSERTS_IN_UNIT_TESTS) +#define tor_assert_nonfatal_unreached() STMT_NIL +#define tor_assert_nonfatal(cond) ((void)(cond)) +#define tor_assert_nonfatal_unreached_once() STMT_NIL +#define tor_assert_nonfatal_once(cond) ((void)(cond)) +#define BUG(cond) (ASSERT_PREDICT_UNLIKELY_(cond) ? 1 : 0) +#else /* Normal case, !ALL_BUGS_ARE_FATAL, !DISABLE_ASSERTS_IN_UNIT_TESTS */ +#define tor_assert_nonfatal_unreached() STMT_BEGIN \ + tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, NULL, 0); \ + STMT_END +#define tor_assert_nonfatal(cond) STMT_BEGIN \ + if (ASSERT_PREDICT_LIKELY_(cond)) { \ + } else { \ + tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, #cond, 0); \ + } \ + STMT_END +#define tor_assert_nonfatal_unreached_once() STMT_BEGIN \ + static int warning_logged__ = 0; \ + if (!warning_logged__) { \ + warning_logged__ = 1; \ + tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, NULL, 1); \ + } \ + STMT_END +#define tor_assert_nonfatal_once(cond) STMT_BEGIN \ + static int warning_logged__ = 0; \ + if (ASSERT_PREDICT_LIKELY_(cond)) { \ + } else if (!warning_logged__) { \ + warning_logged__ = 1; \ + tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, #cond, 1); \ + } \ + STMT_END +#define BUG(cond) \ + (ASSERT_PREDICT_UNLIKELY_(cond) ? \ + (tor_bug_occurred_(SHORT_FILE__,__LINE__,__func__,"!("#cond")",0), 1) \ + : 0) +#endif /* defined(ALL_BUGS_ARE_FATAL) || ... */ + +#ifdef __GNUC__ +#define IF_BUG_ONCE__(cond,var) \ + if (( { \ + static int var = 0; \ + int bool_result = !!(cond); \ + if (bool_result && !var) { \ + var = 1; \ + tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, \ + "!("#cond")", 1); \ + } \ + bool_result; } )) +#else /* !(defined(__GNUC__)) */ +#define IF_BUG_ONCE__(cond,var) \ + static int var = 0; \ + if ((cond) ? \ + (var ? 1 : \ + (var=1, \ + tor_bug_occurred_(SHORT_FILE__, __LINE__, __func__, \ + "!("#cond")", 1), \ + 1)) \ + : 0) +#endif /* defined(__GNUC__) */ +#define IF_BUG_ONCE_VARNAME_(a) \ + warning_logged_on_ ## a ## __ +#define IF_BUG_ONCE_VARNAME__(a) \ + IF_BUG_ONCE_VARNAME_(a) + +/** This macro behaves as 'if (bug(x))', except that it only logs its + * warning once, no matter how many times it triggers. + */ + +#define IF_BUG_ONCE(cond) \ + IF_BUG_ONCE__(ASSERT_PREDICT_UNLIKELY_(cond), \ + IF_BUG_ONCE_VARNAME__(__LINE__)) + +/** Define this if you want Tor to crash when any problem comes up, + * so you can get a coredump and track things down. */ +// #define tor_fragile_assert() tor_assert_unreached(0) +#define tor_fragile_assert() tor_assert_nonfatal_unreached_once() + +void tor_assertion_failed_(const char *fname, unsigned int line, + const char *func, const char *expr); +void tor_bug_occurred_(const char *fname, unsigned int line, + const char *func, const char *expr, + int once); + +#ifdef _WIN32 +#define SHORT_FILE__ (tor_fix_source_file(__FILE__)) +const char *tor_fix_source_file(const char *fname); +#else +#define SHORT_FILE__ (__FILE__) +#define tor_fix_source_file(s) (s) +#endif /* defined(_WIN32) */ + +#ifdef TOR_UNIT_TESTS +void tor_capture_bugs_(int n); +void tor_end_capture_bugs_(void); +const struct smartlist_t *tor_get_captured_bug_log_(void); +void tor_set_failed_assertion_callback(void (*fn)(void)); +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* !defined(TOR_UTIL_BUG_H) */ diff --git a/src/lib/log/win32err.c b/src/lib/log/win32err.c new file mode 100644 index 0000000000..dc45cb4c3d --- /dev/null +++ b/src/lib/log/win32err.c @@ -0,0 +1,61 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file win32err.c + * \brief Convert windows error codes to useful C strings. + **/ + +#ifdef _WIN32 +#include "orconfig.h" +#include "lib/log/win32err.h" +#include "lib/malloc/malloc.h" + +#include <tchar.h> +#include <windows.h> + +/** Return a newly allocated string describing the windows system error code + * <b>err</b>. Note that error codes are different from errno. Error codes + * come from GetLastError() when a winapi call fails. errno is set only when + * ANSI functions fail. Whee. */ +char * +format_win32_error(DWORD err) +{ + TCHAR *str = NULL; + char *result; + DWORD n; + + /* Somebody once decided that this interface was better than strerror(). */ + n = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, + MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), + (LPVOID)&str, + 0, NULL); + + if (str && n) { +#ifdef UNICODE + size_t len; + if (n > 128*1024) + len = (128 * 1024) * 2 + 1; /* This shouldn't be possible, but let's + * make sure. */ + else + len = n * 2 + 1; + result = tor_malloc(len); + wcstombs(result,str,len); + result[len-1] = '\0'; +#else /* !(defined(UNICODE)) */ + result = tor_strdup(str); +#endif /* defined(UNICODE) */ + } else { + result = tor_strdup("<unformattable error>"); + } + if (str) { + LocalFree(str); /* LocalFree != free() */ + } + return result; +} +#endif /* defined(_WIN32) */ diff --git a/src/lib/log/win32err.h b/src/lib/log/win32err.h new file mode 100644 index 0000000000..33413dfd15 --- /dev/null +++ b/src/lib/log/win32err.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file win32err.h + * \brief Header for win32err.c + **/ + +#ifndef TOR_WIN32ERR_H +#define TOR_WIN32ERR_H + +#include "orconfig.h" + +/* Platform-specific helpers. */ +#ifdef _WIN32 +#include <windef.h> +char *format_win32_error(DWORD err); +#endif + +#endif diff --git a/src/lib/malloc/.may_include b/src/lib/malloc/.may_include new file mode 100644 index 0000000000..cc62bb1013 --- /dev/null +++ b/src/lib/malloc/.may_include @@ -0,0 +1,6 @@ +orconfig.h + +lib/cc/*.h +lib/err/*.h +lib/malloc/*.h +lib/testsupport/testsupport.h diff --git a/src/lib/malloc/include.am b/src/lib/malloc/include.am new file mode 100644 index 0000000000..502cc1c6b7 --- /dev/null +++ b/src/lib/malloc/include.am @@ -0,0 +1,21 @@ + +noinst_LIBRARIES += src/lib/libtor-malloc.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-malloc-testing.a +endif + +src_lib_libtor_malloc_a_SOURCES = \ + src/lib/malloc/malloc.c + +if USE_OPENBSD_MALLOC +src_lib_libtor_malloc_a_SOURCES += src/ext/OpenBSD_malloc_Linux.c +endif + +src_lib_libtor_malloc_testing_a_SOURCES = \ + $(src_lib_libtor_malloc_a_SOURCES) +src_lib_libtor_malloc_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_malloc_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/malloc/malloc.h diff --git a/src/lib/malloc/malloc.c b/src/lib/malloc/malloc.c new file mode 100644 index 0000000000..8628acfc97 --- /dev/null +++ b/src/lib/malloc/malloc.c @@ -0,0 +1,230 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file malloc.c + * \brief Wrappers for C malloc code, and replacements for items that + * may be missing. + **/ + +#include "orconfig.h" + +#include <stdlib.h> +#include <string.h> + +#include "lib/testsupport/testsupport.h" +#define UTIL_MALLOC_PRIVATE +#include "lib/malloc/malloc.h" +#include "lib/cc/torint.h" +#include "lib/err/torerr.h" + +#ifdef __clang_analyzer__ +#undef MALLOC_ZERO_WORKS +#endif + +/** Allocate a chunk of <b>size</b> bytes of memory, and return a pointer to + * result. On error, log and terminate the process. (Same as malloc(size), + * but never returns NULL.) + */ +void * +tor_malloc_(size_t size) +{ + void *result; + + raw_assert(size < SIZE_T_CEILING); + +#ifndef MALLOC_ZERO_WORKS + /* Some libc mallocs don't work when size==0. Override them. */ + if (size==0) { + size=1; + } +#endif /* !defined(MALLOC_ZERO_WORKS) */ + + result = raw_malloc(size); + + if (PREDICT_UNLIKELY(result == NULL)) { + /* LCOV_EXCL_START */ + /* If these functions die within a worker process, they won't call + * spawn_exit, but that's ok, since the parent will run out of memory soon + * anyway. */ + raw_assert_unreached_msg("Out of memory on malloc(). Dying."); + /* LCOV_EXCL_STOP */ + } + return result; +} + +/** Allocate a chunk of <b>size</b> bytes of memory, fill the memory with + * zero bytes, and return a pointer to the result. Log and terminate + * the process on error. (Same as calloc(size,1), but never returns NULL.) + */ +void * +tor_malloc_zero_(size_t size) +{ + /* You may ask yourself, "wouldn't it be smart to use calloc instead of + * malloc+memset? Perhaps libc's calloc knows some nifty optimization trick + * we don't!" Indeed it does, but its optimizations are only a big win when + * we're allocating something very big (it knows if it just got the memory + * from the OS in a pre-zeroed state). We don't want to use tor_malloc_zero + * for big stuff, so we don't bother with calloc. */ + void *result = tor_malloc_(size); + memset(result, 0, size); + return result; +} + +/* The square root of SIZE_MAX + 1. If a is less than this, and b is less + * than this, then a*b is less than SIZE_MAX. (For example, if size_t is + * 32 bits, then SIZE_MAX is 0xffffffff and this value is 0x10000. If a and + * b are less than this, then their product is at most (65535*65535) == + * 0xfffe0001. */ +#define SQRT_SIZE_MAX_P1 (((size_t)1) << (sizeof(size_t)*4)) + +/** Return non-zero if and only if the product of the arguments is exact, + * and cannot overflow. */ +STATIC int +size_mul_check(const size_t x, const size_t y) +{ + /* This first check is equivalent to + (x < SQRT_SIZE_MAX_P1 && y < SQRT_SIZE_MAX_P1) + + Rationale: if either one of x or y is >= SQRT_SIZE_MAX_P1, then it + will have some bit set in its most significant half. + */ + return ((x|y) < SQRT_SIZE_MAX_P1 || + y == 0 || + x <= SIZE_MAX / y); +} + +/** Allocate a chunk of <b>nmemb</b>*<b>size</b> bytes of memory, fill + * the memory with zero bytes, and return a pointer to the result. + * Log and terminate the process on error. (Same as + * calloc(<b>nmemb</b>,<b>size</b>), but never returns NULL.) + * The second argument (<b>size</b>) should preferably be non-zero + * and a compile-time constant. + */ +void * +tor_calloc_(size_t nmemb, size_t size) +{ + raw_assert(size_mul_check(nmemb, size)); + return tor_malloc_zero_((nmemb * size)); +} + +/** Change the size of the memory block pointed to by <b>ptr</b> to <b>size</b> + * bytes long; return the new memory block. On error, log and + * terminate. (Like realloc(ptr,size), but never returns NULL.) + */ +void * +tor_realloc_(void *ptr, size_t size) +{ + void *result; + + raw_assert(size < SIZE_T_CEILING); + +#ifndef MALLOC_ZERO_WORKS + /* Some libc mallocs don't work when size==0. Override them. */ + if (size==0) { + size=1; + } +#endif /* !defined(MALLOC_ZERO_WORKS) */ + + result = raw_realloc(ptr, size); + + if (PREDICT_UNLIKELY(result == NULL)) { + /* LCOV_EXCL_START */ + raw_assert_unreached_msg("Out of memory on realloc(). Dying."); + /* LCOV_EXCL_STOP */ + } + return result; +} + +/** + * Try to realloc <b>ptr</b> so that it takes up sz1 * sz2 bytes. Check for + * overflow. Unlike other allocation functions, return NULL on overflow. + */ +void * +tor_reallocarray_(void *ptr, size_t sz1, size_t sz2) +{ + /* XXXX we can make this return 0, but we would need to check all the + * reallocarray users. */ + raw_assert(size_mul_check(sz1, sz2)); + + return tor_realloc(ptr, (sz1 * sz2)); +} + +/** Return a newly allocated copy of the NUL-terminated string s. On + * error, log and terminate. (Like strdup(s), but never returns + * NULL.) + */ +char * +tor_strdup_(const char *s) +{ + char *duplicate; + raw_assert(s); + + duplicate = raw_strdup(s); + + if (PREDICT_UNLIKELY(duplicate == NULL)) { + /* LCOV_EXCL_START */ + raw_assert_unreached_msg("Out of memory on strdup(). Dying."); + /* LCOV_EXCL_STOP */ + } + return duplicate; +} + +/** Allocate and return a new string containing the first <b>n</b> + * characters of <b>s</b>. If <b>s</b> is longer than <b>n</b> + * characters, only the first <b>n</b> are copied. The result is + * always NUL-terminated. (Like strndup(s,n), but never returns + * NULL.) + */ +char * +tor_strndup_(const char *s, size_t n) +{ + char *duplicate; + raw_assert(s); + raw_assert(n < SIZE_T_CEILING); + duplicate = tor_malloc_((n+1)); + /* Performance note: Ordinarily we prefer strlcpy to strncpy. But + * this function gets called a whole lot, and platform strncpy is + * much faster than strlcpy when strlen(s) is much longer than n. + */ + strncpy(duplicate, s, n); + duplicate[n]='\0'; + return duplicate; +} + +/** Allocate a chunk of <b>len</b> bytes, with the same contents as the + * <b>len</b> bytes starting at <b>mem</b>. */ +void * +tor_memdup_(const void *mem, size_t len) +{ + char *duplicate; + raw_assert(len < SIZE_T_CEILING); + raw_assert(mem); + duplicate = tor_malloc_(len); + memcpy(duplicate, mem, len); + return duplicate; +} + +/** As tor_memdup(), but add an extra 0 byte at the end of the resulting + * memory. */ +void * +tor_memdup_nulterm_(const void *mem, size_t len) +{ + char *duplicate; + raw_assert(len < SIZE_T_CEILING+1); + raw_assert(mem); + duplicate = tor_malloc_(len+1); + memcpy(duplicate, mem, len); + duplicate[len] = '\0'; + return duplicate; +} + +/** Helper for places that need to take a function pointer to the right + * spelling of "free()". */ +void +tor_free_(void *mem) +{ + tor_free(mem); +} diff --git a/src/lib/malloc/malloc.h b/src/lib/malloc/malloc.h new file mode 100644 index 0000000000..ef6b509ca4 --- /dev/null +++ b/src/lib/malloc/malloc.h @@ -0,0 +1,92 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file malloc.h + * \brief Headers for util_malloc.c + **/ + +#ifndef TOR_UTIL_MALLOC_H +#define TOR_UTIL_MALLOC_H + +#include <stddef.h> +#include <stdlib.h> +#include "lib/cc/compat_compiler.h" + +/* Memory management */ +void *tor_malloc_(size_t size) ATTR_MALLOC; +void *tor_malloc_zero_(size_t size) ATTR_MALLOC; +void *tor_calloc_(size_t nmemb, size_t size) ATTR_MALLOC; +void *tor_realloc_(void *ptr, size_t size); +void *tor_reallocarray_(void *ptr, size_t size1, size_t size2); +char *tor_strdup_(const char *s) ATTR_MALLOC; +char *tor_strndup_(const char *s, size_t n) + ATTR_MALLOC; +void *tor_memdup_(const void *mem, size_t len) + ATTR_MALLOC; +void *tor_memdup_nulterm_(const void *mem, size_t len) + ATTR_MALLOC; +void tor_free_(void *mem); + +/** Release memory allocated by tor_malloc, tor_realloc, tor_strdup, + * etc. Unlike the free() function, the tor_free() macro sets the + * pointer value to NULL after freeing it. + * + * This is a macro. If you need a function pointer to release memory from + * tor_malloc(), use tor_free_(). + * + * Note that this macro takes the address of the pointer it is going to + * free and clear. If that pointer is stored with a nonstandard + * alignment (eg because of a "packed" pragma) it is not correct to use + * tor_free(). + */ +#ifdef __GNUC__ +#define tor_free(p) STMT_BEGIN \ + typeof(&(p)) tor_free__tmpvar = &(p); \ + raw_free(*tor_free__tmpvar); \ + *tor_free__tmpvar=NULL; \ + STMT_END +#else +#define tor_free(p) STMT_BEGIN \ + raw_free(p); \ + (p)=NULL; \ + STMT_END +#endif + +#define tor_malloc(size) tor_malloc_(size) +#define tor_malloc_zero(size) tor_malloc_zero_(size) +#define tor_calloc(nmemb,size) tor_calloc_(nmemb, size) +#define tor_realloc(ptr, size) tor_realloc_(ptr, size) +#define tor_reallocarray(ptr, sz1, sz2) \ + tor_reallocarray_((ptr), (sz1), (sz2)) +#define tor_strdup(s) tor_strdup_(s) +#define tor_strndup(s, n) tor_strndup_(s, n) +#define tor_memdup(s, n) tor_memdup_(s, n) +#define tor_memdup_nulterm(s, n) tor_memdup_nulterm_(s, n) + +/* Aliases for the underlying system malloc/realloc/free. Only use + * them to indicate "I really want the underlying system function, I know + * what I'm doing." */ +#define raw_malloc malloc +#define raw_realloc realloc +#define raw_free free +#define raw_strdup strdup + +/* Helper macro: free a variable of type 'typename' using freefn, and + * set the variable to NULL. + */ +#define FREE_AND_NULL(typename, freefn, var) \ + do { \ + /* only evaluate (var) once. */ \ + typename **tmp__free__ptr ## freefn = &(var); \ + freefn(*tmp__free__ptr ## freefn); \ + (*tmp__free__ptr ## freefn) = NULL; \ + } while (0) + +#ifdef UTIL_MALLOC_PRIVATE +STATIC int size_mul_check(const size_t x, const size_t y); +#endif + +#endif /* !defined(TOR_UTIL_MALLOC_H) */ diff --git a/src/lib/math/.may_include b/src/lib/math/.may_include new file mode 100644 index 0000000000..1fd26864dc --- /dev/null +++ b/src/lib/math/.may_include @@ -0,0 +1,5 @@ +orconfig.h + +lib/cc/*.h +lib/log/*.h +lib/math/*.h diff --git a/src/lib/math/fp.c b/src/lib/math/fp.c new file mode 100644 index 0000000000..4419635dfe --- /dev/null +++ b/src/lib/math/fp.c @@ -0,0 +1,119 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file fp.c + * + * \brief Basic floating-point compatibility and convenience code. + **/ + +#include "orconfig.h" +#include "lib/math/fp.h" + +#include <math.h> + +/** + * Returns the natural logarithm of d base e. We defined this wrapper here so + * to avoid conflicts with old versions of tor_log(), which were named log(). + */ +double +tor_mathlog(double d) +{ + return log(d); +} + +/** Return the long integer closest to <b>d</b>. We define this wrapper + * here so that not all users of math.h need to use the right incantations + * to get the c99 functions. */ +long +tor_lround(double d) +{ +#if defined(HAVE_LROUND) + return lround(d); +#elif defined(HAVE_RINT) + return (long)rint(d); +#else + return (long)(d > 0 ? d + 0.5 : ceil(d - 0.5)); +#endif /* defined(HAVE_LROUND) || ... */ +} + +/** Return the 64-bit integer closest to d. We define this wrapper here so + * that not all users of math.h need to use the right incantations to get the + * c99 functions. */ +int64_t +tor_llround(double d) +{ +#if defined(HAVE_LLROUND) + return (int64_t)llround(d); +#elif defined(HAVE_RINT) + return (int64_t)rint(d); +#else + return (int64_t)(d > 0 ? d + 0.5 : ceil(d - 0.5)); +#endif /* defined(HAVE_LLROUND) || ... */ +} + +/** Cast a given double value to a int64_t. Return 0 if number is NaN. + * Returns either INT64_MIN or INT64_MAX if number is outside of the int64_t + * range. */ +int64_t +clamp_double_to_int64(double number) +{ + int exponent; + +#if defined(MINGW_ANY) && GCC_VERSION >= 409 +/* + Mingw's math.h uses gcc's __builtin_choose_expr() facility to declare + isnan, isfinite, and signbit. But as implemented in at least some + versions of gcc, __builtin_choose_expr() can generate type warnings + even from branches that are not taken. So, suppress those warnings. +*/ +#define PROBLEMATIC_FLOAT_CONVERSION_WARNING +DISABLE_GCC_WARNING(float-conversion) +#endif /* defined(MINGW_ANY) && GCC_VERSION >= 409 */ + +/* + With clang 4.0 we apparently run into "double promotion" warnings here, + since clang thinks we're promoting a double to a long double. + */ +#if defined(__clang__) +#if __has_warning("-Wdouble-promotion") +#define PROBLEMATIC_DOUBLE_PROMOTION_WARNING +DISABLE_GCC_WARNING(double-promotion) +#endif +#endif /* defined(__clang__) */ + + /* NaN is a special case that can't be used with the logic below. */ + if (isnan(number)) { + return 0; + } + + /* Time to validate if result can overflows a int64_t value. Fun with + * float! Find that exponent exp such that + * number == x * 2^exp + * for some x with abs(x) in [0.5, 1.0). Note that this implies that the + * magnitude of number is strictly less than 2^exp. + * + * If number is infinite, the call to frexp is legal but the contents of + * are exponent unspecified. */ + frexp(number, &exponent); + + /* If the magnitude of number is strictly less than 2^63, the truncated + * version of number is guaranteed to be representable. The only + * representable integer for which this is not the case is INT64_MIN, but + * it is covered by the logic below. */ + if (isfinite(number) && exponent <= 63) { + return (int64_t)number; + } + + /* Handle infinities and finite numbers with magnitude >= 2^63. */ + return signbit(number) ? INT64_MIN : INT64_MAX; + +#ifdef PROBLEMATIC_DOUBLE_PROMOTION_WARNING +ENABLE_GCC_WARNING(double-promotion) +#endif +#ifdef PROBLEMATIC_FLOAT_CONVERSION_WARNING +ENABLE_GCC_WARNING(float-conversion) +#endif +} diff --git a/src/lib/math/fp.h b/src/lib/math/fp.h new file mode 100644 index 0000000000..6f07152e92 --- /dev/null +++ b/src/lib/math/fp.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file fp.h + * + * \brief Header for fp.c + **/ + +#ifndef TOR_FP_H +#define TOR_FP_H + +#include "lib/cc/compat_compiler.h" +#include "lib/cc/torint.h" + +double tor_mathlog(double d) ATTR_CONST; +long tor_lround(double d) ATTR_CONST; +int64_t tor_llround(double d) ATTR_CONST; +int64_t clamp_double_to_int64(double number); + +#endif diff --git a/src/lib/math/include.am b/src/lib/math/include.am new file mode 100644 index 0000000000..b088b3f3cc --- /dev/null +++ b/src/lib/math/include.am @@ -0,0 +1,20 @@ + +noinst_LIBRARIES += src/lib/libtor-math.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-math-testing.a +endif + +src_lib_libtor_math_a_SOURCES = \ + src/lib/math/fp.c \ + src/lib/math/laplace.c + + +src_lib_libtor_math_testing_a_SOURCES = \ + $(src_lib_libtor_math_a_SOURCES) +src_lib_libtor_math_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_math_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/math/fp.h \ + src/lib/math/laplace.h diff --git a/src/lib/math/laplace.c b/src/lib/math/laplace.c new file mode 100644 index 0000000000..302edb20b8 --- /dev/null +++ b/src/lib/math/laplace.c @@ -0,0 +1,73 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file laplace.c + * + * \brief Implements a Laplace distribution, used for adding noise to things. + **/ + +#include "orconfig.h" +#include "lib/math/laplace.h" +#include "lib/math/fp.h" + +#include "lib/log/util_bug.h" + +#include <math.h> +#include <stdlib.h> + +/** Transform a random value <b>p</b> from the uniform distribution in + * [0.0, 1.0[ into a Laplace distributed value with location parameter + * <b>mu</b> and scale parameter <b>b</b>. Truncate the final result + * to be an integer in [INT64_MIN, INT64_MAX]. */ +int64_t +sample_laplace_distribution(double mu, double b, double p) +{ + double result; + tor_assert(p >= 0.0 && p < 1.0); + + /* This is the "inverse cumulative distribution function" from: + * http://en.wikipedia.org/wiki/Laplace_distribution */ + if (p <= 0.0) { + /* Avoid taking log(0.0) == -INFINITY, as some processors or compiler + * options can cause the program to trap. */ + return INT64_MIN; + } + + result = mu - b * (p > 0.5 ? 1.0 : -1.0) + * tor_mathlog(1.0 - 2.0 * fabs(p - 0.5)); + + return clamp_double_to_int64(result); +} + +/** Add random noise between INT64_MIN and INT64_MAX coming from a Laplace + * distribution with mu = 0 and b = <b>delta_f</b>/<b>epsilon</b> to + * <b>signal</b> based on the provided <b>random</b> value in [0.0, 1.0[. + * The epsilon value must be between ]0.0, 1.0]. delta_f must be greater + * than 0. */ +int64_t +add_laplace_noise(int64_t signal_, double random_, double delta_f, + double epsilon) +{ + int64_t noise; + + /* epsilon MUST be between ]0.0, 1.0] */ + tor_assert(epsilon > 0.0 && epsilon <= 1.0); + /* delta_f MUST be greater than 0. */ + tor_assert(delta_f > 0.0); + + /* Just add noise, no further signal */ + noise = sample_laplace_distribution(0.0, + delta_f / epsilon, + random_); + + /* Clip (signal + noise) to [INT64_MIN, INT64_MAX] */ + if (noise > 0 && INT64_MAX - noise < signal_) + return INT64_MAX; + else if (noise < 0 && INT64_MIN - noise > signal_) + return INT64_MIN; + else + return signal_ + noise; +} diff --git a/src/lib/math/laplace.h b/src/lib/math/laplace.h new file mode 100644 index 0000000000..e8651e5197 --- /dev/null +++ b/src/lib/math/laplace.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file laplace.h + * + * \brief Header for laplace.c + **/ + +#ifndef TOR_LAPLACE_H +#define TOR_LAPLACE_H + +#include "lib/cc/compat_compiler.h" +#include "lib/cc/torint.h" + +int64_t sample_laplace_distribution(double mu, double b, double p); +int64_t add_laplace_noise(int64_t signal, double random, double delta_f, + double epsilon); + +#endif diff --git a/src/lib/memarea/.may_include b/src/lib/memarea/.may_include new file mode 100644 index 0000000000..814652a93c --- /dev/null +++ b/src/lib/memarea/.may_include @@ -0,0 +1,7 @@ +orconfig.h +lib/arch/*.h +lib/cc/*.h +lib/container/*.h +lib/log/*.h +lib/malloc/*.h +lib/memarea/*.h diff --git a/src/lib/memarea/include.am b/src/lib/memarea/include.am new file mode 100644 index 0000000000..94343dcead --- /dev/null +++ b/src/lib/memarea/include.am @@ -0,0 +1,17 @@ + +noinst_LIBRARIES += src/lib/libtor-memarea.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-memarea-testing.a +endif + +src_lib_libtor_memarea_a_SOURCES = \ + src/lib/memarea/memarea.c + +src_lib_libtor_memarea_testing_a_SOURCES = \ + $(src_lib_libtor_memarea_a_SOURCES) +src_lib_libtor_memarea_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_memarea_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/memarea/memarea.h diff --git a/src/lib/memarea/memarea.c b/src/lib/memarea/memarea.c new file mode 100644 index 0000000000..486673116c --- /dev/null +++ b/src/lib/memarea/memarea.c @@ -0,0 +1,403 @@ +/* Copyright (c) 2008-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file memarea.c + * + * \brief Implementation for memarea_t, an allocator for allocating lots of + * small objects that will be freed all at once. + */ + +#include "orconfig.h" +#include "lib/memarea/memarea.h" + +#include <stdlib.h> +#include <string.h> + +#include "lib/arch/bytes.h" +#include "lib/cc/torint.h" +#include "lib/container/smartlist.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" + +#ifndef DISABLE_MEMORY_SENTINELS + +/** If true, we try to detect any attempts to write beyond the length of a + * memarea. */ +#define USE_SENTINELS + +/** All returned pointers should be aligned to the nearest multiple of this + * value. */ +#define MEMAREA_ALIGN SIZEOF_VOID_P + +/** A value which, when masked out of a pointer, produces a maximally aligned + * pointer. */ +#if MEMAREA_ALIGN == 4 +#define MEMAREA_ALIGN_MASK ((uintptr_t)3) +#elif MEMAREA_ALIGN == 8 +#define MEMAREA_ALIGN_MASK ((uintptr_t)7) +#else +#error "void* is neither 4 nor 8 bytes long. I don't know how to align stuff." +#endif /* MEMAREA_ALIGN == 4 || ... */ + +#if defined(__GNUC__) && defined(FLEXIBLE_ARRAY_MEMBER) +#define USE_ALIGNED_ATTRIBUTE +/** Name for the 'memory' member of a memory chunk. */ +#define U_MEM mem +#else +#define U_MEM u.mem +#endif /* defined(__GNUC__) && defined(FLEXIBLE_ARRAY_MEMBER) */ + +#ifdef USE_SENTINELS +/** Magic value that we stick at the end of a memarea so we can make sure + * there are no run-off-the-end bugs. */ +#define SENTINEL_VAL 0x90806622u +/** How many bytes per area do we devote to the sentinel? */ +#define SENTINEL_LEN sizeof(uint32_t) +/** Given a mem_area_chunk_t with SENTINEL_LEN extra bytes allocated at the + * end, set those bytes. */ +#define SET_SENTINEL(chunk) \ + STMT_BEGIN \ + set_uint32( &(chunk)->U_MEM[chunk->mem_size], SENTINEL_VAL ); \ + STMT_END +/** Assert that the sentinel on a memarea is set correctly. */ +#define CHECK_SENTINEL(chunk) \ + STMT_BEGIN \ + uint32_t sent_val = get_uint32(&(chunk)->U_MEM[chunk->mem_size]); \ + tor_assert(sent_val == SENTINEL_VAL); \ + STMT_END +#else /* !(defined(USE_SENTINELS)) */ +#define SENTINEL_LEN 0 +#define SET_SENTINEL(chunk) STMT_NIL +#define CHECK_SENTINEL(chunk) STMT_NIL +#endif /* defined(USE_SENTINELS) */ + +/** Increment <b>ptr</b> until it is aligned to MEMAREA_ALIGN. */ +static inline void * +realign_pointer(void *ptr) +{ + uintptr_t x = (uintptr_t)ptr; + x = (x+MEMAREA_ALIGN_MASK) & ~MEMAREA_ALIGN_MASK; + /* Reinstate this if bug 930 ever reappears + tor_assert(((void*)x) >= ptr); + */ + return (void*)x; +} + +/** Implements part of a memarea. New memory is carved off from chunk->mem in + * increasing order until a request is too big, at which point a new chunk is + * allocated. */ +typedef struct memarea_chunk_t { + /** Next chunk in this area. Only kept around so we can free it. */ + struct memarea_chunk_t *next_chunk; + size_t mem_size; /**< How much RAM is available in mem, total? */ + char *next_mem; /**< Next position in mem to allocate data at. If it's + * equal to mem+mem_size, this chunk is full. */ +#ifdef USE_ALIGNED_ATTRIBUTE + /** Actual content of the memory chunk. */ + char mem[FLEXIBLE_ARRAY_MEMBER] __attribute__((aligned(MEMAREA_ALIGN))); +#else + union { + char mem[1]; /**< Memory space in this chunk. */ + void *void_for_alignment_; /**< Dummy; used to make sure mem is aligned. */ + } u; /**< Union used to enforce alignment when we don't have support for + * doing it right. */ +#endif /* defined(USE_ALIGNED_ATTRIBUTE) */ +} memarea_chunk_t; + +/** How many bytes are needed for overhead before we get to the memory part + * of a chunk? */ +#define CHUNK_HEADER_SIZE offsetof(memarea_chunk_t, U_MEM) + +/** What's the smallest that we'll allocate a chunk? */ +#define CHUNK_SIZE 4096 + +/** A memarea_t is an allocation region for a set of small memory requests + * that will all be freed at once. */ +struct memarea_t { + memarea_chunk_t *first; /**< Top of the chunk stack: never NULL. */ +}; + +/** Helper: allocate a new memarea chunk of around <b>chunk_size</b> bytes. */ +static memarea_chunk_t * +alloc_chunk(size_t sz) +{ + tor_assert(sz < SIZE_T_CEILING); + + size_t chunk_size = sz < CHUNK_SIZE ? CHUNK_SIZE : sz; + memarea_chunk_t *res; + chunk_size += SENTINEL_LEN; + res = tor_malloc(chunk_size); + res->next_chunk = NULL; + res->mem_size = chunk_size - CHUNK_HEADER_SIZE - SENTINEL_LEN; + res->next_mem = res->U_MEM; + tor_assert(res->next_mem+res->mem_size+SENTINEL_LEN == + ((char*)res)+chunk_size); + tor_assert(realign_pointer(res->next_mem) == res->next_mem); + SET_SENTINEL(res); + return res; +} + +/** Release <b>chunk</b> from a memarea. */ +static void +memarea_chunk_free_unchecked(memarea_chunk_t *chunk) +{ + CHECK_SENTINEL(chunk); + tor_free(chunk); +} + +/** Allocate and return new memarea. */ +memarea_t * +memarea_new(void) +{ + memarea_t *head = tor_malloc(sizeof(memarea_t)); + head->first = alloc_chunk(CHUNK_SIZE); + return head; +} + +/** Free <b>area</b>, invalidating all pointers returned from memarea_alloc() + * and friends for this area */ +void +memarea_drop_all_(memarea_t *area) +{ + memarea_chunk_t *chunk, *next; + for (chunk = area->first; chunk; chunk = next) { + next = chunk->next_chunk; + memarea_chunk_free_unchecked(chunk); + } + area->first = NULL; /*fail fast on */ + tor_free(area); +} + +/** Forget about having allocated anything in <b>area</b>, and free some of + * the backing storage associated with it, as appropriate. Invalidates all + * pointers returned from memarea_alloc() for this area. */ +void +memarea_clear(memarea_t *area) +{ + memarea_chunk_t *chunk, *next; + if (area->first->next_chunk) { + for (chunk = area->first->next_chunk; chunk; chunk = next) { + next = chunk->next_chunk; + memarea_chunk_free_unchecked(chunk); + } + area->first->next_chunk = NULL; + } + area->first->next_mem = area->first->U_MEM; +} + +/** Return true iff <b>p</b> is in a range that has been returned by an + * allocation from <b>area</b>. */ +int +memarea_owns_ptr(const memarea_t *area, const void *p) +{ + memarea_chunk_t *chunk; + const char *ptr = p; + for (chunk = area->first; chunk; chunk = chunk->next_chunk) { + if (ptr >= chunk->U_MEM && ptr < chunk->next_mem) + return 1; + } + return 0; +} + +/** Return a pointer to a chunk of memory in <b>area</b> of at least <b>sz</b> + * bytes. <b>sz</b> should be significantly smaller than the area's chunk + * size, though we can deal if it isn't. */ +void * +memarea_alloc(memarea_t *area, size_t sz) +{ + memarea_chunk_t *chunk = area->first; + char *result; + tor_assert(chunk); + CHECK_SENTINEL(chunk); + tor_assert(sz < SIZE_T_CEILING); + if (sz == 0) + sz = 1; + tor_assert(chunk->next_mem <= chunk->U_MEM + chunk->mem_size); + const size_t space_remaining = + (chunk->U_MEM + chunk->mem_size) - chunk->next_mem; + if (sz > space_remaining) { + if (sz+CHUNK_HEADER_SIZE >= CHUNK_SIZE) { + /* This allocation is too big. Stick it in a special chunk, and put + * that chunk second in the list. */ + memarea_chunk_t *new_chunk = alloc_chunk(sz+CHUNK_HEADER_SIZE); + new_chunk->next_chunk = chunk->next_chunk; + chunk->next_chunk = new_chunk; + chunk = new_chunk; + } else { + memarea_chunk_t *new_chunk = alloc_chunk(CHUNK_SIZE); + new_chunk->next_chunk = chunk; + area->first = chunk = new_chunk; + } + tor_assert(chunk->mem_size >= sz); + } + result = chunk->next_mem; + chunk->next_mem = chunk->next_mem + sz; + /* Reinstate these if bug 930 ever comes back + tor_assert(chunk->next_mem >= chunk->U_MEM); + tor_assert(chunk->next_mem <= chunk->U_MEM+chunk->mem_size); + */ + chunk->next_mem = realign_pointer(chunk->next_mem); + return result; +} + +/** As memarea_alloc(), but clears the memory it returns. */ +void * +memarea_alloc_zero(memarea_t *area, size_t sz) +{ + void *result = memarea_alloc(area, sz); + memset(result, 0, sz); + return result; +} + +/** As memdup, but returns the memory from <b>area</b>. */ +void * +memarea_memdup(memarea_t *area, const void *s, size_t n) +{ + char *result = memarea_alloc(area, n); + memcpy(result, s, n); + return result; +} + +/** As strdup, but returns the memory from <b>area</b>. */ +char * +memarea_strdup(memarea_t *area, const char *s) +{ + return memarea_memdup(area, s, strlen(s)+1); +} + +/** As strndup, but returns the memory from <b>area</b>. */ +char * +memarea_strndup(memarea_t *area, const char *s, size_t n) +{ + size_t ln = 0; + char *result; + tor_assert(n < SIZE_T_CEILING); + for (ln = 0; ln < n && s[ln]; ++ln) + ; + result = memarea_alloc(area, ln+1); + memcpy(result, s, ln); + result[ln]='\0'; + return result; +} + +/** Set <b>allocated_out</b> to the number of bytes allocated in <b>area</b>, + * and <b>used_out</b> to the number of bytes currently used. */ +void +memarea_get_stats(memarea_t *area, size_t *allocated_out, size_t *used_out) +{ + size_t a = 0, u = 0; + memarea_chunk_t *chunk; + for (chunk = area->first; chunk; chunk = chunk->next_chunk) { + CHECK_SENTINEL(chunk); + a += CHUNK_HEADER_SIZE + chunk->mem_size; + tor_assert(chunk->next_mem >= chunk->U_MEM); + u += CHUNK_HEADER_SIZE + (chunk->next_mem - chunk->U_MEM); + } + *allocated_out = a; + *used_out = u; +} + +/** Assert that <b>area</b> is okay. */ +void +memarea_assert_ok(memarea_t *area) +{ + memarea_chunk_t *chunk; + tor_assert(area->first); + + for (chunk = area->first; chunk; chunk = chunk->next_chunk) { + CHECK_SENTINEL(chunk); + tor_assert(chunk->next_mem >= chunk->U_MEM); + tor_assert(chunk->next_mem <= + (char*) realign_pointer(chunk->U_MEM+chunk->mem_size)); + } +} + +#else /* !(!defined(DISABLE_MEMORY_SENTINELS)) */ + +struct memarea_t { + smartlist_t *pieces; +}; + +memarea_t * +memarea_new(void) +{ + memarea_t *ma = tor_malloc_zero(sizeof(memarea_t)); + ma->pieces = smartlist_new(); + return ma; +} +void +memarea_drop_all_(memarea_t *area) +{ + memarea_clear(area); + smartlist_free(area->pieces); + tor_free(area); +} +void +memarea_clear(memarea_t *area) +{ + SMARTLIST_FOREACH(area->pieces, void *, p, tor_free_(p)); + smartlist_clear(area->pieces); +} +int +memarea_owns_ptr(const memarea_t *area, const void *ptr) +{ + SMARTLIST_FOREACH(area->pieces, const void *, p, if (ptr == p) return 1;); + return 0; +} + +void * +memarea_alloc(memarea_t *area, size_t sz) +{ + void *result = tor_malloc(sz); + smartlist_add(area->pieces, result); + return result; +} + +void * +memarea_alloc_zero(memarea_t *area, size_t sz) +{ + void *result = tor_malloc_zero(sz); + smartlist_add(area->pieces, result); + return result; +} +void * +memarea_memdup(memarea_t *area, const void *s, size_t n) +{ + void *r = memarea_alloc(area, n); + memcpy(r, s, n); + return r; +} +char * +memarea_strdup(memarea_t *area, const char *s) +{ + size_t n = strlen(s); + char *r = memarea_alloc(area, n+1); + memcpy(r, s, n); + r[n] = 0; + return r; +} +char * +memarea_strndup(memarea_t *area, const char *s, size_t n) +{ + size_t ln = strnlen(s, n); + char *r = memarea_alloc(area, ln+1); + memcpy(r, s, ln); + r[ln] = 0; + return r; +} +void +memarea_get_stats(memarea_t *area, + size_t *allocated_out, size_t *used_out) +{ + (void)area; + *allocated_out = *used_out = 128; +} +void +memarea_assert_ok(memarea_t *area) +{ + (void)area; +} + +#endif /* !defined(DISABLE_MEMORY_SENTINELS) */ diff --git a/src/lib/memarea/memarea.h b/src/lib/memarea/memarea.h new file mode 100644 index 0000000000..9c23cf62e9 --- /dev/null +++ b/src/lib/memarea/memarea.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2008-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file memarea.h + * + * \brief Header for memarea.c + **/ + +#ifndef TOR_MEMAREA_H +#define TOR_MEMAREA_H + +#include <stddef.h> + +typedef struct memarea_t memarea_t; + +memarea_t *memarea_new(void); +void memarea_drop_all_(memarea_t *area); +#define memarea_drop_all(area) \ + do { \ + memarea_drop_all_(area); \ + (area) = NULL; \ + } while (0) +void memarea_clear(memarea_t *area); +int memarea_owns_ptr(const memarea_t *area, const void *ptr); +void *memarea_alloc(memarea_t *area, size_t sz); +void *memarea_alloc_zero(memarea_t *area, size_t sz); +void *memarea_memdup(memarea_t *area, const void *s, size_t n); +char *memarea_strdup(memarea_t *area, const char *s); +char *memarea_strndup(memarea_t *area, const char *s, size_t n); +void memarea_get_stats(memarea_t *area, + size_t *allocated_out, size_t *used_out); +void memarea_assert_ok(memarea_t *area); + +#endif /* !defined(TOR_MEMAREA_H) */ diff --git a/src/lib/meminfo/.may_include b/src/lib/meminfo/.may_include new file mode 100644 index 0000000000..9e4d25fd6a --- /dev/null +++ b/src/lib/meminfo/.may_include @@ -0,0 +1,8 @@ +orconfig.h + +lib/cc/*.h +lib/fs/*.h +lib/log/*.h +lib/malloc/*.h +lib/meminfo/*.h +lib/testsupport/*.h diff --git a/src/lib/meminfo/include.am b/src/lib/meminfo/include.am new file mode 100644 index 0000000000..d1fdde6313 --- /dev/null +++ b/src/lib/meminfo/include.am @@ -0,0 +1,17 @@ + +noinst_LIBRARIES += src/lib/libtor-meminfo.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-meminfo-testing.a +endif + +src_lib_libtor_meminfo_a_SOURCES = \ + src/lib/meminfo/meminfo.c + +src_lib_libtor_meminfo_testing_a_SOURCES = \ + $(src_lib_libtor_meminfo_a_SOURCES) +src_lib_libtor_meminfo_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_meminfo_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/meminfo/meminfo.h diff --git a/src/lib/meminfo/meminfo.c b/src/lib/meminfo/meminfo.c new file mode 100644 index 0000000000..f233188897 --- /dev/null +++ b/src/lib/meminfo/meminfo.c @@ -0,0 +1,180 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file meminfo.c + * + * \brief Functions to query total memory, and access meta-information about + * the allocator. + **/ + +#include "lib/meminfo/meminfo.h" + +#include "lib/cc/compat_compiler.h" +#include "lib/cc/torint.h" +#include "lib/fs/files.h" +#include "lib/log/log.h" +#include "lib/malloc/malloc.h" + +#ifdef HAVE_SYS_SYSCTL_H +#include <sys/sysctl.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_MALLOC_H +#include <malloc.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef _WIN32 +#include <windows.h> +#endif +#include <string.h> + +DISABLE_GCC_WARNING(aggregate-return) +/** Call the platform malloc info function, and dump the results to the log at + * level <b>severity</b>. If no such function exists, do nothing. */ +void +tor_log_mallinfo(int severity) +{ +#ifdef HAVE_MALLINFO + struct mallinfo mi; + memset(&mi, 0, sizeof(mi)); + mi = mallinfo(); + tor_log(severity, LD_MM, + "mallinfo() said: arena=%d, ordblks=%d, smblks=%d, hblks=%d, " + "hblkhd=%d, usmblks=%d, fsmblks=%d, uordblks=%d, fordblks=%d, " + "keepcost=%d", + mi.arena, mi.ordblks, mi.smblks, mi.hblks, + mi.hblkhd, mi.usmblks, mi.fsmblks, mi.uordblks, mi.fordblks, + mi.keepcost); +#else /* !(defined(HAVE_MALLINFO)) */ + (void)severity; +#endif /* defined(HAVE_MALLINFO) */ +} +ENABLE_GCC_WARNING(aggregate-return) + +#if defined(HW_PHYSMEM64) +/* OpenBSD and NetBSD define this */ +#define INT64_HW_MEM HW_PHYSMEM64 +#elif defined(HW_MEMSIZE) +/* OSX defines this one */ +#define INT64_HW_MEM HW_MEMSIZE +#endif /* defined(HW_PHYSMEM64) || ... */ + +/** + * Helper: try to detect the total system memory, and return it. On failure, + * return 0. + */ +static uint64_t +get_total_system_memory_impl(void) +{ +#if defined(__linux__) + /* On linux, sysctl is deprecated. Because proc is so awesome that you + * shouldn't _want_ to write portable code, I guess? */ + unsigned long long result=0; + int fd = -1; + char *s = NULL; + const char *cp; + size_t file_size=0; + if (-1 == (fd = tor_open_cloexec("/proc/meminfo",O_RDONLY,0))) + return 0; + s = read_file_to_str_until_eof(fd, 65536, &file_size); + if (!s) + goto err; + cp = strstr(s, "MemTotal:"); + if (!cp) + goto err; + /* Use the system sscanf so that space will match a wider number of space */ + if (sscanf(cp, "MemTotal: %llu kB\n", &result) != 1) + goto err; + + close(fd); + tor_free(s); + return result * 1024; + + /* LCOV_EXCL_START Can't reach this unless proc is broken. */ + err: + tor_free(s); + close(fd); + return 0; + /* LCOV_EXCL_STOP */ +#elif defined (_WIN32) + /* Windows has MEMORYSTATUSEX; pretty straightforward. */ + MEMORYSTATUSEX ms; + memset(&ms, 0, sizeof(ms)); + ms.dwLength = sizeof(ms); + if (! GlobalMemoryStatusEx(&ms)) + return 0; + + return ms.ullTotalPhys; + +#elif defined(HAVE_SYSCTL) && defined(INT64_HW_MEM) + /* On many systems, HW_PHYSMEM is clipped to 32 bits; let's use a better + * variant if we know about it. */ + uint64_t memsize = 0; + size_t len = sizeof(memsize); + int mib[2] = {CTL_HW, INT64_HW_MEM}; + if (sysctl(mib,2,&memsize,&len,NULL,0)) + return 0; + + return memsize; + +#elif defined(HAVE_SYSCTL) && defined(HW_PHYSMEM) + /* On some systems (like FreeBSD I hope) you can use a size_t with + * HW_PHYSMEM. */ + size_t memsize=0; + size_t len = sizeof(memsize); + int mib[2] = {CTL_HW, HW_PHYSMEM}; + if (sysctl(mib,2,&memsize,&len,NULL,0)) + return 0; + + return memsize; + +#else + /* I have no clue. */ + return 0; +#endif /* defined(__linux__) || ... */ +} + +/** + * Try to find out how much physical memory the system has. On success, + * return 0 and set *<b>mem_out</b> to that value. On failure, return -1. + */ +MOCK_IMPL(int, +get_total_system_memory, (size_t *mem_out)) +{ + static size_t mem_cached=0; + uint64_t m = get_total_system_memory_impl(); + if (0 == m) { + /* LCOV_EXCL_START -- can't make this happen without mocking. */ + /* We couldn't find our memory total */ + if (0 == mem_cached) { + /* We have no cached value either */ + *mem_out = 0; + return -1; + } + + *mem_out = mem_cached; + return 0; + /* LCOV_EXCL_STOP */ + } + +#if SIZE_MAX != UINT64_MAX + if (m > SIZE_MAX) { + /* I think this could happen if we're a 32-bit Tor running on a 64-bit + * system: we could have more system memory than would fit in a + * size_t. */ + m = SIZE_MAX; + } +#endif /* SIZE_MAX != UINT64_MAX */ + + *mem_out = mem_cached = (size_t) m; + + return 0; +} diff --git a/src/lib/meminfo/meminfo.h b/src/lib/meminfo/meminfo.h new file mode 100644 index 0000000000..2d64e1ab06 --- /dev/null +++ b/src/lib/meminfo/meminfo.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file meminfo.h + * + * \brief Header for meminfo.c + **/ + +#ifndef TOR_MEMINFO_H +#define TOR_MEMINFO_H + +#include "lib/testsupport/testsupport.h" +#include <stddef.h> + +void tor_log_mallinfo(int severity); +MOCK_DECL(int, get_total_system_memory, (size_t *mem_out)); + +#endif diff --git a/src/lib/net/.may_include b/src/lib/net/.may_include new file mode 100644 index 0000000000..13b209bbed --- /dev/null +++ b/src/lib/net/.may_include @@ -0,0 +1,15 @@ +orconfig.h +siphash.h +ht.h + +lib/arch/*.h +lib/cc/*.h +lib/container/*.h +lib/ctime/*.h +lib/err/*.h +lib/lock/*.h +lib/log/*.h +lib/net/*.h +lib/string/*.h +lib/testsupport/*.h +lib/malloc/*.h
\ No newline at end of file diff --git a/src/lib/net/address.c b/src/lib/net/address.c new file mode 100644 index 0000000000..a2d234b742 --- /dev/null +++ b/src/lib/net/address.c @@ -0,0 +1,2057 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file address.c + * \brief Functions to use and manipulate the tor_addr_t structure. + * + * This module doesn't have any support for the libc resolver: that is all in + * resolve.c. + **/ + +#define ADDRESS_PRIVATE + +#include "orconfig.h" + +#ifdef _WIN32 +/* For access to structs needed by GetAdaptersAddresses */ +#ifndef WIN32_LEAN_AND_MEAN +#error "orconfig.h didn't define WIN32_LEAN_AND_MEAN" +#endif +#ifndef WINVER +#error "orconfig.h didn't define WINVER" +#endif +#ifndef _WIN32_WINNT +#error "orconfig.h didn't define _WIN32_WINNT" +#endif +#if WINVER < 0x0501 +#error "winver too low" +#endif +#if _WIN32_WINNT < 0x0501 +#error "winver too low" +#endif +#include <winsock2.h> +#include <process.h> +#include <windows.h> +#include <iphlpapi.h> +#endif /* defined(_WIN32) */ + +#include "lib/net/address.h" +#include "lib/net/socket.h" +#include "lib/container/smartlist.h" +#include "lib/ctime/di_ops.h" +#include "lib/log/log.h" +#include "lib/log/escape.h" +#include "lib/malloc/malloc.h" +#include "lib/net/inaddr.h" +#include "lib/string/compat_ctype.h" +#include "lib/string/compat_string.h" +#include "lib/string/parse_int.h" +#include "lib/string/printf.h" +#include "lib/string/util_string.h" + +#include "siphash.h" + +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> /* FreeBSD needs this to know what version it is */ +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif +#ifdef HAVE_IFADDRS_H +#include <ifaddrs.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#ifdef HAVE_NET_IF_H +#include <net/if.h> +#endif +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* tor_addr_is_null() and maybe other functions rely on AF_UNSPEC being 0 to + * work correctly. Bail out here if we've found a platform where AF_UNSPEC + * isn't 0. */ +#if AF_UNSPEC != 0 +#error We rely on AF_UNSPEC being 0. Let us know about your platform, please! +#endif + +/** Convert the tor_addr_t in <b>a</b>, with port in <b>port</b>, into a + * sockaddr object in *<b>sa_out</b> of object size <b>len</b>. If not enough + * room is available in sa_out, or on error, return 0. On success, return + * the length of the sockaddr. + * + * Interface note: ordinarily, we return -1 for error. We can't do that here, + * since socklen_t is unsigned on some platforms. + **/ +socklen_t +tor_addr_to_sockaddr(const tor_addr_t *a, + uint16_t port, + struct sockaddr *sa_out, + socklen_t len) +{ + memset(sa_out, 0, len); + + sa_family_t family = tor_addr_family(a); + if (family == AF_INET) { + struct sockaddr_in *sin; + if (len < (int)sizeof(struct sockaddr_in)) + return 0; + sin = (struct sockaddr_in *)sa_out; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sin->sin_len = sizeof(struct sockaddr_in); +#endif + sin->sin_family = AF_INET; + sin->sin_port = htons(port); + sin->sin_addr.s_addr = tor_addr_to_ipv4n(a); + return sizeof(struct sockaddr_in); + } else if (family == AF_INET6) { + struct sockaddr_in6 *sin6; + if (len < (int)sizeof(struct sockaddr_in6)) + return 0; + sin6 = (struct sockaddr_in6 *)sa_out; +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sin6->sin6_len = sizeof(struct sockaddr_in6); +#endif + sin6->sin6_family = AF_INET6; + sin6->sin6_port = htons(port); + memcpy(&sin6->sin6_addr, tor_addr_to_in6_assert(a), + sizeof(struct in6_addr)); + return sizeof(struct sockaddr_in6); + } else { + return 0; + } +} + +/** Set address <b>a</b> to zero. This address belongs to + * the AF_UNIX family. */ +static void +tor_addr_make_af_unix(tor_addr_t *a) +{ + memset(a, 0, sizeof(*a)); + a->family = AF_UNIX; +} + +/** Set the tor_addr_t in <b>a</b> to contain the socket address contained in + * <b>sa</b>. IF <b>port_out</b> is non-NULL and <b>sa</b> contains a port, + * set *<b>port_out</b> to that port. Return 0 on success and -1 on + * failure. */ +int +tor_addr_from_sockaddr(tor_addr_t *a, const struct sockaddr *sa, + uint16_t *port_out) +{ + tor_assert(a); + tor_assert(sa); + + /* This memset is redundant; leaving it in to avoid any future accidents, + however. */ + memset(a, 0, sizeof(*a)); + + if (sa->sa_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *) sa; + tor_addr_from_ipv4n(a, sin->sin_addr.s_addr); + if (port_out) + *port_out = ntohs(sin->sin_port); + } else if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa; + tor_addr_from_in6(a, &sin6->sin6_addr); + if (port_out) + *port_out = ntohs(sin6->sin6_port); + } else if (sa->sa_family == AF_UNIX) { + tor_addr_make_af_unix(a); + return 0; + } else { + tor_addr_make_unspec(a); + return -1; + } + return 0; +} + +/** Return a newly allocated string holding the address described in + * <b>sa</b>. AF_UNIX, AF_UNSPEC, AF_INET, and AF_INET6 are supported. */ +char * +tor_sockaddr_to_str(const struct sockaddr *sa) +{ + char address[TOR_ADDR_BUF_LEN]; + char *result; + tor_addr_t addr; + uint16_t port; +#ifdef HAVE_SYS_UN_H + if (sa->sa_family == AF_UNIX) { + struct sockaddr_un *s_un = (struct sockaddr_un *)sa; + tor_asprintf(&result, "unix:%s", s_un->sun_path); + return result; + } +#endif /* defined(HAVE_SYS_UN_H) */ + if (sa->sa_family == AF_UNSPEC) + return tor_strdup("unspec"); + + if (tor_addr_from_sockaddr(&addr, sa, &port) < 0) + return NULL; + if (! tor_addr_to_str(address, &addr, sizeof(address), 1)) + return NULL; + tor_asprintf(&result, "%s:%d", address, (int)port); + return result; +} + +/** Set address <b>a</b> to the unspecified address. This address belongs to + * no family. */ +void +tor_addr_make_unspec(tor_addr_t *a) +{ + memset(a, 0, sizeof(*a)); + a->family = AF_UNSPEC; +} + +/** Set address <b>a</b> to the null address in address family <b>family</b>. + * The null address for AF_INET is 0.0.0.0. The null address for AF_INET6 is + * [::]. AF_UNSPEC is all null. */ +void +tor_addr_make_null(tor_addr_t *a, sa_family_t family) +{ + memset(a, 0, sizeof(*a)); + a->family = family; +} + +/** Return true iff <b>ip</b> is an IP reserved to localhost or local networks. + * + * If <b>ip</b> is in RFC1918 or RFC4193 or RFC4291, we will return true. + * (fec0::/10, deprecated by RFC3879, is also treated as internal for now + * and will return true.) + * + * If <b>ip</b> is 0.0.0.0 or 100.64.0.0/10 (RFC6598), we will act as: + * - Internal if <b>for_listening</b> is 0, as these addresses are not + * routable on the internet and we won't be publicly accessible to clients. + * - External if <b>for_listening</b> is 1, as clients could connect to us + * from the internet (in the case of 0.0.0.0) or a service provider's + * internal network (in the case of RFC6598). + */ +int +tor_addr_is_internal_(const tor_addr_t *addr, int for_listening, + const char *filename, int lineno) +{ + uint32_t iph4 = 0; + uint32_t iph6[4]; + + tor_assert(addr); + sa_family_t v_family = tor_addr_family(addr); + + if (v_family == AF_INET) { + iph4 = tor_addr_to_ipv4h(addr); + } else if (v_family == AF_INET6) { + if (tor_addr_is_v4(addr)) { /* v4-mapped */ + uint32_t *addr32 = NULL; + v_family = AF_INET; + // Work around an incorrect NULL pointer dereference warning in + // "clang --analyze" due to limited analysis depth + addr32 = tor_addr_to_in6_addr32(addr); + // To improve performance, wrap this assertion in: + // #if !defined(__clang_analyzer__) || PARANOIA + tor_assert(addr32); + iph4 = ntohl(addr32[3]); + } + } + + if (v_family == AF_INET6) { + const uint32_t *a32 = tor_addr_to_in6_addr32(addr); + iph6[0] = ntohl(a32[0]); + iph6[1] = ntohl(a32[1]); + iph6[2] = ntohl(a32[2]); + iph6[3] = ntohl(a32[3]); + if (for_listening && !iph6[0] && !iph6[1] && !iph6[2] && !iph6[3]) /* :: */ + return 0; + + if (((iph6[0] & 0xfe000000) == 0xfc000000) || /* fc00/7 - RFC4193 */ + ((iph6[0] & 0xffc00000) == 0xfe800000) || /* fe80/10 - RFC4291 */ + ((iph6[0] & 0xffc00000) == 0xfec00000)) /* fec0/10 D- RFC3879 */ + return 1; + + if (!iph6[0] && !iph6[1] && !iph6[2] && + ((iph6[3] & 0xfffffffe) == 0x00000000)) /* ::/127 */ + return 1; + + return 0; + } else if (v_family == AF_INET) { + /* special case for binding to 0.0.0.0 or 100.64/10 (RFC6598) */ + if (for_listening && (!iph4 || ((iph4 & 0xffc00000) == 0x64400000))) + return 0; + if (((iph4 & 0xff000000) == 0x0a000000) || /* 10/8 */ + ((iph4 & 0xff000000) == 0x00000000) || /* 0/8 */ + ((iph4 & 0xff000000) == 0x7f000000) || /* 127/8 */ + ((iph4 & 0xffc00000) == 0x64400000) || /* 100.64/10 */ + ((iph4 & 0xffff0000) == 0xa9fe0000) || /* 169.254/16 */ + ((iph4 & 0xfff00000) == 0xac100000) || /* 172.16/12 */ + ((iph4 & 0xffff0000) == 0xc0a80000)) /* 192.168/16 */ + return 1; + return 0; + } + + /* unknown address family... assume it's not safe for external use */ + /* rather than tor_assert(0) */ + log_warn(LD_BUG, "tor_addr_is_internal() called from %s:%d with a " + "non-IP address of type %d", filename, lineno, (int)v_family); + tor_fragile_assert(); + return 1; +} + +/** Convert a tor_addr_t <b>addr</b> into a string, and store it in + * <b>dest</b> of size <b>len</b>. Returns a pointer to dest on success, + * or NULL on failure. If <b>decorate</b>, surround IPv6 addresses with + * brackets. + */ +const char * +tor_addr_to_str(char *dest, const tor_addr_t *addr, size_t len, int decorate) +{ + const char *ptr; + tor_assert(addr && dest); + + switch (tor_addr_family(addr)) { + case AF_INET: + /* Shortest addr x.x.x.x + \0 */ + if (len < 8) + return NULL; + ptr = tor_inet_ntop(AF_INET, &addr->addr.in_addr, dest, len); + break; + case AF_INET6: + /* Shortest addr [ :: ] + \0 */ + if (len < (3 + (decorate ? 2 : 0))) + return NULL; + + if (decorate) + ptr = tor_inet_ntop(AF_INET6, &addr->addr.in6_addr, dest+1, len-2); + else + ptr = tor_inet_ntop(AF_INET6, &addr->addr.in6_addr, dest, len); + + if (ptr && decorate) { + *dest = '['; + memcpy(dest+strlen(dest), "]", 2); + tor_assert(ptr == dest+1); + ptr = dest; + } + break; + case AF_UNIX: + tor_snprintf(dest, len, "AF_UNIX"); + ptr = dest; + break; + default: + return NULL; + } + return ptr; +} + +/** Parse an .in-addr.arpa or .ip6.arpa address from <b>address</b>. Return 0 + * if this is not an .in-addr.arpa address or an .ip6.arpa address. Return -1 + * if this is an ill-formed .in-addr.arpa address or an .ip6.arpa address. + * Also return -1 if <b>family</b> is not AF_UNSPEC, and the parsed address + * family does not match <b>family</b>. On success, return 1, and store the + * result, if any, into <b>result</b>, if provided. + * + * If <b>accept_regular</b> is set and the address is in neither recognized + * reverse lookup hostname format, try parsing the address as a regular + * IPv4 or IPv6 address too. + */ +int +tor_addr_parse_PTR_name(tor_addr_t *result, const char *address, + int family, int accept_regular) +{ + if (!strcasecmpend(address, ".in-addr.arpa")) { + /* We have an in-addr.arpa address. */ + char buf[INET_NTOA_BUF_LEN]; + size_t len; + struct in_addr inaddr; + if (family == AF_INET6) + return -1; + + len = strlen(address) - strlen(".in-addr.arpa"); + if (len >= INET_NTOA_BUF_LEN) + return -1; /* Too long. */ + + memcpy(buf, address, len); + buf[len] = '\0'; + if (tor_inet_aton(buf, &inaddr) == 0) + return -1; /* malformed. */ + + /* reverse the bytes */ + inaddr.s_addr = (uint32_t) + (((inaddr.s_addr & 0x000000ff) << 24) + |((inaddr.s_addr & 0x0000ff00) << 8) + |((inaddr.s_addr & 0x00ff0000) >> 8) + |((inaddr.s_addr & 0xff000000) >> 24)); + + if (result) { + tor_addr_from_in(result, &inaddr); + } + return 1; + } + + if (!strcasecmpend(address, ".ip6.arpa")) { + const char *cp; + int n0, n1; + struct in6_addr in6; + + if (family == AF_INET) + return -1; + + cp = address; + for (int i = 0; i < 16; ++i) { + n0 = hex_decode_digit(*cp++); /* The low-order nybble appears first. */ + if (*cp++ != '.') return -1; /* Then a dot. */ + n1 = hex_decode_digit(*cp++); /* The high-order nybble appears first. */ + if (*cp++ != '.') return -1; /* Then another dot. */ + if (n0<0 || n1 < 0) /* Both nybbles must be hex. */ + return -1; + + /* We don't check the length of the string in here. But that's okay, + * since we already know that the string ends with ".ip6.arpa", and + * there is no way to frameshift .ip6.arpa so it fits into the pattern + * of hexdigit, period, hexdigit, period that we enforce above. + */ + + /* Assign from low-byte to high-byte. */ + in6.s6_addr[15-i] = n0 | (n1 << 4); + } + if (strcasecmp(cp, "ip6.arpa")) + return -1; + + if (result) { + tor_addr_from_in6(result, &in6); + } + return 1; + } + + if (accept_regular) { + tor_addr_t tmp; + int r = tor_addr_parse(&tmp, address); + if (r < 0) + return 0; + if (r != family && family != AF_UNSPEC) + return -1; + + if (result) + memcpy(result, &tmp, sizeof(tor_addr_t)); + + return 1; + } + + return 0; +} + +/** Convert <b>addr</b> to an in-addr.arpa name or a .ip6.arpa name, + * and store the result in the <b>outlen</b>-byte buffer at + * <b>out</b>. Returns a non-negative integer on success. + * Returns -1 on failure. */ +int +tor_addr_to_PTR_name(char *out, size_t outlen, + const tor_addr_t *addr) +{ + tor_assert(out); + tor_assert(addr); + + if (addr->family == AF_INET) { + uint32_t a = tor_addr_to_ipv4h(addr); + + return tor_snprintf(out, outlen, "%d.%d.%d.%d.in-addr.arpa", + (int)(uint8_t)((a )&0xff), + (int)(uint8_t)((a>>8 )&0xff), + (int)(uint8_t)((a>>16)&0xff), + (int)(uint8_t)((a>>24)&0xff)); + } else if (addr->family == AF_INET6) { + int i; + char *cp = out; + const uint8_t *bytes = tor_addr_to_in6_addr8(addr); + if (outlen < REVERSE_LOOKUP_NAME_BUF_LEN) + return -1; + for (i = 15; i >= 0; --i) { + uint8_t byte = bytes[i]; + *cp++ = "0123456789abcdef"[byte & 0x0f]; + *cp++ = '.'; + *cp++ = "0123456789abcdef"[byte >> 4]; + *cp++ = '.'; + } + memcpy(cp, "ip6.arpa", 9); /* 8 characters plus NUL */ + return 32 * 2 + 8; + } + return -1; +} + +/** Parse a string <b>s</b> containing an IPv4/IPv6 address, and possibly + * a mask and port or port range. Store the parsed address in + * <b>addr_out</b>, a mask (if any) in <b>mask_out</b>, and port(s) (if any) + * in <b>port_min_out</b> and <b>port_max_out</b>. + * + * The syntax is: + * Address OptMask OptPortRange + * Address ::= IPv4Address / "[" IPv6Address "]" / "*" + * OptMask ::= "/" Integer / + * OptPortRange ::= ":*" / ":" Integer / ":" Integer "-" Integer / + * + * - If mask, minport, or maxport are NULL, we do not want these + * options to be set; treat them as an error if present. + * - If the string has no mask, the mask is set to /32 (IPv4) or /128 (IPv6). + * - If the string has one port, it is placed in both min and max port + * variables. + * - If the string has no port(s), port_(min|max)_out are set to 1 and 65535. + * + * Return an address family on success, or -1 if an invalid address string is + * provided. + * + * If 'flags & TAPMP_EXTENDED_STAR' is false, then the wildcard address '*' + * yield an IPv4 wildcard. + * + * If 'flags & TAPMP_EXTENDED_STAR' is true, then the wildcard address '*' + * yields an AF_UNSPEC wildcard address, which expands to corresponding + * wildcard IPv4 and IPv6 rules, and the following change is made + * in the grammar above: + * Address ::= IPv4Address / "[" IPv6Address "]" / "*" / "*4" / "*6" + * with the new "*4" and "*6" productions creating a wildcard to match + * IPv4 or IPv6 addresses. + * + * If 'flags & TAPMP_EXTENDED_STAR' and 'flags & TAPMP_STAR_IPV4_ONLY' are + * both true, then the wildcard address '*' yields an IPv4 wildcard. + * + * If 'flags & TAPMP_EXTENDED_STAR' and 'flags & TAPMP_STAR_IPV6_ONLY' are + * both true, then the wildcard address '*' yields an IPv6 wildcard. + * + * TAPMP_STAR_IPV4_ONLY and TAPMP_STAR_IPV6_ONLY are mutually exclusive. */ +int +tor_addr_parse_mask_ports(const char *s, + unsigned flags, + tor_addr_t *addr_out, + maskbits_t *maskbits_out, + uint16_t *port_min_out, uint16_t *port_max_out) +{ + char *base = NULL, *address, *mask = NULL, *port = NULL, *rbracket = NULL; + char *endptr; + int any_flag=0, v4map=0; + sa_family_t family; + struct in6_addr in6_tmp; + struct in_addr in_tmp = { .s_addr = 0 }; + + tor_assert(s); + tor_assert(addr_out); + /* We can either only want an IPv4 address or only want an IPv6 address, + * but we can't only want IPv4 & IPv6 at the same time. */ + tor_assert(!((flags & TAPMP_STAR_IPV4_ONLY) + && (flags & TAPMP_STAR_IPV6_ONLY))); + + /** Longest possible length for an address, mask, and port-range combination. + * Includes IP, [], /mask, :, ports */ +#define MAX_ADDRESS_LENGTH (TOR_ADDR_BUF_LEN+2+(1+INET_NTOA_BUF_LEN)+12+1) + + if (strlen(s) > MAX_ADDRESS_LENGTH) { + log_warn(LD_GENERAL, "Impossibly long IP %s; rejecting", escaped(s)); + goto err; + } + base = tor_strdup(s); + + /* Break 'base' into separate strings. */ + address = base; + if (*address == '[') { /* Probably IPv6 */ + address++; + rbracket = strchr(address, ']'); + if (!rbracket) { + log_warn(LD_GENERAL, + "No closing IPv6 bracket in address pattern; rejecting."); + goto err; + } + } + mask = strchr((rbracket?rbracket:address),'/'); + port = strchr((mask?mask:(rbracket?rbracket:address)), ':'); + if (port) + *port++ = '\0'; + if (mask) + *mask++ = '\0'; + if (rbracket) + *rbracket = '\0'; + if (port && mask) + tor_assert(port > mask); + if (mask && rbracket) + tor_assert(mask > rbracket); + + /* Now "address" is the a.b.c.d|'*'|abcd::1 part... + * "mask" is the Mask|Maskbits part... + * and "port" is the *|port|min-max part. + */ + + /* Process the address portion */ + memset(addr_out, 0, sizeof(tor_addr_t)); + + if (!strcmp(address, "*")) { + if (flags & TAPMP_EXTENDED_STAR) { + if (flags & TAPMP_STAR_IPV4_ONLY) { + family = AF_INET; + tor_addr_from_ipv4h(addr_out, 0); + } else if (flags & TAPMP_STAR_IPV6_ONLY) { + static char nil_bytes[16] = { [0]=0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }; + family = AF_INET6; + tor_addr_from_ipv6_bytes(addr_out, nil_bytes); + } else { + family = AF_UNSPEC; + tor_addr_make_unspec(addr_out); + log_info(LD_GENERAL, + "'%s' expands into rules which apply to all IPv4 and IPv6 " + "addresses. (Use accept/reject *4:* for IPv4 or " + "accept[6]/reject[6] *6:* for IPv6.)", s); + } + } else { + family = AF_INET; + tor_addr_from_ipv4h(addr_out, 0); + } + any_flag = 1; + } else if (!strcmp(address, "*4") && (flags & TAPMP_EXTENDED_STAR)) { + family = AF_INET; + tor_addr_from_ipv4h(addr_out, 0); + any_flag = 1; + } else if (!strcmp(address, "*6") && (flags & TAPMP_EXTENDED_STAR)) { + static char nil_bytes[16] = { [0]=0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }; + family = AF_INET6; + tor_addr_from_ipv6_bytes(addr_out, nil_bytes); + any_flag = 1; + } else if (tor_inet_pton(AF_INET6, address, &in6_tmp) > 0) { + family = AF_INET6; + tor_addr_from_in6(addr_out, &in6_tmp); + } else if (tor_inet_pton(AF_INET, address, &in_tmp) > 0) { + family = AF_INET; + tor_addr_from_in(addr_out, &in_tmp); + } else { + log_warn(LD_GENERAL, "Malformed IP %s in address pattern; rejecting.", + escaped(address)); + goto err; + } + + v4map = tor_addr_is_v4(addr_out); + + /* Parse mask */ + if (maskbits_out) { + int bits = 0; + struct in_addr v4mask; + + if (mask) { /* the caller (tried to) specify a mask */ + bits = (int) strtol(mask, &endptr, 10); + if (!*endptr) { /* strtol converted everything, so it was an integer */ + if ((bits<0 || bits>128) || + (family == AF_INET && bits > 32)) { + log_warn(LD_GENERAL, + "Bad number of mask bits (%d) on address range; rejecting.", + bits); + goto err; + } + } else { /* mask might still be an address-style mask */ + if (tor_inet_pton(AF_INET, mask, &v4mask) > 0) { + bits = addr_mask_get_bits(ntohl(v4mask.s_addr)); + if (bits < 0) { + log_warn(LD_GENERAL, + "IPv4-style mask %s is not a prefix address; rejecting.", + escaped(mask)); + goto err; + } + } else { /* Not IPv4; we don't do address-style IPv6 masks. */ + log_warn(LD_GENERAL, + "Malformed mask on address range %s; rejecting.", + escaped(s)); + goto err; + } + } + if (family == AF_INET6 && v4map) { + if (bits > 32 && bits < 96) { /* Crazy */ + log_warn(LD_GENERAL, + "Bad mask bits %d for V4-mapped V6 address; rejecting.", + bits); + goto err; + } + /* XXXX_IP6 is this really what we want? */ + bits = 96 + bits%32; /* map v4-mapped masks onto 96-128 bits */ + } + if (any_flag) { + log_warn(LD_GENERAL, + "Found bit prefix with wildcard address; rejecting"); + goto err; + } + } else { /* pick an appropriate mask, as none was given */ + if (any_flag) + bits = 0; /* This is okay whether it's V6 or V4 (FIX V4-mapped V6!) */ + else if (tor_addr_family(addr_out) == AF_INET) + bits = 32; + else if (tor_addr_family(addr_out) == AF_INET6) + bits = 128; + } + *maskbits_out = (maskbits_t) bits; + } else { + if (mask) { + log_warn(LD_GENERAL, + "Unexpected mask in address %s; rejecting", escaped(s)); + goto err; + } + } + + /* Parse port(s) */ + if (port_min_out) { + uint16_t port2; + if (!port_max_out) /* caller specified one port; fake the second one */ + port_max_out = &port2; + + if (parse_port_range(port, port_min_out, port_max_out) < 0) { + goto err; + } else if ((*port_min_out != *port_max_out) && port_max_out == &port2) { + log_warn(LD_GENERAL, + "Wanted one port from address range, but there are two."); + + port_max_out = NULL; /* caller specified one port, so set this back */ + goto err; + } + } else { + if (port) { + log_warn(LD_GENERAL, + "Unexpected ports in address %s; rejecting", escaped(s)); + goto err; + } + } + + tor_free(base); + return tor_addr_family(addr_out); + err: + tor_free(base); + return -1; +} + +/** Determine whether an address is IPv4, either native or IPv4-mapped IPv6. + * Note that this is about representation only, as any decent stack will + * reject IPv4-mapped addresses received on the wire (and won't use them + * on the wire either). + */ +int +tor_addr_is_v4(const tor_addr_t *addr) +{ + tor_assert(addr); + + if (tor_addr_family(addr) == AF_INET) + return 1; + + if (tor_addr_family(addr) == AF_INET6) { + /* First two don't need to be ordered */ + uint32_t *a32 = tor_addr_to_in6_addr32(addr); + if (a32[0] == 0 && a32[1] == 0 && ntohl(a32[2]) == 0x0000ffffu) + return 1; + } + + return 0; /* Not IPv4 - unknown family or a full-blood IPv6 address */ +} + +/** Determine whether an address <b>addr</b> is null, either all zeroes or + * belonging to family AF_UNSPEC. + */ +int +tor_addr_is_null(const tor_addr_t *addr) +{ + tor_assert(addr); + + switch (tor_addr_family(addr)) { + case AF_INET6: { + uint32_t *a32 = tor_addr_to_in6_addr32(addr); + return (a32[0] == 0) && (a32[1] == 0) && (a32[2] == 0) && (a32[3] == 0); + } + case AF_INET: + return (tor_addr_to_ipv4n(addr) == 0); + case AF_UNIX: + return 1; + case AF_UNSPEC: + return 1; + default: + log_warn(LD_BUG, "Called with unknown address family %d", + (int)tor_addr_family(addr)); + return 0; + } + //return 1; +} + +/** Return true iff <b>addr</b> is a loopback address */ +int +tor_addr_is_loopback(const tor_addr_t *addr) +{ + tor_assert(addr); + switch (tor_addr_family(addr)) { + case AF_INET6: { + /* ::1 */ + uint32_t *a32 = tor_addr_to_in6_addr32(addr); + return (a32[0] == 0) && (a32[1] == 0) && (a32[2] == 0) && + (ntohl(a32[3]) == 1); + } + case AF_INET: + /* 127.0.0.1 */ + return (tor_addr_to_ipv4h(addr) & 0xff000000) == 0x7f000000; + case AF_UNSPEC: + return 0; + /* LCOV_EXCL_START */ + default: + tor_fragile_assert(); + return 0; + /* LCOV_EXCL_STOP */ + } +} + +/* Is addr valid? + * Checks that addr is non-NULL and not tor_addr_is_null(). + * If for_listening is true, IPv4 addr 0.0.0.0 is allowed. + * It means "bind to all addresses on the local machine". */ +int +tor_addr_is_valid(const tor_addr_t *addr, int for_listening) +{ + /* NULL addresses are invalid regardless of for_listening */ + if (addr == NULL) { + return 0; + } + + /* Only allow IPv4 0.0.0.0 for_listening. */ + if (for_listening && addr->family == AF_INET + && tor_addr_to_ipv4h(addr) == 0) { + return 1; + } + + /* Otherwise, the address is valid if it's not tor_addr_is_null() */ + return !tor_addr_is_null(addr); +} + +/* Is the network-order IPv4 address v4n_addr valid? + * Checks that addr is not zero. + * Except if for_listening is true, where IPv4 addr 0.0.0.0 is allowed. */ +int +tor_addr_is_valid_ipv4n(uint32_t v4n_addr, int for_listening) +{ + /* Any IPv4 address is valid with for_listening. */ + if (for_listening) { + return 1; + } + + /* Otherwise, zero addresses are invalid. */ + return v4n_addr != 0; +} + +/* Is port valid? + * Checks that port is not 0. + * Except if for_listening is true, where port 0 is allowed. + * It means "OS chooses a port". */ +int +tor_port_is_valid(uint16_t port, int for_listening) +{ + /* Any port value is valid with for_listening. */ + if (for_listening) { + return 1; + } + + /* Otherwise, zero ports are invalid. */ + return port != 0; +} + +/** Set <b>dest</b> to equal the IPv4 address in <b>v4addr</b> (given in + * network order). */ +void +tor_addr_from_ipv4n(tor_addr_t *dest, uint32_t v4addr) +{ + tor_assert(dest); + memset(dest, 0, sizeof(tor_addr_t)); + dest->family = AF_INET; + dest->addr.in_addr.s_addr = v4addr; +} + +/** Set <b>dest</b> to equal the IPv6 address in the 16 bytes at + * <b>ipv6_bytes</b>. */ +void +tor_addr_from_ipv6_bytes(tor_addr_t *dest, const char *ipv6_bytes) +{ + tor_assert(dest); + tor_assert(ipv6_bytes); + memset(dest, 0, sizeof(tor_addr_t)); + dest->family = AF_INET6; + memcpy(dest->addr.in6_addr.s6_addr, ipv6_bytes, 16); +} + +/** Set <b>dest</b> equal to the IPv6 address in the in6_addr <b>in6</b>. */ +void +tor_addr_from_in6(tor_addr_t *dest, const struct in6_addr *in6) +{ + tor_addr_from_ipv6_bytes(dest, (const char*)in6->s6_addr); +} + +/** Copy a tor_addr_t from <b>src</b> to <b>dest</b>. + */ +void +tor_addr_copy(tor_addr_t *dest, const tor_addr_t *src) +{ + if (src == dest) + return; + tor_assert(src); + tor_assert(dest); + memcpy(dest, src, sizeof(tor_addr_t)); +} + +/** Copy a tor_addr_t from <b>src</b> to <b>dest</b>, taking extra care to + * copy only the well-defined portions. Used for computing hashes of + * addresses. + */ +void +tor_addr_copy_tight(tor_addr_t *dest, const tor_addr_t *src) +{ + tor_assert(src != dest); + tor_assert(src); + tor_assert(dest); + memset(dest, 0, sizeof(tor_addr_t)); + dest->family = src->family; + switch (tor_addr_family(src)) + { + case AF_INET: + dest->addr.in_addr.s_addr = src->addr.in_addr.s_addr; + break; + case AF_INET6: + memcpy(dest->addr.in6_addr.s6_addr, src->addr.in6_addr.s6_addr, 16); + case AF_UNSPEC: + break; + // LCOV_EXCL_START + default: + tor_fragile_assert(); + // LCOV_EXCL_STOP + } +} + +/** Given two addresses <b>addr1</b> and <b>addr2</b>, return 0 if the two + * addresses are equivalent under the mask mbits, less than 0 if addr1 + * precedes addr2, and greater than 0 otherwise. + * + * Different address families (IPv4 vs IPv6) are always considered unequal if + * <b>how</b> is CMP_EXACT; otherwise, IPv6-mapped IPv4 addresses are + * considered equivalent to their IPv4 equivalents. + * + * As a special case, all pointer-wise distinct AF_UNIX addresses are always + * considered unequal since tor_addr_t currently does not contain the + * information required to make the comparison. + */ +int +tor_addr_compare(const tor_addr_t *addr1, const tor_addr_t *addr2, + tor_addr_comparison_t how) +{ + return tor_addr_compare_masked(addr1, addr2, 128, how); +} + +/** As tor_addr_compare(), but only looks at the first <b>mask</b> bits of + * the address. + * + * Reduce over-specific masks (>128 for ipv6, >32 for ipv4) to 128 or 32. + * + * The mask is interpreted relative to <b>addr1</b>, so that if a is + * \::ffff:1.2.3.4, and b is 3.4.5.6, + * tor_addr_compare_masked(a,b,100,CMP_SEMANTIC) is the same as + * -tor_addr_compare_masked(b,a,4,CMP_SEMANTIC). + * + * We guarantee that the ordering from tor_addr_compare_masked is a total + * order on addresses, but not that it is any particular order, or that it + * will be the same from one version to the next. + */ +int +tor_addr_compare_masked(const tor_addr_t *addr1, const tor_addr_t *addr2, + maskbits_t mbits, tor_addr_comparison_t how) +{ + /** Helper: Evaluates to -1 if a is less than b, 0 if a equals b, or 1 if a + * is greater than b. May evaluate a and b more than once. */ +#define TRISTATE(a,b) (((a)<(b))?-1: (((a)==(b))?0:1)) + sa_family_t family1, family2, v_family1, v_family2; + + tor_assert(addr1 && addr2); + + v_family1 = family1 = tor_addr_family(addr1); + v_family2 = family2 = tor_addr_family(addr2); + + if (family1==family2) { + /* When the families are the same, there's only one way to do the + * comparison: exactly. */ + int r; + switch (family1) { + case AF_UNSPEC: + return 0; /* All unspecified addresses are equal */ + case AF_INET: { + uint32_t a1 = tor_addr_to_ipv4h(addr1); + uint32_t a2 = tor_addr_to_ipv4h(addr2); + if (mbits <= 0) + return 0; + if (mbits > 32) + mbits = 32; + a1 >>= (32-mbits); + a2 >>= (32-mbits); + r = TRISTATE(a1, a2); + return r; + } + case AF_INET6: { + if (mbits > 128) + mbits = 128; + + const uint8_t *a1 = tor_addr_to_in6_addr8(addr1); + const uint8_t *a2 = tor_addr_to_in6_addr8(addr2); + const int bytes = mbits >> 3; + const int leftover_bits = mbits & 7; + if (bytes && (r = tor_memcmp(a1, a2, bytes))) { + return r; + } else if (leftover_bits) { + uint8_t b1 = a1[bytes] >> (8-leftover_bits); + uint8_t b2 = a2[bytes] >> (8-leftover_bits); + return TRISTATE(b1, b2); + } else { + return 0; + } + } + case AF_UNIX: + /* HACKHACKHACKHACKHACK: + * tor_addr_t doesn't contain a copy of sun_path, so it's not + * possible to compare this at all. + * + * Since the only time we currently actually should be comparing + * 2 AF_UNIX addresses is when dealing with ISO_CLIENTADDR (which + * is disabled for AF_UNIX SocksPorts anyway), this just does + * a pointer comparison. + * + * See: #20261. + */ + if (addr1 < addr2) + return -1; + else if (addr1 == addr2) + return 0; + else + return 1; + /* LCOV_EXCL_START */ + default: + tor_fragile_assert(); + return 0; + /* LCOV_EXCL_STOP */ + } + } else if (how == CMP_EXACT) { + /* Unequal families and an exact comparison? Stop now! */ + return TRISTATE(family1, family2); + } + + if (mbits == 0) + return 0; + + if (family1 == AF_INET6 && tor_addr_is_v4(addr1)) + v_family1 = AF_INET; + if (family2 == AF_INET6 && tor_addr_is_v4(addr2)) + v_family2 = AF_INET; + if (v_family1 == v_family2) { + /* One or both addresses are a mapped ipv4 address. */ + uint32_t a1, a2; + if (family1 == AF_INET6) { + a1 = tor_addr_to_mapped_ipv4h(addr1); + if (mbits <= 96) + return 0; + mbits -= 96; /* We just decided that the first 96 bits of a1 "match". */ + } else { + a1 = tor_addr_to_ipv4h(addr1); + } + if (family2 == AF_INET6) { + a2 = tor_addr_to_mapped_ipv4h(addr2); + } else { + a2 = tor_addr_to_ipv4h(addr2); + } + if (mbits > 32) mbits = 32; + a1 >>= (32-mbits); + a2 >>= (32-mbits); + return TRISTATE(a1, a2); + } else { + /* Unequal families, and semantic comparison, and no semantic family + * matches. */ + return TRISTATE(family1, family2); + } +} + +/** Input for siphash, to produce some output for an unspec value. */ +static const uint32_t unspec_hash_input[] = { 0x4e4df09f, 0x92985342 }; + +/** Return a hash code based on the address addr. DOCDOC extra */ +uint64_t +tor_addr_hash(const tor_addr_t *addr) +{ + switch (tor_addr_family(addr)) { + case AF_INET: + return siphash24g(&addr->addr.in_addr.s_addr, 4); + case AF_UNSPEC: + return siphash24g(unspec_hash_input, sizeof(unspec_hash_input)); + case AF_INET6: + return siphash24g(&addr->addr.in6_addr.s6_addr, 16); + /* LCOV_EXCL_START */ + default: + tor_fragile_assert(); + return 0; + /* LCOV_EXCL_STOP */ + } +} + +/** As tor_addr_hash, but use a particular siphash key. */ +uint64_t +tor_addr_keyed_hash(const struct sipkey *key, const tor_addr_t *addr) +{ + /* This is duplicate code with tor_addr_hash, since this function needs to + * be backportable all the way to 0.2.9. */ + + switch (tor_addr_family(addr)) { + case AF_INET: + return siphash24(&addr->addr.in_addr.s_addr, 4, key); + case AF_UNSPEC: + return siphash24(unspec_hash_input, sizeof(unspec_hash_input), key); + case AF_INET6: + return siphash24(&addr->addr.in6_addr.s6_addr, 16, key); + default: + /* LCOV_EXCL_START */ + tor_fragile_assert(); + return 0; + /* LCOV_EXCL_STOP */ + } +} + +/** Return a newly allocated string with a representation of <b>addr</b>. */ +char * +tor_addr_to_str_dup(const tor_addr_t *addr) +{ + char buf[TOR_ADDR_BUF_LEN]; + if (tor_addr_to_str(buf, addr, sizeof(buf), 0)) { + return tor_strdup(buf); + } else { + return tor_strdup("<unknown address type>"); + } +} + +/** Return a string representing the address <b>addr</b>. This string + * is statically allocated, and must not be freed. Each call to + * <b>fmt_addr_impl</b> invalidates the last result of the function. + * This function is not thread-safe. If <b>decorate</b> is set, add + * brackets to IPv6 addresses. + * + * It's better to use the wrapper macros of this function: + * <b>fmt_addr()</b> and <b>fmt_and_decorate_addr()</b>. + */ +const char * +fmt_addr_impl(const tor_addr_t *addr, int decorate) +{ + static char buf[TOR_ADDR_BUF_LEN]; + if (!addr) return "<null>"; + if (tor_addr_to_str(buf, addr, sizeof(buf), decorate)) + return buf; + else + return "???"; +} + +/** Return a string representing the pair <b>addr</b> and <b>port</b>. + * This calls fmt_and_decorate_addr internally, so IPv6 addresses will + * have brackets, and the caveats of fmt_addr_impl apply. + */ +const char * +fmt_addrport(const tor_addr_t *addr, uint16_t port) +{ + /* Add space for a colon and up to 5 digits. */ + static char buf[TOR_ADDR_BUF_LEN + 6]; + tor_snprintf(buf, sizeof(buf), "%s:%u", fmt_and_decorate_addr(addr), port); + return buf; +} + +/** Like fmt_addr(), but takes <b>addr</b> as a host-order IPv4 + * addresses. Also not thread-safe, also clobbers its return buffer on + * repeated calls. */ +const char * +fmt_addr32(uint32_t addr) +{ + static char buf[INET_NTOA_BUF_LEN]; + struct in_addr in; + in.s_addr = htonl(addr); + tor_inet_ntoa(&in, buf, sizeof(buf)); + return buf; +} + +/** Convert the string in <b>src</b> to a tor_addr_t <b>addr</b>. The string + * may be an IPv4 address, an IPv6 address, or an IPv6 address surrounded by + * square brackets. + * + * Return an address family on success, or -1 if an invalid address string is + * provided. */ +int +tor_addr_parse(tor_addr_t *addr, const char *src) +{ + /* Holds substring of IPv6 address after removing square brackets */ + char *tmp = NULL; + int result; + struct in_addr in_tmp; + struct in6_addr in6_tmp; + tor_assert(addr && src); + if (src[0] == '[' && src[1]) + src = tmp = tor_strndup(src+1, strlen(src)-2); + + if (tor_inet_pton(AF_INET6, src, &in6_tmp) > 0) { + result = AF_INET6; + tor_addr_from_in6(addr, &in6_tmp); + } else if (tor_inet_pton(AF_INET, src, &in_tmp) > 0) { + result = AF_INET; + tor_addr_from_in(addr, &in_tmp); + } else { + result = -1; + } + + tor_free(tmp); + return result; +} + +#ifdef HAVE_IFADDRS_TO_SMARTLIST +/* + * Convert a linked list consisting of <b>ifaddrs</b> structures + * into smartlist of <b>tor_addr_t</b> structures. + */ +STATIC smartlist_t * +ifaddrs_to_smartlist(const struct ifaddrs *ifa, sa_family_t family) +{ + smartlist_t *result = smartlist_new(); + const struct ifaddrs *i; + + for (i = ifa; i; i = i->ifa_next) { + tor_addr_t tmp; + if ((i->ifa_flags & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING)) + continue; + if (!i->ifa_addr) + continue; + if (i->ifa_addr->sa_family != AF_INET && + i->ifa_addr->sa_family != AF_INET6) + continue; + if (family != AF_UNSPEC && i->ifa_addr->sa_family != family) + continue; + if (tor_addr_from_sockaddr(&tmp, i->ifa_addr, NULL) < 0) + continue; + smartlist_add(result, tor_memdup(&tmp, sizeof(tmp))); + } + + return result; +} + +/** Use getiffaddrs() function to get list of current machine + * network interface addresses. Represent the result by smartlist of + * <b>tor_addr_t</b> structures. + */ +STATIC smartlist_t * +get_interface_addresses_ifaddrs(int severity, sa_family_t family) +{ + + /* Most free Unixy systems provide getifaddrs, which gives us a linked list + * of struct ifaddrs. */ + struct ifaddrs *ifa = NULL; + smartlist_t *result; + if (getifaddrs(&ifa) < 0) { + log_fn(severity, LD_NET, "Unable to call getifaddrs(): %s", + strerror(errno)); + return NULL; + } + + result = ifaddrs_to_smartlist(ifa, family); + + freeifaddrs(ifa); + + return result; +} +#endif /* defined(HAVE_IFADDRS_TO_SMARTLIST) */ + +#ifdef HAVE_IP_ADAPTER_TO_SMARTLIST + +/** Convert a Windows-specific <b>addresses</b> linked list into smartlist + * of <b>tor_addr_t</b> structures. + */ + +STATIC smartlist_t * +ip_adapter_addresses_to_smartlist(const IP_ADAPTER_ADDRESSES *addresses) +{ + smartlist_t *result = smartlist_new(); + const IP_ADAPTER_ADDRESSES *address; + + for (address = addresses; address; address = address->Next) { + const IP_ADAPTER_UNICAST_ADDRESS *a; + for (a = address->FirstUnicastAddress; a; a = a->Next) { + /* Yes, it's a linked list inside a linked list */ + const struct sockaddr *sa = a->Address.lpSockaddr; + tor_addr_t tmp; + if (sa->sa_family != AF_INET && sa->sa_family != AF_INET6) + continue; + if (tor_addr_from_sockaddr(&tmp, sa, NULL) < 0) + continue; + smartlist_add(result, tor_memdup(&tmp, sizeof(tmp))); + } + } + + return result; +} + +/** Windows only: use GetAdaptersAddresses() to retrieve the network interface + * addresses of the current machine. + * Returns a smartlist of <b>tor_addr_t</b> structures. + */ +STATIC smartlist_t * +get_interface_addresses_win32(int severity, sa_family_t family) +{ + smartlist_t *result = NULL; + ULONG size, res; + IP_ADAPTER_ADDRESSES *addresses = NULL; + + (void) severity; + +#define FLAGS (GAA_FLAG_SKIP_ANYCAST | \ + GAA_FLAG_SKIP_MULTICAST | \ + GAA_FLAG_SKIP_DNS_SERVER) + + /* Guess how much space we need. */ + size = 15*1024; + addresses = tor_malloc(size); + /* Exists in windows XP and later. */ + res = GetAdaptersAddresses(family, FLAGS, NULL, addresses, &size); + if (res == ERROR_BUFFER_OVERFLOW) { + /* we didn't guess that we needed enough space; try again */ + tor_free(addresses); + addresses = tor_malloc(size); + res = GetAdaptersAddresses(AF_UNSPEC, FLAGS, NULL, addresses, &size); + } + if (res != NO_ERROR) { + log_fn(severity, LD_NET, "GetAdaptersAddresses failed (result: %lu)", res); + goto done; + } + + result = ip_adapter_addresses_to_smartlist(addresses); + + done: + tor_free(addresses); + return result; +} + +#endif /* defined(HAVE_IP_ADAPTER_TO_SMARTLIST) */ + +#ifdef HAVE_IFCONF_TO_SMARTLIST + +/* Guess how much space we need. There shouldn't be any struct ifreqs + * larger than this, even on OS X where the struct's size is dynamic. */ +#define IFREQ_SIZE 4096 + +/* This is defined on Mac OS X */ +#ifndef _SIZEOF_ADDR_IFREQ +#define _SIZEOF_ADDR_IFREQ sizeof +#endif + +/* Free ifc->ifc_buf safely. */ +static void +ifconf_free_ifc_buf(struct ifconf *ifc) +{ + /* On macOS, tor_free() takes the address of ifc.ifc_buf, which leads to + * undefined behaviour, because pointer-to-pointers are expected to be + * aligned at 8-bytes, but the ifconf structure is packed. So we use + * raw_free() instead. */ + raw_free(ifc->ifc_buf); + ifc->ifc_buf = NULL; +} + +/** Convert <b>*buf</b>, an ifreq structure array of size <b>buflen</b>, + * into smartlist of <b>tor_addr_t</b> structures. + */ +STATIC smartlist_t * +ifreq_to_smartlist(char *buf, size_t buflen) +{ + smartlist_t *result = smartlist_new(); + char *end = buf + buflen; + + /* These acrobatics are due to alignment issues which trigger + * undefined behaviour traps on OSX. */ + struct ifreq *r = tor_malloc(IFREQ_SIZE); + + while (buf < end) { + /* Copy up to IFREQ_SIZE bytes into the struct ifreq, but don't overrun + * buf. */ + memcpy(r, buf, end - buf < IFREQ_SIZE ? end - buf : IFREQ_SIZE); + + const struct sockaddr *sa = &r->ifr_addr; + tor_addr_t tmp; + int valid_sa_family = (sa->sa_family == AF_INET || + sa->sa_family == AF_INET6); + + int conversion_success = (tor_addr_from_sockaddr(&tmp, sa, NULL) == 0); + + if (valid_sa_family && conversion_success) + smartlist_add(result, tor_memdup(&tmp, sizeof(tmp))); + + buf += _SIZEOF_ADDR_IFREQ(*r); + } + + tor_free(r); + return result; +} + +/** Use ioctl(.,SIOCGIFCONF,.) to get a list of current machine + * network interface addresses. Represent the result by smartlist of + * <b>tor_addr_t</b> structures. + */ +STATIC smartlist_t * +get_interface_addresses_ioctl(int severity, sa_family_t family) +{ + /* Some older unixy systems make us use ioctl(SIOCGIFCONF) */ + struct ifconf ifc; + ifc.ifc_buf = NULL; + int fd; + smartlist_t *result = NULL; + + /* This interface, AFAICT, only supports AF_INET addresses, + * except on AIX. For Solaris, we could use SIOCGLIFCONF. */ + + /* Bail out if family is neither AF_INET nor AF_UNSPEC since + * ioctl() technique supports non-IPv4 interface addresses on + * a small number of niche systems only. If family is AF_UNSPEC, + * fall back to getting AF_INET addresses only. */ + if (family == AF_UNSPEC) + family = AF_INET; + else if (family != AF_INET) + return NULL; + + fd = socket(family, SOCK_DGRAM, 0); + if (fd < 0) { + tor_log(severity, LD_NET, "socket failed: %s", strerror(errno)); + goto done; + } + + int mult = 1; + do { + mult *= 2; + ifc.ifc_len = mult * IFREQ_SIZE; + ifc.ifc_buf = tor_realloc(ifc.ifc_buf, ifc.ifc_len); + + tor_assert(ifc.ifc_buf); + + if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) { + tor_log(severity, LD_NET, "ioctl failed: %s", strerror(errno)); + goto done; + } + /* Ensure we have least IFREQ_SIZE bytes unused at the end. Otherwise, we + * don't know if we got everything during ioctl. */ + } while (mult * IFREQ_SIZE - ifc.ifc_len <= IFREQ_SIZE); + result = ifreq_to_smartlist(ifc.ifc_buf, ifc.ifc_len); + + done: + if (fd >= 0) + close(fd); + ifconf_free_ifc_buf(&ifc); + return result; +} +#endif /* defined(HAVE_IFCONF_TO_SMARTLIST) */ + +/** Try to ask our network interfaces what addresses they are bound to. + * Return a new smartlist of tor_addr_t on success, and NULL on failure. + * (An empty smartlist indicates that we successfully learned that we have no + * addresses.) Log failure messages at <b>severity</b>. Only return the + * interface addresses of requested <b>family</b> and ignore the addresses + * of other address families. */ +MOCK_IMPL(smartlist_t *, +get_interface_addresses_raw,(int severity, sa_family_t family)) +{ + smartlist_t *result = NULL; +#if defined(HAVE_IFADDRS_TO_SMARTLIST) + if ((result = get_interface_addresses_ifaddrs(severity, family))) + return result; +#endif +#if defined(HAVE_IP_ADAPTER_TO_SMARTLIST) + if ((result = get_interface_addresses_win32(severity, family))) + return result; +#endif +#if defined(HAVE_IFCONF_TO_SMARTLIST) + if ((result = get_interface_addresses_ioctl(severity, family))) + return result; +#endif + (void) severity; + (void) result; + return NULL; +} + +/** Return true iff <b>a</b> is a multicast address. */ +int +tor_addr_is_multicast(const tor_addr_t *a) +{ + sa_family_t family = tor_addr_family(a); + if (family == AF_INET) { + uint32_t ipv4h = tor_addr_to_ipv4h(a); + if ((ipv4h >> 24) == 0xe0) + return 1; /* Multicast */ + } else if (family == AF_INET6) { + const uint8_t *a32 = tor_addr_to_in6_addr8(a); + if (a32[0] == 0xff) + return 1; + } + return 0; +} + +/** Attempt to retrieve IP address of current host by utilizing some + * UDP socket trickery. Only look for address of given <b>family</b> + * (only AF_INET and AF_INET6 are supported). Set result to *<b>addr</b>. + * Return 0 on success, -1 on failure. + */ +MOCK_IMPL(int, +get_interface_address6_via_udp_socket_hack,(int severity, + sa_family_t family, + tor_addr_t *addr)) +{ + struct sockaddr_storage target_addr; + int sock=-1, r=-1; + socklen_t addr_len; + + memset(addr, 0, sizeof(tor_addr_t)); + memset(&target_addr, 0, sizeof(target_addr)); + + /* Don't worry: no packets are sent. We just need to use a real address + * on the actual Internet. */ + if (family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&target_addr; + /* Use the "discard" service port */ + sin6->sin6_port = htons(9); + sock = tor_open_socket(PF_INET6,SOCK_DGRAM,IPPROTO_UDP); + addr_len = (socklen_t)sizeof(struct sockaddr_in6); + sin6->sin6_family = AF_INET6; + S6_ADDR16(sin6->sin6_addr)[0] = htons(0x2002); /* 2002:: */ + } else if (family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in*)&target_addr; + /* Use the "discard" service port */ + sin->sin_port = htons(9); + sock = tor_open_socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP); + addr_len = (socklen_t)sizeof(struct sockaddr_in); + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = htonl(0x12000001); /* 18.0.0.1 */ + } else { + return -1; + } + + if (sock < 0) { + int e = tor_socket_errno(-1); + log_fn(severity, LD_NET, "unable to create socket: %s", + tor_socket_strerror(e)); + goto err; + } + + if (tor_connect_socket(sock,(struct sockaddr *)&target_addr, + addr_len) < 0) { + int e = tor_socket_errno(sock); + log_fn(severity, LD_NET, "connect() failed: %s", tor_socket_strerror(e)); + goto err; + } + + if (tor_addr_from_getsockname(addr, sock) < 0) { + int e = tor_socket_errno(sock); + log_fn(severity, LD_NET, "getsockname() to determine interface failed: %s", + tor_socket_strerror(e)); + goto err; + } + + if (tor_addr_is_loopback(addr) || tor_addr_is_multicast(addr)) { + log_fn(severity, LD_NET, "Address that we determined via UDP socket" + " magic is unsuitable for public comms."); + } else { + r=0; + } + + err: + if (sock >= 0) + tor_close_socket(sock); + if (r == -1) + memset(addr, 0, sizeof(tor_addr_t)); + return r; +} + +/** Set *<b>addr</b> to an arbitrary IP address (if any) of an interface that + * connects to the Internet. Prefer public IP addresses to internal IP + * addresses. This address should only be used in checking whether our + * address has changed, as it may be an internal IP address. Return 0 on + * success, -1 on failure. + * Prefer get_interface_address6_list for a list of all addresses on all + * interfaces which connect to the Internet. + */ +MOCK_IMPL(int, +get_interface_address6,(int severity, sa_family_t family, tor_addr_t *addr)) +{ + smartlist_t *addrs; + int rv = -1; + tor_assert(addr); + + memset(addr, 0, sizeof(tor_addr_t)); + + /* Get a list of public or internal IPs in arbitrary order */ + addrs = get_interface_address6_list(severity, family, 1); + + /* Find the first non-internal address, or the last internal address + * Ideally, we want the default route, see #12377 for details */ + SMARTLIST_FOREACH_BEGIN(addrs, tor_addr_t *, a) { + tor_addr_copy(addr, a); + rv = 0; + + /* If we found a non-internal address, declare success. Otherwise, + * keep looking. */ + if (!tor_addr_is_internal(a, 0)) + break; + } SMARTLIST_FOREACH_END(a); + + interface_address6_list_free(addrs); + return rv; +} + +/** Free a smartlist of IP addresses returned by get_interface_address6_list. + */ +void +interface_address6_list_free_(smartlist_t *addrs) +{ + if (addrs != NULL) { + SMARTLIST_FOREACH(addrs, tor_addr_t *, a, tor_free(a)); + smartlist_free(addrs); + } +} + +/** Return a smartlist of the IP addresses of type family from all interfaces + * on the server. Excludes loopback and multicast addresses. Only includes + * internal addresses if include_internal is true. (Note that a relay behind + * NAT may use an internal address to connect to the Internet.) + * An empty smartlist means that there are no addresses of the selected type + * matching these criteria. + * Returns NULL on failure. + * Use interface_address6_list_free to free the returned list. + */ +MOCK_IMPL(smartlist_t *, +get_interface_address6_list,(int severity, + sa_family_t family, + int include_internal)) +{ + smartlist_t *addrs; + tor_addr_t addr; + + /* Try to do this the smart way if possible. */ + if ((addrs = get_interface_addresses_raw(severity, family))) { + SMARTLIST_FOREACH_BEGIN(addrs, tor_addr_t *, a) + { + if (tor_addr_is_loopback(a) || + tor_addr_is_multicast(a)) { + SMARTLIST_DEL_CURRENT_KEEPORDER(addrs, a); + tor_free(a); + continue; + } + + if (!include_internal && tor_addr_is_internal(a, 0)) { + SMARTLIST_DEL_CURRENT_KEEPORDER(addrs, a); + tor_free(a); + continue; + } + } SMARTLIST_FOREACH_END(a); + } + + if (addrs && smartlist_len(addrs) > 0) { + return addrs; + } + + /* if we removed all entries as unsuitable */ + if (addrs) { + smartlist_free(addrs); + } + + /* Okay, the smart way is out. */ + addrs = smartlist_new(); + + if (family == AF_INET || family == AF_UNSPEC) { + if (get_interface_address6_via_udp_socket_hack(severity,AF_INET, + &addr) == 0) { + if (include_internal || !tor_addr_is_internal(&addr, 0)) { + smartlist_add(addrs, tor_memdup(&addr, sizeof(addr))); + } + } + } + + if (family == AF_INET6 || family == AF_UNSPEC) { + if (get_interface_address6_via_udp_socket_hack(severity,AF_INET6, + &addr) == 0) { + if (include_internal || !tor_addr_is_internal(&addr, 0)) { + smartlist_add(addrs, tor_memdup(&addr, sizeof(addr))); + } + } + } + + return addrs; +} + +/* ====== + * IPv4 helpers + * XXXX IPv6 deprecate some of these. + */ + +/** Given an address of the form "ip:port", try to divide it into its + * ip and port portions, setting *<b>address_out</b> to a newly + * allocated string holding the address portion and *<b>port_out</b> + * to the port. + * + * Don't do DNS lookups and don't allow domain names in the "ip" field. + * + * If <b>default_port</b> is less than 0, don't accept <b>addrport</b> of the + * form "ip" or "ip:0". Otherwise, accept those forms, and set + * *<b>port_out</b> to <b>default_port</b>. + * + * Return 0 on success, -1 on failure. */ +int +tor_addr_port_parse(int severity, const char *addrport, + tor_addr_t *address_out, uint16_t *port_out, + int default_port) +{ + int retval = -1; + int r; + char *addr_tmp = NULL; + + tor_assert(addrport); + tor_assert(address_out); + tor_assert(port_out); + + r = tor_addr_port_split(severity, addrport, &addr_tmp, port_out); + if (r < 0) + goto done; + + if (!*port_out) { + if (default_port >= 0) + *port_out = default_port; + else + goto done; + } + + /* make sure that address_out is an IP address */ + if (tor_addr_parse(address_out, addr_tmp) < 0) + goto done; + + retval = 0; + + done: + tor_free(addr_tmp); + return retval; +} + +/** Given an address of the form "host[:port]", try to divide it into its host + * and port portions, setting *<b>address_out</b> to a newly allocated string + * holding the address portion and *<b>port_out</b> to the port (or 0 if no + * port is given). Return 0 on success, -1 on failure. */ +int +tor_addr_port_split(int severity, const char *addrport, + char **address_out, uint16_t *port_out) +{ + tor_addr_t a_tmp; + tor_assert(addrport); + tor_assert(address_out); + tor_assert(port_out); + /* We need to check for IPv6 manually because the logic below doesn't + * do a good job on IPv6 addresses that lack a port. */ + if (tor_addr_parse(&a_tmp, addrport) == AF_INET6) { + *port_out = 0; + *address_out = tor_strdup(addrport); + return 0; + } + + const char *colon; + char *address_ = NULL; + int port_; + int ok = 1; + + colon = strrchr(addrport, ':'); + if (colon) { + address_ = tor_strndup(addrport, colon-addrport); + port_ = (int) tor_parse_long(colon+1,10,1,65535,NULL,NULL); + if (!port_) { + log_fn(severity, LD_GENERAL, "Port %s out of range", escaped(colon+1)); + ok = 0; + } + if (!port_out) { + char *esc_addrport = esc_for_log(addrport); + log_fn(severity, LD_GENERAL, + "Port %s given on %s when not required", + escaped(colon+1), esc_addrport); + tor_free(esc_addrport); + ok = 0; + } + } else { + address_ = tor_strdup(addrport); + port_ = 0; + } + + if (ok) { + *address_out = address_; + } else { + *address_out = NULL; + tor_free(address_); + } + + if (port_out) + *port_out = ok ? ((uint16_t) port_) : 0; + + return ok ? 0 : -1; +} + +/** If <b>mask</b> is an address mask for a bit-prefix, return the number of + * bits. Otherwise, return -1. */ +int +addr_mask_get_bits(uint32_t mask) +{ + int i; + if (mask == 0) + return 0; + if (mask == 0xFFFFFFFFu) + return 32; + for (i=1; i<=32; ++i) { + if (mask == (uint32_t) ~((1u<<(32-i))-1)) { + return i; + } + } + return -1; +} + +/** Parse a string <b>s</b> in the format of (*|port(-maxport)?)?, setting the + * various *out pointers as appropriate. Return 0 on success, -1 on failure. + */ +int +parse_port_range(const char *port, uint16_t *port_min_out, + uint16_t *port_max_out) +{ + int port_min, port_max, ok; + tor_assert(port_min_out); + tor_assert(port_max_out); + + if (!port || *port == '\0' || strcmp(port, "*") == 0) { + port_min = 1; + port_max = 65535; + } else { + char *endptr = NULL; + port_min = (int)tor_parse_long(port, 10, 0, 65535, &ok, &endptr); + if (!ok) { + log_warn(LD_GENERAL, + "Malformed port %s on address range; rejecting.", + escaped(port)); + return -1; + } else if (endptr && *endptr == '-') { + port = endptr+1; + endptr = NULL; + port_max = (int)tor_parse_long(port, 10, 1, 65535, &ok, &endptr); + if (!ok) { + log_warn(LD_GENERAL, + "Malformed port %s on address range; rejecting.", + escaped(port)); + return -1; + } + } else { + port_max = port_min; + } + if (port_min > port_max) { + log_warn(LD_GENERAL, "Insane port range on address policy; rejecting."); + return -1; + } + } + + if (port_min < 1) + port_min = 1; + if (port_max > 65535) + port_max = 65535; + + *port_min_out = (uint16_t) port_min; + *port_max_out = (uint16_t) port_max; + + return 0; +} + +/** Given a host-order <b>addr</b>, call tor_inet_ntop() on it + * and return a strdup of the resulting address. + */ +char * +tor_dup_ip(uint32_t addr) +{ + char buf[TOR_ADDR_BUF_LEN]; + struct in_addr in; + + in.s_addr = htonl(addr); + tor_inet_ntop(AF_INET, &in, buf, sizeof(buf)); + return tor_strdup(buf); +} + +/** + * Set *<b>addr</b> to a host-order IPv4 address (if any) of an + * interface that connects to the Internet. Prefer public IP addresses to + * internal IP addresses. This address should only be used in checking + * whether our address has changed, as it may be an internal IPv4 address. + * Return 0 on success, -1 on failure. + * Prefer get_interface_address_list6 for a list of all IPv4 and IPv6 + * addresses on all interfaces which connect to the Internet. + */ +MOCK_IMPL(int, +get_interface_address,(int severity, uint32_t *addr)) +{ + tor_addr_t local_addr; + int r; + + memset(addr, 0, sizeof(uint32_t)); + + r = get_interface_address6(severity, AF_INET, &local_addr); + if (r>=0) + *addr = tor_addr_to_ipv4h(&local_addr); + return r; +} + +/** Return true if we can tell that <b>name</b> is a canonical name for the + * loopback address. Return true also for *.local hostnames, which are + * multicast DNS names for hosts on the local network. */ +int +tor_addr_hostname_is_local(const char *name) +{ + return !strcasecmp(name, "localhost") || + !strcasecmp(name, "local") || + !strcasecmpend(name, ".local"); +} + +/** Return a newly allocated tor_addr_port_t with <b>addr</b> and + <b>port</b> filled in. */ +tor_addr_port_t * +tor_addr_port_new(const tor_addr_t *addr, uint16_t port) +{ + tor_addr_port_t *ap = tor_malloc_zero(sizeof(tor_addr_port_t)); + if (addr) + tor_addr_copy(&ap->addr, addr); + ap->port = port; + return ap; +} + +/** Return true iff <a>a</b> and <b>b</b> are the same address and port */ +int +tor_addr_port_eq(const tor_addr_port_t *a, + const tor_addr_port_t *b) +{ + return tor_addr_eq(&a->addr, &b->addr) && a->port == b->port; +} + +/** Return true if <b>string</b> represents a valid IPv4 adddress in + * 'a.b.c.d' form. + */ +int +string_is_valid_ipv4_address(const char *string) +{ + struct in_addr addr; + + return (tor_inet_pton(AF_INET,string,&addr) == 1); +} + +/** Return true if <b>string</b> represents a valid IPv6 address in + * a form that inet_pton() can parse. + */ +int +string_is_valid_ipv6_address(const char *string) +{ + struct in6_addr addr; + + return (tor_inet_pton(AF_INET6,string,&addr) == 1); +} + +/** Return true iff <b>string</b> is a valid destination address, + * i.e. either a DNS hostname or IPv4/IPv6 address string. + */ +int +string_is_valid_dest(const char *string) +{ + char *tmp = NULL; + int retval; + size_t len; + + if (string == NULL) + return 0; + + len = strlen(string); + + if (len == 0) + return 0; + + if (string[0] == '[' && string[len - 1] == ']') + string = tmp = tor_strndup(string + 1, len - 2); + + retval = string_is_valid_ipv4_address(string) || + string_is_valid_ipv6_address(string) || + string_is_valid_nonrfc_hostname(string); + + tor_free(tmp); + + return retval; +} + +/** Return true iff <b>string</b> matches a pattern of DNS names + * that we allow Tor clients to connect to. + * + * Note: This allows certain technically invalid characters ('_') to cope + * with misconfigured zones that have been encountered in the wild. + */ +int +string_is_valid_nonrfc_hostname(const char *string) +{ + int result = 1; + int has_trailing_dot; + char *last_label; + smartlist_t *components; + + if (!string || strlen(string) == 0) + return 0; + + if (string_is_valid_ipv4_address(string)) + return 0; + + components = smartlist_new(); + + smartlist_split_string(components,string,".",0,0); + + if (BUG(smartlist_len(components) == 0)) + return 0; // LCOV_EXCL_LINE should be impossible given the earlier checks. + + /* Allow a single terminating '.' used rarely to indicate domains + * are FQDNs rather than relative. */ + last_label = (char *)smartlist_get(components, + smartlist_len(components) - 1); + has_trailing_dot = (last_label[0] == '\0'); + if (has_trailing_dot) { + smartlist_pop_last(components); + tor_free(last_label); + last_label = NULL; + } + + SMARTLIST_FOREACH_BEGIN(components, char *, c) { + if ((c[0] == '-') || (*c == '_')) { + result = 0; + break; + } + + do { + result = (TOR_ISALNUM(*c) || (*c == '-') || (*c == '_')); + c++; + } while (result && *c); + + if (result == 0) { + break; + } + } SMARTLIST_FOREACH_END(c); + + SMARTLIST_FOREACH_BEGIN(components, char *, c) { + tor_free(c); + } SMARTLIST_FOREACH_END(c); + + smartlist_free(components); + + return result; +} diff --git a/src/lib/net/address.h b/src/lib/net/address.h new file mode 100644 index 0000000000..9b826c8359 --- /dev/null +++ b/src/lib/net/address.h @@ -0,0 +1,388 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file address.h + * \brief Headers for address.h + **/ + +#ifndef TOR_ADDRESS_H +#define TOR_ADDRESS_H + +#include "orconfig.h" +#include "lib/cc/torint.h" +#include "lib/log/util_bug.h" +#include "lib/net/inaddr_st.h" +#include "lib/net/nettypes.h" + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#endif + +#include <stddef.h> +#include <stdlib.h> + +#ifdef ADDRESS_PRIVATE + +#if defined(HAVE_SYS_IOCTL_H) +#include <sys/ioctl.h> +#endif + +#ifdef HAVE_GETIFADDRS +#define HAVE_IFADDRS_TO_SMARTLIST +#endif + +#ifdef _WIN32 +#define HAVE_IP_ADAPTER_TO_SMARTLIST +#endif + +#if defined(SIOCGIFCONF) && defined(HAVE_IOCTL) +#define HAVE_IFCONF_TO_SMARTLIST +#endif + +#if defined(HAVE_NET_IF_H) +#include <net/if.h> // for struct ifconf +#endif + +#if defined(HAVE_IFADDRS_TO_SMARTLIST) +#include <ifaddrs.h> +#endif + +// TODO win32 specific includes +#endif /* defined(ADDRESS_PRIVATE) */ + +/** The number of bits from an address to consider while doing a masked + * comparison. */ +typedef uint8_t maskbits_t; + +struct in_addr; +/** Holds an IPv4 or IPv6 address. (Uses less memory than struct + * sockaddr_storage.) */ +typedef struct tor_addr_t +{ + sa_family_t family; + union { + uint32_t dummy_; /* This field is here so we have something to initialize + * with a reliable cross-platform type. */ + struct in_addr in_addr; + struct in6_addr in6_addr; + } addr; +} tor_addr_t; + +/** Holds an IP address and a TCP/UDP port. */ +typedef struct tor_addr_port_t +{ + tor_addr_t addr; + uint16_t port; +} tor_addr_port_t; + +#define TOR_ADDR_NULL {AF_UNSPEC, {0}} + +/* XXXX To do: extract all of the functions here that can possibly invoke + * XXXX resolver, and make sure they have distinctive names. */ + +static inline const struct in6_addr *tor_addr_to_in6(const tor_addr_t *a); +static inline const struct in6_addr *tor_addr_to_in6_assert( + const tor_addr_t *a); +static inline uint32_t tor_addr_to_ipv4n(const tor_addr_t *a); +static inline uint32_t tor_addr_to_ipv4h(const tor_addr_t *a); +static inline uint32_t tor_addr_to_mapped_ipv4h(const tor_addr_t *a); +static inline sa_family_t tor_addr_family(const tor_addr_t *a); +static inline const struct in_addr *tor_addr_to_in(const tor_addr_t *a); +static inline int tor_addr_eq_ipv4h(const tor_addr_t *a, uint32_t u); + +socklen_t tor_addr_to_sockaddr(const tor_addr_t *a, uint16_t port, + struct sockaddr *sa_out, socklen_t len); +int tor_addr_from_sockaddr(tor_addr_t *a, const struct sockaddr *sa, + uint16_t *port_out); +void tor_addr_make_unspec(tor_addr_t *a); +void tor_addr_make_null(tor_addr_t *a, sa_family_t family); +char *tor_sockaddr_to_str(const struct sockaddr *sa); + +/** Return an in6_addr* equivalent to <b>a</b>, or NULL if <b>a</b> is not + * an IPv6 address. */ +static inline const struct in6_addr * +tor_addr_to_in6(const tor_addr_t *a) +{ + return a->family == AF_INET6 ? &a->addr.in6_addr : NULL; +} + +/** As tor_addr_to_in6, but assert that the address truly is an IPv6 + * address. */ +static inline const struct in6_addr * +tor_addr_to_in6_assert(const tor_addr_t *a) +{ + tor_assert(a->family == AF_INET6); + return &a->addr.in6_addr; +} + +/** Given an IPv6 address <b>x</b>, yield it as an array of uint8_t. + * + * Requires that <b>x</b> is actually an IPv6 address. + */ +#define tor_addr_to_in6_addr8(x) tor_addr_to_in6_assert(x)->s6_addr + +/** Given an IPv6 address <b>x</b>, yield it as an array of uint16_t. + * + * Requires that <b>x</b> is actually an IPv6 address. + */ +#define tor_addr_to_in6_addr16(x) S6_ADDR16(*tor_addr_to_in6_assert(x)) +/** Given an IPv6 address <b>x</b>, yield it as an array of uint32_t. + * + * Requires that <b>x</b> is actually an IPv6 address. + */ +#define tor_addr_to_in6_addr32(x) S6_ADDR32(*tor_addr_to_in6_assert(x)) + +/** Return an IPv4 address in network order for <b>a</b>, or 0 if + * <b>a</b> is not an IPv4 address. */ +static inline uint32_t +tor_addr_to_ipv4n(const tor_addr_t *a) +{ + return a->family == AF_INET ? a->addr.in_addr.s_addr : 0; +} +/** Return an IPv4 address in host order for <b>a</b>, or 0 if + * <b>a</b> is not an IPv4 address. */ +static inline uint32_t +tor_addr_to_ipv4h(const tor_addr_t *a) +{ + return ntohl(tor_addr_to_ipv4n(a)); +} +/** Given an IPv6 address, return its mapped IPv4 address in host order, or + * 0 if <b>a</b> is not an IPv6 address. + * + * (Does not check whether the address is really a mapped address */ +static inline uint32_t +tor_addr_to_mapped_ipv4h(const tor_addr_t *a) +{ + if (a->family == AF_INET6) { + uint32_t *addr32 = NULL; + // Work around an incorrect NULL pointer dereference warning in + // "clang --analyze" due to limited analysis depth + addr32 = tor_addr_to_in6_addr32(a); + // To improve performance, wrap this assertion in: + // #if !defined(__clang_analyzer__) || PARANOIA + tor_assert(addr32); + return ntohl(addr32[3]); + } else { + return 0; + } +} +/** Return the address family of <b>a</b>. Possible values are: + * AF_INET6, AF_INET, AF_UNSPEC. */ +static inline sa_family_t +tor_addr_family(const tor_addr_t *a) +{ + return a->family; +} +/** Return an in_addr* equivalent to <b>a</b>, or NULL if <b>a</b> is not + * an IPv4 address. */ +static inline const struct in_addr * +tor_addr_to_in(const tor_addr_t *a) +{ + return a->family == AF_INET ? &a->addr.in_addr : NULL; +} +/** Return true iff <b>a</b> is an IPv4 address equal to the host-ordered + * address in <b>u</b>. */ +static inline int +tor_addr_eq_ipv4h(const tor_addr_t *a, uint32_t u) +{ + return a->family == AF_INET ? (tor_addr_to_ipv4h(a) == u) : 0; +} + +/** Length of a buffer that you need to allocate to be sure you can encode + * any tor_addr_t. + * + * This allows enough space for + * "[ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255]", + * plus a terminating NUL. + */ +#define TOR_ADDR_BUF_LEN 48 + +char *tor_addr_to_str_dup(const tor_addr_t *addr) ATTR_MALLOC; + +/** Wrapper function of fmt_addr_impl(). It does not decorate IPv6 + * addresses. */ +#define fmt_addr(a) fmt_addr_impl((a), 0) +/** Wrapper function of fmt_addr_impl(). It decorates IPv6 + * addresses. */ +#define fmt_and_decorate_addr(a) fmt_addr_impl((a), 1) +const char *fmt_addr_impl(const tor_addr_t *addr, int decorate); +const char *fmt_addrport(const tor_addr_t *addr, uint16_t port); +const char * fmt_addr32(uint32_t addr); + +MOCK_DECL(int,get_interface_address6,(int severity, sa_family_t family, +tor_addr_t *addr)); +struct smartlist_t; +void interface_address6_list_free_(struct smartlist_t * addrs);// XXXX +#define interface_address6_list_free(addrs) \ + FREE_AND_NULL(struct smartlist_t, interface_address6_list_free_, (addrs)) +MOCK_DECL(struct smartlist_t *,get_interface_address6_list,(int severity, + sa_family_t family, + int include_internal)); + +/** Flag to specify how to do a comparison between addresses. In an "exact" + * comparison, addresses are equivalent only if they are in the same family + * with the same value. In a "semantic" comparison, IPv4 addresses match all + * IPv6 encodings of those addresses. */ +typedef enum { + CMP_EXACT, + CMP_SEMANTIC, +} tor_addr_comparison_t; + +int tor_addr_compare(const tor_addr_t *addr1, const tor_addr_t *addr2, + tor_addr_comparison_t how); +int tor_addr_compare_masked(const tor_addr_t *addr1, const tor_addr_t *addr2, + maskbits_t mask, tor_addr_comparison_t how); +/** Return true iff a and b are the same address. The comparison is done + * "exactly". */ +#define tor_addr_eq(a,b) (0==tor_addr_compare((a),(b),CMP_EXACT)) + +uint64_t tor_addr_hash(const tor_addr_t *addr); +struct sipkey; +uint64_t tor_addr_keyed_hash(const struct sipkey *key, const tor_addr_t *addr); +int tor_addr_is_v4(const tor_addr_t *addr); +int tor_addr_is_internal_(const tor_addr_t *ip, int for_listening, + const char *filename, int lineno); +#define tor_addr_is_internal(addr, for_listening) \ + tor_addr_is_internal_((addr), (for_listening), SHORT_FILE__, __LINE__) +int tor_addr_is_multicast(const tor_addr_t *a); + +/** Longest length that can be required for a reverse lookup name. */ +/* 32 nybbles, 32 dots, 8 characters of "ip6.arpa", 1 NUL: 73 characters. */ +#define REVERSE_LOOKUP_NAME_BUF_LEN 73 +int tor_addr_to_PTR_name(char *out, size_t outlen, + const tor_addr_t *addr); +int tor_addr_parse_PTR_name(tor_addr_t *result, const char *address, + int family, int accept_regular); + +/* Does the address * yield an AF_UNSPEC wildcard address (1), + * which expands to corresponding wildcard IPv4 and IPv6 rules, and do we + * allow *4 and *6 for IPv4 and IPv6 wildcards, respectively; + * or does the address * yield IPv4 wildcard address (0). */ +#define TAPMP_EXTENDED_STAR 1 +/* Does the address * yield an IPv4 wildcard address rule (1); + * or does it yield wildcard IPv4 and IPv6 rules (0) */ +#define TAPMP_STAR_IPV4_ONLY (1 << 1) +/* Does the address * yield an IPv6 wildcard address rule (1); + * or does it yield wildcard IPv4 and IPv6 rules (0) */ +#define TAPMP_STAR_IPV6_ONLY (1 << 2) +/* TAPMP_STAR_IPV4_ONLY and TAPMP_STAR_IPV6_ONLY are mutually exclusive. */ +int tor_addr_parse_mask_ports(const char *s, unsigned flags, + tor_addr_t *addr_out, maskbits_t *mask_out, + uint16_t *port_min_out, uint16_t *port_max_out); +const char * tor_addr_to_str(char *dest, const tor_addr_t *addr, size_t len, + int decorate); +int tor_addr_parse(tor_addr_t *addr, const char *src); +void tor_addr_copy(tor_addr_t *dest, const tor_addr_t *src); +void tor_addr_copy_tight(tor_addr_t *dest, const tor_addr_t *src); +void tor_addr_from_ipv4n(tor_addr_t *dest, uint32_t v4addr); +/** Set <b>dest</b> to the IPv4 address encoded in <b>v4addr</b> in host + * order. */ +#define tor_addr_from_ipv4h(dest, v4addr) \ + tor_addr_from_ipv4n((dest), htonl(v4addr)) +void tor_addr_from_ipv6_bytes(tor_addr_t *dest, const char *bytes); +/** Set <b>dest</b> to the IPv4 address incoded in <b>in</b>. */ +#define tor_addr_from_in(dest, in) \ + tor_addr_from_ipv4n((dest), (in)->s_addr); +void tor_addr_from_in6(tor_addr_t *dest, const struct in6_addr *in6); +int tor_addr_is_null(const tor_addr_t *addr); +int tor_addr_is_loopback(const tor_addr_t *addr); + +int tor_addr_is_valid(const tor_addr_t *addr, int for_listening); +int tor_addr_is_valid_ipv4n(uint32_t v4n_addr, int for_listening); +#define tor_addr_is_valid_ipv4h(v4h_addr, for_listening) \ + tor_addr_is_valid_ipv4n(htonl(v4h_addr), (for_listening)) +int tor_port_is_valid(uint16_t port, int for_listening); +/* Are addr and port both valid? */ +#define tor_addr_port_is_valid(addr, port, for_listening) \ + (tor_addr_is_valid((addr), (for_listening)) && \ + tor_port_is_valid((port), (for_listening))) +/* Are ap->addr and ap->port both valid? */ +#define tor_addr_port_is_valid_ap(ap, for_listening) \ + tor_addr_port_is_valid(&(ap)->addr, (ap)->port, (for_listening)) +/* Are the network-order v4addr and port both valid? */ +#define tor_addr_port_is_valid_ipv4n(v4n_addr, port, for_listening) \ + (tor_addr_is_valid_ipv4n((v4n_addr), (for_listening)) && \ + tor_port_is_valid((port), (for_listening))) +/* Are the host-order v4addr and port both valid? */ +#define tor_addr_port_is_valid_ipv4h(v4h_addr, port, for_listening) \ + (tor_addr_is_valid_ipv4h((v4h_addr), (for_listening)) && \ + tor_port_is_valid((port), (for_listening))) + +int tor_addr_port_split(int severity, const char *addrport, + char **address_out, uint16_t *port_out); + +int tor_addr_port_parse(int severity, const char *addrport, + tor_addr_t *address_out, uint16_t *port_out, + int default_port); + +int tor_addr_hostname_is_local(const char *name); + +/* IPv4 helpers */ +int parse_port_range(const char *port, uint16_t *port_min_out, + uint16_t *port_max_out); +int addr_mask_get_bits(uint32_t mask); +char *tor_dup_ip(uint32_t addr) ATTR_MALLOC; +MOCK_DECL(int,get_interface_address,(int severity, uint32_t *addr)); +#define interface_address_list_free(lst)\ + interface_address6_list_free(lst) +/** Return a smartlist of the IPv4 addresses of all interfaces on the server. + * Excludes loopback and multicast addresses. Only includes internal addresses + * if include_internal is true. (Note that a relay behind NAT may use an + * internal address to connect to the Internet.) + * An empty smartlist means that there are no IPv4 addresses. + * Returns NULL on failure. + * Use free_interface_address_list to free the returned list. + */ +static inline struct smartlist_t * +get_interface_address_list(int severity, int include_internal) +{ + return get_interface_address6_list(severity, AF_INET, include_internal); +} + +tor_addr_port_t *tor_addr_port_new(const tor_addr_t *addr, uint16_t port); +int tor_addr_port_eq(const tor_addr_port_t *a, + const tor_addr_port_t *b); + +int string_is_valid_dest(const char *string); +int string_is_valid_nonrfc_hostname(const char *string); +int string_is_valid_ipv4_address(const char *string); +int string_is_valid_ipv6_address(const char *string); + +#ifdef ADDRESS_PRIVATE +MOCK_DECL(struct smartlist_t *,get_interface_addresses_raw,(int severity, + sa_family_t family)); +MOCK_DECL(int,get_interface_address6_via_udp_socket_hack,(int severity, + sa_family_t family, + tor_addr_t *addr)); + +#ifdef HAVE_IFADDRS_TO_SMARTLIST +STATIC struct smartlist_t *ifaddrs_to_smartlist(const struct ifaddrs *ifa, + sa_family_t family); +STATIC struct smartlist_t *get_interface_addresses_ifaddrs(int severity, + sa_family_t family); +#endif /* defined(HAVE_IFADDRS_TO_SMARTLIST) */ + +#ifdef HAVE_IP_ADAPTER_TO_SMARTLIST +STATIC struct smartlist_t *ip_adapter_addresses_to_smartlist( + const IP_ADAPTER_ADDRESSES *addresses); +STATIC struct smartlist_t *get_interface_addresses_win32(int severity, + sa_family_t family); +#endif /* defined(HAVE_IP_ADAPTER_TO_SMARTLIST) */ + +#ifdef HAVE_IFCONF_TO_SMARTLIST +STATIC struct smartlist_t *ifreq_to_smartlist(char *ifr, + size_t buflen); +STATIC struct smartlist_t *get_interface_addresses_ioctl(int severity, + sa_family_t family); +#endif /* defined(HAVE_IFCONF_TO_SMARTLIST) */ + +#endif /* defined(ADDRESS_PRIVATE) */ + +#endif /* !defined(TOR_ADDRESS_H) */ diff --git a/src/lib/net/alertsock.c b/src/lib/net/alertsock.c new file mode 100644 index 0000000000..cc59d7d893 --- /dev/null +++ b/src/lib/net/alertsock.c @@ -0,0 +1,295 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file alertsock.c + * + * \brief Use a socket to alert the main thread from a worker thread. + * + * Because our main loop spends all of its time in select, epoll, kqueue, or + * etc, we need a way to wake up the main loop from another thread. This code + * tries to provide the fastest reasonable way to do that, depending on our + * platform. + **/ + +#include "orconfig.h" +#include "lib/net/alertsock.h" +#include "lib/net/socket.h" +#include "lib/log/util_bug.h" + +#ifdef HAVE_SYS_EVENTFD_H +#include <sys/eventfd.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef _WIN32 +#include <winsock2.h> +#endif + +#if defined(HAVE_EVENTFD) || defined(HAVE_PIPE) +/* As write(), but retry on EINTR, and return the negative error code on + * error. */ +static int +write_ni(int fd, const void *buf, size_t n) +{ + int r; + again: + r = (int) write(fd, buf, n); + if (r < 0) { + if (errno == EINTR) + goto again; + else + return -errno; + } + return r; +} +/* As read(), but retry on EINTR, and return the negative error code on error. + */ +static int +read_ni(int fd, void *buf, size_t n) +{ + int r; + again: + r = (int) read(fd, buf, n); + if (r < 0) { + if (errno == EINTR) + goto again; + else + return -errno; + } + return r; +} +#endif /* defined(HAVE_EVENTFD) || defined(HAVE_PIPE) */ + +/** As send(), but retry on EINTR, and return the negative error code on + * error. */ +static int +send_ni(int fd, const void *buf, size_t n, int flags) +{ + int r; + again: + r = (int) send(fd, buf, n, flags); + if (r < 0) { + int error = tor_socket_errno(fd); + if (ERRNO_IS_EINTR(error)) + goto again; + else + return -error; + } + return r; +} + +/** As recv(), but retry on EINTR, and return the negative error code on + * error. */ +static int +recv_ni(int fd, void *buf, size_t n, int flags) +{ + int r; + again: + r = (int) recv(fd, buf, n, flags); + if (r < 0) { + int error = tor_socket_errno(fd); + if (ERRNO_IS_EINTR(error)) + goto again; + else + return -error; + } + return r; +} + +#ifdef HAVE_EVENTFD +/* Increment the event count on an eventfd <b>fd</b> */ +static int +eventfd_alert(int fd) +{ + uint64_t u = 1; + int r = write_ni(fd, (void*)&u, sizeof(u)); + if (r < 0 && -r != EAGAIN) + return -1; + return 0; +} + +/* Drain all events from an eventfd <b>fd</b>. */ +static int +eventfd_drain(int fd) +{ + uint64_t u = 0; + int r = read_ni(fd, (void*)&u, sizeof(u)); + if (r < 0 && -r != EAGAIN) + return r; + return 0; +} +#endif /* defined(HAVE_EVENTFD) */ + +#ifdef HAVE_PIPE +/** Send a byte over a pipe. Return 0 on success or EAGAIN; -1 on error */ +static int +pipe_alert(int fd) +{ + ssize_t r = write_ni(fd, "x", 1); + if (r < 0 && -r != EAGAIN) + return (int)r; + return 0; +} + +/** Drain all input from a pipe <b>fd</b> and ignore it. Return 0 on + * success, -1 on error. */ +static int +pipe_drain(int fd) +{ + char buf[32]; + ssize_t r; + do { + r = read_ni(fd, buf, sizeof(buf)); + } while (r > 0); + if (r < 0 && errno != EAGAIN) + return -errno; + /* A value of r = 0 means EOF on the fd so successfully drained. */ + return 0; +} +#endif /* defined(HAVE_PIPE) */ + +/** Send a byte on socket <b>fd</b>t. Return 0 on success or EAGAIN, + * -1 on error. */ +static int +sock_alert(tor_socket_t fd) +{ + ssize_t r = send_ni(fd, "x", 1, 0); + if (r < 0 && !ERRNO_IS_EAGAIN(-r)) + return (int)r; + return 0; +} + +/** Drain all the input from a socket <b>fd</b>, and ignore it. Return 0 on + * success, -errno on error. */ +static int +sock_drain(tor_socket_t fd) +{ + char buf[32]; + ssize_t r; + do { + r = recv_ni(fd, buf, sizeof(buf), 0); + } while (r > 0); + if (r < 0 && !ERRNO_IS_EAGAIN(-r)) + return (int)r; + /* A value of r = 0 means EOF on the fd so successfully drained. */ + return 0; +} + +/** Allocate a new set of alert sockets, and set the appropriate function + * pointers, in <b>socks_out</b>. */ +int +alert_sockets_create(alert_sockets_t *socks_out, uint32_t flags) +{ + tor_socket_t socks[2] = { TOR_INVALID_SOCKET, TOR_INVALID_SOCKET }; + +#ifdef HAVE_EVENTFD + /* First, we try the Linux eventfd() syscall. This gives a 64-bit counter + * associated with a single file descriptor. */ +#if defined(EFD_CLOEXEC) && defined(EFD_NONBLOCK) + if (!(flags & ASOCKS_NOEVENTFD2)) + socks[0] = eventfd(0, EFD_CLOEXEC|EFD_NONBLOCK); +#endif + if (socks[0] < 0 && !(flags & ASOCKS_NOEVENTFD)) { + socks[0] = eventfd(0,0); + if (socks[0] >= 0) { + if (fcntl(socks[0], F_SETFD, FD_CLOEXEC) < 0 || + set_socket_nonblocking(socks[0]) < 0) { + // LCOV_EXCL_START -- if eventfd succeeds, fcntl will. + tor_assert_nonfatal_unreached(); + close(socks[0]); + return -1; + // LCOV_EXCL_STOP + } + } + } + if (socks[0] >= 0) { + socks_out->read_fd = socks_out->write_fd = socks[0]; + socks_out->alert_fn = eventfd_alert; + socks_out->drain_fn = eventfd_drain; + return 0; + } +#endif /* defined(HAVE_EVENTFD) */ + +#ifdef HAVE_PIPE2 + /* Now we're going to try pipes. First type the pipe2() syscall, if we + * have it, so we can save some calls... */ + if (!(flags & ASOCKS_NOPIPE2) && + pipe2(socks, O_NONBLOCK|O_CLOEXEC) == 0) { + socks_out->read_fd = socks[0]; + socks_out->write_fd = socks[1]; + socks_out->alert_fn = pipe_alert; + socks_out->drain_fn = pipe_drain; + return 0; + } +#endif /* defined(HAVE_PIPE2) */ + +#ifdef HAVE_PIPE + /* Now try the regular pipe() syscall. Pipes have a bit lower overhead than + * socketpairs, fwict. */ + if (!(flags & ASOCKS_NOPIPE) && + pipe(socks) == 0) { + if (fcntl(socks[0], F_SETFD, FD_CLOEXEC) < 0 || + fcntl(socks[1], F_SETFD, FD_CLOEXEC) < 0 || + set_socket_nonblocking(socks[0]) < 0 || + set_socket_nonblocking(socks[1]) < 0) { + // LCOV_EXCL_START -- if pipe succeeds, you can fcntl the output + tor_assert_nonfatal_unreached(); + close(socks[0]); + close(socks[1]); + return -1; + // LCOV_EXCL_STOP + } + socks_out->read_fd = socks[0]; + socks_out->write_fd = socks[1]; + socks_out->alert_fn = pipe_alert; + socks_out->drain_fn = pipe_drain; + return 0; + } +#endif /* defined(HAVE_PIPE) */ + + /* If nothing else worked, fall back on socketpair(). */ + if (!(flags & ASOCKS_NOSOCKETPAIR) && + tor_socketpair(AF_UNIX, SOCK_STREAM, 0, socks) == 0) { + if (set_socket_nonblocking(socks[0]) < 0 || + set_socket_nonblocking(socks[1])) { + // LCOV_EXCL_START -- if socketpair worked, you can make it nonblocking. + tor_assert_nonfatal_unreached(); + tor_close_socket(socks[0]); + tor_close_socket(socks[1]); + return -1; + // LCOV_EXCL_STOP + } + socks_out->read_fd = socks[0]; + socks_out->write_fd = socks[1]; + socks_out->alert_fn = sock_alert; + socks_out->drain_fn = sock_drain; + return 0; + } + return -1; +} + +/** Close the sockets in <b>socks</b>. */ +void +alert_sockets_close(alert_sockets_t *socks) +{ + if (socks->alert_fn == sock_alert) { + /* they are sockets. */ + tor_close_socket(socks->read_fd); + tor_close_socket(socks->write_fd); + } else { + close(socks->read_fd); + if (socks->write_fd != socks->read_fd) + close(socks->write_fd); + } + socks->read_fd = socks->write_fd = -1; +} diff --git a/src/lib/net/alertsock.h b/src/lib/net/alertsock.h new file mode 100644 index 0000000000..c45f42be81 --- /dev/null +++ b/src/lib/net/alertsock.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file alertsock.h + * + * \brief Header for alertsock.c + **/ + +#ifndef TOR_ALERTSOCK_H +#define TOR_ALERTSOCK_H + +#include "orconfig.h" +#include "lib/net/nettypes.h" +#include "lib/cc/torint.h" + +/** Helper type used to manage waking up the main thread while it's in + * the libevent main loop. Used by the work queue code. */ +typedef struct alert_sockets_t { + /* XXXX This structure needs a better name. */ + /** Socket that the main thread should listen for EV_READ events on. + * Note that this socket may be a regular fd on a non-Windows platform. + */ + tor_socket_t read_fd; + /** Socket to use when alerting the main thread. */ + tor_socket_t write_fd; + /** Function to alert the main thread */ + int (*alert_fn)(tor_socket_t write_fd); + /** Function to make the main thread no longer alerted. */ + int (*drain_fn)(tor_socket_t read_fd); +} alert_sockets_t; + +/* Flags to disable one or more alert_sockets backends. */ +#define ASOCKS_NOEVENTFD2 (1u<<0) +#define ASOCKS_NOEVENTFD (1u<<1) +#define ASOCKS_NOPIPE2 (1u<<2) +#define ASOCKS_NOPIPE (1u<<3) +#define ASOCKS_NOSOCKETPAIR (1u<<4) + +int alert_sockets_create(alert_sockets_t *socks_out, uint32_t flags); +void alert_sockets_close(alert_sockets_t *socks); + +#endif diff --git a/src/lib/net/buffers_net.c b/src/lib/net/buffers_net.c new file mode 100644 index 0000000000..3eb0a033d5 --- /dev/null +++ b/src/lib/net/buffers_net.c @@ -0,0 +1,202 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file buffers_net.c + * \brief Read and write data on a buf_t object. + **/ + +#define BUFFERS_PRIVATE +#include "lib/net/buffers_net.h" +#include "lib/container/buffers.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/net/nettypes.h" + +#ifdef _WIN32 +#include <winsock2.h> +#endif + +#include <stdlib.h> + +#ifdef PARANOIA +/** Helper: If PARANOIA is defined, assert that the buffer in local variable + * <b>buf</b> is well-formed. */ +#define check() STMT_BEGIN buf_assert_ok(buf); STMT_END +#else +#define check() STMT_NIL +#endif /* defined(PARANOIA) */ + +/** Read up to <b>at_most</b> bytes from the socket <b>fd</b> into + * <b>chunk</b> (which must be on <b>buf</b>). If we get an EOF, set + * *<b>reached_eof</b> to 1. Return -1 on error, 0 on eof or blocking, + * and the number of bytes read otherwise. */ +static inline int +read_to_chunk(buf_t *buf, chunk_t *chunk, tor_socket_t fd, size_t at_most, + int *reached_eof, int *socket_error) +{ + ssize_t read_result; + if (at_most > CHUNK_REMAINING_CAPACITY(chunk)) + at_most = CHUNK_REMAINING_CAPACITY(chunk); + read_result = tor_socket_recv(fd, CHUNK_WRITE_PTR(chunk), at_most, 0); + + if (read_result < 0) { + int e = tor_socket_errno(fd); + if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */ +#ifdef _WIN32 + if (e == WSAENOBUFS) + log_warn(LD_NET,"recv() failed: WSAENOBUFS. Not enough ram?"); +#endif + *socket_error = e; + return -1; + } + return 0; /* would block. */ + } else if (read_result == 0) { + log_debug(LD_NET,"Encountered eof on fd %d", (int)fd); + *reached_eof = 1; + return 0; + } else { /* actually got bytes. */ + buf->datalen += read_result; + chunk->datalen += read_result; + log_debug(LD_NET,"Read %ld bytes. %d on inbuf.", (long)read_result, + (int)buf->datalen); + tor_assert(read_result < INT_MAX); + return (int)read_result; + } +} + +/** Read from socket <b>s</b>, writing onto end of <b>buf</b>. Read at most + * <b>at_most</b> bytes, growing the buffer as necessary. If recv() returns 0 + * (because of EOF), set *<b>reached_eof</b> to 1 and return 0. Return -1 on + * error; else return the number of bytes read. + */ +/* XXXX indicate "read blocked" somehow? */ +int +buf_read_from_socket(buf_t *buf, tor_socket_t s, size_t at_most, + int *reached_eof, + int *socket_error) +{ + /* XXXX It's stupid to overload the return values for these functions: + * "error status" and "number of bytes read" are not mutually exclusive. + */ + int r = 0; + size_t total_read = 0; + + check(); + tor_assert(reached_eof); + tor_assert(SOCKET_OK(s)); + + if (BUG(buf->datalen >= INT_MAX)) + return -1; + if (BUG(buf->datalen >= INT_MAX - at_most)) + return -1; + + while (at_most > total_read) { + size_t readlen = at_most - total_read; + chunk_t *chunk; + if (!buf->tail || CHUNK_REMAINING_CAPACITY(buf->tail) < MIN_READ_LEN) { + chunk = buf_add_chunk_with_capacity(buf, at_most, 1); + if (readlen > chunk->memlen) + readlen = chunk->memlen; + } else { + size_t cap = CHUNK_REMAINING_CAPACITY(buf->tail); + chunk = buf->tail; + if (cap < readlen) + readlen = cap; + } + + r = read_to_chunk(buf, chunk, s, readlen, reached_eof, socket_error); + check(); + if (r < 0) + return r; /* Error */ + tor_assert(total_read+r < INT_MAX); + total_read += r; + if ((size_t)r < readlen) { /* eof, block, or no more to read. */ + break; + } + } + return (int)total_read; +} + +/** Helper for buf_flush_to_socket(): try to write <b>sz</b> bytes from chunk + * <b>chunk</b> of buffer <b>buf</b> onto socket <b>s</b>. On success, deduct + * the bytes written from *<b>buf_flushlen</b>. Return the number of bytes + * written on success, 0 on blocking, -1 on failure. + */ +static inline int +flush_chunk(tor_socket_t s, buf_t *buf, chunk_t *chunk, size_t sz, + size_t *buf_flushlen) +{ + ssize_t write_result; + + if (sz > chunk->datalen) + sz = chunk->datalen; + write_result = tor_socket_send(s, chunk->data, sz, 0); + + if (write_result < 0) { + int e = tor_socket_errno(s); + if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */ +#ifdef _WIN32 + if (e == WSAENOBUFS) + log_warn(LD_NET,"write() failed: WSAENOBUFS. Not enough ram?"); +#endif + return -1; + } + log_debug(LD_NET,"write() would block, returning."); + return 0; + } else { + *buf_flushlen -= write_result; + buf_drain(buf, write_result); + tor_assert(write_result < INT_MAX); + return (int)write_result; + } +} + +/** Write data from <b>buf</b> to the socket <b>s</b>. Write at most + * <b>sz</b> bytes, decrement *<b>buf_flushlen</b> by + * the number of bytes actually written, and remove the written bytes + * from the buffer. Return the number of bytes written on success, + * -1 on failure. Return 0 if write() would block. + */ +int +buf_flush_to_socket(buf_t *buf, tor_socket_t s, size_t sz, + size_t *buf_flushlen) +{ + /* XXXX It's stupid to overload the return values for these functions: + * "error status" and "number of bytes flushed" are not mutually exclusive. + */ + int r; + size_t flushed = 0; + tor_assert(buf_flushlen); + tor_assert(SOCKET_OK(s)); + if (BUG(*buf_flushlen > buf->datalen)) { + *buf_flushlen = buf->datalen; + } + if (BUG(sz > *buf_flushlen)) { + sz = *buf_flushlen; + } + + check(); + while (sz) { + size_t flushlen0; + tor_assert(buf->head); + if (buf->head->datalen >= sz) + flushlen0 = sz; + else + flushlen0 = buf->head->datalen; + + r = flush_chunk(s, buf, buf->head, flushlen0, buf_flushlen); + check(); + if (r < 0) + return r; + flushed += r; + sz -= r; + if (r == 0 || (size_t)r < flushlen0) /* can't flush any more now. */ + break; + } + tor_assert(flushed < INT_MAX); + return (int)flushed; +} diff --git a/src/lib/net/buffers_net.h b/src/lib/net/buffers_net.h new file mode 100644 index 0000000000..5f69bebedf --- /dev/null +++ b/src/lib/net/buffers_net.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file buffers_net.h + * + * \brief Header file for buffers_net.c. + **/ + +#ifndef TOR_BUFFERS_NET_H +#define TOR_BUFFERS_NET_H + +#include <stddef.h> +#include "lib/net/socket.h" + +struct buf_t; +int buf_read_from_socket(struct buf_t *buf, tor_socket_t s, size_t at_most, + int *reached_eof, + int *socket_error); + +int buf_flush_to_socket(struct buf_t *buf, tor_socket_t s, size_t sz, + size_t *buf_flushlen); + +#endif /* !defined(TOR_BUFFERS_H) */ diff --git a/src/lib/net/gethostname.c b/src/lib/net/gethostname.c new file mode 100644 index 0000000000..e54a1ea16e --- /dev/null +++ b/src/lib/net/gethostname.c @@ -0,0 +1,30 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file gethostname.c + * \brief Mockable wrapper for gethostname(). + */ + +#include "orconfig.h" +#include "lib/net/gethostname.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef _WIN32 +#include <winsock2.h> +#endif + +/** Get name of current host and write it to <b>name</b> array, whose + * length is specified by <b>namelen</b> argument. Return 0 upon + * successful completion; otherwise return return -1. (Currently, + * this function is merely a mockable wrapper for POSIX gethostname().) + */ +MOCK_IMPL(int, +tor_gethostname,(char *name, size_t namelen)) +{ + return gethostname(name,namelen); +} diff --git a/src/lib/net/gethostname.h b/src/lib/net/gethostname.h new file mode 100644 index 0000000000..69b0528bc0 --- /dev/null +++ b/src/lib/net/gethostname.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file gethostname.h + * \brief Header for gethostname.c + **/ + +#ifndef TOR_GETHOSTNAME_H +#define TOR_GETHOSTNAME_H + +#include "lib/testsupport/testsupport.h" +#include <stddef.h> + +MOCK_DECL(int,tor_gethostname,(char *name, size_t namelen)); + +#endif diff --git a/src/lib/net/inaddr.c b/src/lib/net/inaddr.c new file mode 100644 index 0000000000..1a2406ce5f --- /dev/null +++ b/src/lib/net/inaddr.c @@ -0,0 +1,267 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file inaddr.c + * \brief Convert in_addr and in6_addr to and from strings. + **/ + +#include "lib/net/inaddr.h" + +#include "lib/cc/torint.h" +#include "lib/log/util_bug.h" +#include "lib/net/inaddr_st.h" +#include "lib/string/compat_ctype.h" +#include "lib/string/compat_string.h" +#include "lib/string/printf.h" +#include "lib/string/scanf.h" +#include "lib/string/util_string.h" + +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#include <stdlib.h> +#include <string.h> + +#ifdef _WIN32 +#include <winsock2.h> +#endif + +/** Set *addr to the IP address (in dotted-quad notation) stored in *str. + * Return 1 on success, 0 if *str is badly formatted. + * (Like inet_aton(str,addr), but works on Windows and Solaris.) + */ +int +tor_inet_aton(const char *str, struct in_addr* addr) +{ + unsigned a,b,c,d; + char more; + if (tor_sscanf(str, "%3u.%3u.%3u.%3u%c", &a,&b,&c,&d,&more) != 4) + return 0; + if (a > 255) return 0; + if (b > 255) return 0; + if (c > 255) return 0; + if (d > 255) return 0; + addr->s_addr = htonl((a<<24) | (b<<16) | (c<<8) | d); + return 1; +} + +/** Given an IPv4 in_addr struct *<b>in</b> (in network order, as usual), + * write it as a string into the <b>buf_len</b>-byte buffer in + * <b>buf</b>. Returns a non-negative integer on success. + * Returns -1 on failure. + */ +int +tor_inet_ntoa(const struct in_addr *in, char *buf, size_t buf_len) +{ + uint32_t a = ntohl(in->s_addr); + return tor_snprintf(buf, buf_len, "%d.%d.%d.%d", + (int)(uint8_t)((a>>24)&0xff), + (int)(uint8_t)((a>>16)&0xff), + (int)(uint8_t)((a>>8 )&0xff), + (int)(uint8_t)((a )&0xff)); +} + +/** Given <b>af</b>==AF_INET and <b>src</b> a struct in_addr, or + * <b>af</b>==AF_INET6 and <b>src</b> a struct in6_addr, try to format the + * address and store it in the <b>len</b>-byte buffer <b>dst</b>. Returns + * <b>dst</b> on success, NULL on failure. + * + * (Like inet_ntop(af,src,dst,len), but works on platforms that don't have it: + * Tor sometimes needs to format ipv6 addresses even on platforms without ipv6 + * support.) */ +const char * +tor_inet_ntop(int af, const void *src, char *dst, size_t len) +{ + if (af == AF_INET) { + if (tor_inet_ntoa(src, dst, len) < 0) + return NULL; + else + return dst; + } else if (af == AF_INET6) { + const struct in6_addr *addr = src; + char buf[64], *cp; + int longestGapLen = 0, longestGapPos = -1, i, + curGapPos = -1, curGapLen = 0; + uint16_t words[8]; + for (i = 0; i < 8; ++i) { + words[i] = (((uint16_t)addr->s6_addr[2*i])<<8) + addr->s6_addr[2*i+1]; + } + if (words[0] == 0 && words[1] == 0 && words[2] == 0 && words[3] == 0 && + words[4] == 0 && ((words[5] == 0 && words[6] && words[7]) || + (words[5] == 0xffff))) { + /* This is an IPv4 address. */ + if (words[5] == 0) { + tor_snprintf(buf, sizeof(buf), "::%d.%d.%d.%d", + addr->s6_addr[12], addr->s6_addr[13], + addr->s6_addr[14], addr->s6_addr[15]); + } else { + tor_snprintf(buf, sizeof(buf), "::%x:%d.%d.%d.%d", words[5], + addr->s6_addr[12], addr->s6_addr[13], + addr->s6_addr[14], addr->s6_addr[15]); + } + if ((strlen(buf) + 1) > len) /* +1 for \0 */ + return NULL; + strlcpy(dst, buf, len); + return dst; + } + i = 0; + while (i < 8) { + if (words[i] == 0) { + curGapPos = i++; + curGapLen = 1; + while (i<8 && words[i] == 0) { + ++i; ++curGapLen; + } + if (curGapLen > longestGapLen) { + longestGapPos = curGapPos; + longestGapLen = curGapLen; + } + } else { + ++i; + } + } + if (longestGapLen<=1) + longestGapPos = -1; + + cp = buf; + for (i = 0; i < 8; ++i) { + if (words[i] == 0 && longestGapPos == i) { + if (i == 0) + *cp++ = ':'; + *cp++ = ':'; + while (i < 8 && words[i] == 0) + ++i; + --i; /* to compensate for loop increment. */ + } else { + tor_snprintf(cp, sizeof(buf)-(cp-buf), "%x", (unsigned)words[i]); + cp += strlen(cp); + if (i != 7) + *cp++ = ':'; + } + } + *cp = '\0'; + if ((strlen(buf) + 1) > len) /* +1 for \0 */ + return NULL; + strlcpy(dst, buf, len); + return dst; + } else { + return NULL; + } +} + +/** Given <b>af</b>==AF_INET or <b>af</b>==AF_INET6, and a string <b>src</b> + * encoding an IPv4 address or IPv6 address correspondingly, try to parse the + * address and store the result in <b>dst</b> (which must have space for a + * struct in_addr or a struct in6_addr, as appropriate). Return 1 on success, + * 0 on a bad parse, and -1 on a bad <b>af</b>. + * + * (Like inet_pton(af,src,dst) but works on platforms that don't have it: Tor + * sometimes needs to format ipv6 addresses even on platforms without ipv6 + * support.) */ +int +tor_inet_pton(int af, const char *src, void *dst) +{ + if (af == AF_INET) { + return tor_inet_aton(src, dst); + } else if (af == AF_INET6) { + struct in6_addr *out = dst; + uint16_t words[8]; + int gapPos = -1, i, setWords=0; + const char *dot = strchr(src, '.'); + const char *eow; /* end of words. */ + memset(words, 0xf8, sizeof(words)); + if (dot == src) + return 0; + else if (!dot) + eow = src+strlen(src); + else { + unsigned byte1,byte2,byte3,byte4; + char more; + for (eow = dot-1; eow > src && TOR_ISDIGIT(*eow); --eow) + ; + if (*eow != ':') + return 0; + ++eow; + + /* We use "scanf" because some platform inet_aton()s are too lax + * about IPv4 addresses of the form "1.2.3" */ + if (tor_sscanf(eow, "%3u.%3u.%3u.%3u%c", + &byte1,&byte2,&byte3,&byte4,&more) != 4) + return 0; + + if (byte1 > 255 || byte2 > 255 || byte3 > 255 || byte4 > 255) + return 0; + + words[6] = (byte1<<8) | byte2; + words[7] = (byte3<<8) | byte4; + setWords += 2; + } + + i = 0; + while (src < eow) { + if (i > 7) + return 0; + if (TOR_ISXDIGIT(*src)) { + char *next; + ssize_t len; + long r = strtol(src, &next, 16); + if (next == NULL || next == src) { + /* The 'next == src' error case can happen on versions of openbsd + * which treat "0xfoo" as an error, rather than as "0" followed by + * "xfoo". */ + return 0; + } + + len = *next == '\0' ? eow - src : next - src; + if (len > 4) + return 0; + if (len > 1 && !TOR_ISXDIGIT(src[1])) + return 0; /* 0x is not valid */ + + tor_assert(r >= 0); + tor_assert(r < 65536); + words[i++] = (uint16_t)r; + setWords++; + src = next; + if (*src != ':' && src != eow) + return 0; + ++src; + } else if (*src == ':' && i > 0 && gapPos == -1) { + gapPos = i; + ++src; + } else if (*src == ':' && i == 0 && src+1 < eow && src[1] == ':' && + gapPos == -1) { + gapPos = i; + src += 2; + } else { + return 0; + } + } + + if (setWords > 8 || + (setWords == 8 && gapPos != -1) || + (setWords < 8 && gapPos == -1)) + return 0; + + if (gapPos >= 0) { + int nToMove = setWords - (dot ? 2 : 0) - gapPos; + int gapLen = 8 - setWords; + tor_assert(nToMove >= 0); + memmove(&words[gapPos+gapLen], &words[gapPos], + sizeof(uint16_t)*nToMove); + memset(&words[gapPos], 0, sizeof(uint16_t)*gapLen); + } + for (i = 0; i < 8; ++i) { + out->s6_addr[2*i ] = words[i] >> 8; + out->s6_addr[2*i+1] = words[i] & 0xff; + } + + return 1; + } else { + return -1; + } +} diff --git a/src/lib/net/inaddr.h b/src/lib/net/inaddr.h new file mode 100644 index 0000000000..36352b65ea --- /dev/null +++ b/src/lib/net/inaddr.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file inaddr.h + * \brief Header for inaddr.c. + **/ + +#ifndef TOR_INADDR_H +#define TOR_INADDR_H + +#include "orconfig.h" +#include <stddef.h> + +struct in_addr; + +int tor_inet_aton(const char *str, struct in_addr *addr); +/** Length of a buffer to allocate to hold the results of tor_inet_ntoa.*/ +#define INET_NTOA_BUF_LEN 16 +int tor_inet_ntoa(const struct in_addr *in, char *buf, size_t buf_len); + +const char *tor_inet_ntop(int af, const void *src, char *dst, size_t len); +int tor_inet_pton(int af, const char *src, void *dst); + +#endif diff --git a/src/lib/net/inaddr_st.h b/src/lib/net/inaddr_st.h new file mode 100644 index 0000000000..806f2c096a --- /dev/null +++ b/src/lib/net/inaddr_st.h @@ -0,0 +1,107 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file inaddr_st.h + * + * \brief Define in6_addr, its members, and related types on platforms that + * lack it. + **/ + +#ifndef TOR_INADDR_ST_H +#define TOR_INADDR_ST_H + +#include "orconfig.h" +#include <stddef.h> + +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETINET_IN6_H +#include <netinet/in6.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#ifdef _WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#include <windows.h> +#endif + +#include "lib/cc/torint.h" + +struct in_addr; + +/** Implementation of struct in6_addr for platforms that do not have it. + * Generally, these platforms are ones without IPv6 support, but we want to + * have a working in6_addr there anyway, so we can use it to parse IPv6 + * addresses. */ +#if !defined(HAVE_STRUCT_IN6_ADDR) +struct in6_addr +{ + union { + uint8_t u6_addr8[16]; + uint16_t u6_addr16[8]; + uint32_t u6_addr32[4]; + } in6_u; +#define s6_addr in6_u.u6_addr8 +#define s6_addr16 in6_u.u6_addr16 +#define s6_addr32 in6_u.u6_addr32 +}; +#endif /* !defined(HAVE_STRUCT_IN6_ADDR) */ + +/** @{ */ +/** Many BSD variants seem not to define these. */ +#if defined(__APPLE__) || defined(__darwin__) || \ + defined(__FreeBSD__) || defined(__NetBSD__) || defined(OpenBSD) +#ifndef s6_addr16 +#define s6_addr16 __u6_addr.__u6_addr16 +#endif +#ifndef s6_addr32 +#define s6_addr32 __u6_addr.__u6_addr32 +#endif +#endif /* defined(__APPLE__) || defined(__darwin__) || ... */ +/** @} */ + +#ifndef HAVE_SA_FAMILY_T +typedef uint16_t sa_family_t; +#endif + +/** @{ */ +/** Apparently, MS and Solaris don't define s6_addr16 or s6_addr32; these + * macros get you a pointer to s6_addr32 or local equivalent. */ +#ifdef HAVE_STRUCT_IN6_ADDR_S6_ADDR32 +#define S6_ADDR32(x) ((uint32_t*)(x).s6_addr32) +#else +#define S6_ADDR32(x) ((uint32_t*)((char*)&(x).s6_addr)) +#endif +#ifdef HAVE_STRUCT_IN6_ADDR_S6_ADDR16 +#define S6_ADDR16(x) ((uint16_t*)(x).s6_addr16) +#else +#define S6_ADDR16(x) ((uint16_t*)((char*)&(x).s6_addr)) +#endif +/** @} */ + +/** Implementation of struct sockaddr_in6 on platforms that do not have + * it. See notes on struct in6_addr. */ +#if !defined(HAVE_STRUCT_SOCKADDR_IN6) +struct sockaddr_in6 { + sa_family_t sin6_family; + uint16_t sin6_port; + // uint32_t sin6_flowinfo; + struct in6_addr sin6_addr; + // uint32_t sin6_scope_id; +}; +#endif /* !defined(HAVE_STRUCT_SOCKADDR_IN6) */ + +#endif /* TOR_INADDR_ST_H */ diff --git a/src/lib/net/include.am b/src/lib/net/include.am new file mode 100644 index 0000000000..ff0967e786 --- /dev/null +++ b/src/lib/net/include.am @@ -0,0 +1,34 @@ + +noinst_LIBRARIES += src/lib/libtor-net.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-net-testing.a +endif + +src_lib_libtor_net_a_SOURCES = \ + src/lib/net/address.c \ + src/lib/net/alertsock.c \ + src/lib/net/buffers_net.c \ + src/lib/net/gethostname.c \ + src/lib/net/inaddr.c \ + src/lib/net/resolve.c \ + src/lib/net/socket.c \ + src/lib/net/socketpair.c + +src_lib_libtor_net_testing_a_SOURCES = \ + $(src_lib_libtor_net_a_SOURCES) +src_lib_libtor_net_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_net_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/net/address.h \ + src/lib/net/alertsock.h \ + src/lib/net/buffers_net.h \ + src/lib/net/gethostname.h \ + src/lib/net/inaddr.h \ + src/lib/net/inaddr_st.h \ + src/lib/net/nettypes.h \ + src/lib/net/resolve.h \ + src/lib/net/socket.h \ + src/lib/net/socketpair.h \ + src/lib/net/socks5_status.h diff --git a/src/lib/net/nettypes.h b/src/lib/net/nettypes.h new file mode 100644 index 0000000000..6209bbe18a --- /dev/null +++ b/src/lib/net/nettypes.h @@ -0,0 +1,44 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file nettypes.h + * \brief Declarations for types used throughout the Tor networking system + **/ + +#ifndef TOR_NET_TYPES_H +#define TOR_NET_TYPES_H + +#include "orconfig.h" +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#if (SIZEOF_SOCKLEN_T == 0) +typedef int socklen_t; +#endif + +#ifdef _WIN32 +/* XXX Actually, this should arguably be SOCKET; we use intptr_t here so that + * any inadvertent checks for the socket being <= 0 or > 0 will probably + * still work. */ +#define tor_socket_t intptr_t +#define TOR_SOCKET_T_FORMAT "%"PRIuPTR +#define SOCKET_OK(s) ((SOCKET)(s) != INVALID_SOCKET) +#define TOR_INVALID_SOCKET INVALID_SOCKET +#else /* !(defined(_WIN32)) */ +/** Type used for a network socket. */ +#define tor_socket_t int +#define TOR_SOCKET_T_FORMAT "%d" +/** Macro: true iff 's' is a possible value for a valid initialized socket. */ +#define SOCKET_OK(s) ((s) >= 0) +/** Error/uninitialized value for a tor_socket_t. */ +#define TOR_INVALID_SOCKET (-1) +#endif /* defined(_WIN32) */ + +#endif diff --git a/src/lib/net/resolve.c b/src/lib/net/resolve.c new file mode 100644 index 0000000000..8cee29df37 --- /dev/null +++ b/src/lib/net/resolve.c @@ -0,0 +1,424 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file resolve.c + * \brief Use the libc DNS resolver to convert hostnames into addresses. + **/ + +#include "lib/net/resolve.h" + +#include "lib/net/address.h" +#include "lib/net/inaddr.h" +#include "lib/malloc/malloc.h" +#include "lib/string/parse_int.h" +#include "lib/string/util_string.h" + +#include "siphash.h" +#include "ht.h" + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +#include <string.h> + +/** Similar behavior to Unix gethostbyname: resolve <b>name</b>, and set + * *<b>addr</b> to the proper IP address, in host byte order. Returns 0 + * on success, -1 on failure; 1 on transient failure. + * + * (This function exists because standard windows gethostbyname + * doesn't treat raw IP addresses properly.) + */ + +MOCK_IMPL(int, +tor_lookup_hostname,(const char *name, uint32_t *addr)) +{ + tor_addr_t myaddr; + int ret; + + if ((ret = tor_addr_lookup(name, AF_INET, &myaddr))) + return ret; + + if (tor_addr_family(&myaddr) == AF_INET) { + *addr = tor_addr_to_ipv4h(&myaddr); + return ret; + } + + return -1; +} + +/** Similar behavior to Unix gethostbyname: resolve <b>name</b>, and set + * *<b>addr</b> to the proper IP address and family. The <b>family</b> + * argument (which must be AF_INET, AF_INET6, or AF_UNSPEC) declares a + * <i>preferred</i> family, though another one may be returned if only one + * family is implemented for this address. + * + * Return 0 on success, -1 on failure; 1 on transient failure. + */ +MOCK_IMPL(int, +tor_addr_lookup,(const char *name, uint16_t family, tor_addr_t *addr)) +{ + /* Perhaps eventually this should be replaced by a tor_getaddrinfo or + * something. + */ + struct in_addr iaddr; + struct in6_addr iaddr6; + tor_assert(name); + tor_assert(addr); + tor_assert(family == AF_INET || family == AF_INET6 || family == AF_UNSPEC); + if (!*name) { + /* Empty address is an error. */ + return -1; + } else if (tor_inet_pton(AF_INET, name, &iaddr)) { + /* It's an IPv4 IP. */ + if (family == AF_INET6) + return -1; + tor_addr_from_in(addr, &iaddr); + return 0; + } else if (tor_inet_pton(AF_INET6, name, &iaddr6)) { + if (family == AF_INET) + return -1; + tor_addr_from_in6(addr, &iaddr6); + return 0; + } else { +#ifdef HAVE_GETADDRINFO + int err; + struct addrinfo *res=NULL, *res_p; + struct addrinfo *best=NULL; + struct addrinfo hints; + int result = -1; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + err = tor_getaddrinfo(name, NULL, &hints, &res); + /* The check for 'res' here shouldn't be necessary, but it makes static + * analysis tools happy. */ + if (!err && res) { + best = NULL; + for (res_p = res; res_p; res_p = res_p->ai_next) { + if (family == AF_UNSPEC) { + if (res_p->ai_family == AF_INET) { + best = res_p; + break; + } else if (res_p->ai_family == AF_INET6 && !best) { + best = res_p; + } + } else if (family == res_p->ai_family) { + best = res_p; + break; + } + } + if (!best) + best = res; + if (best->ai_family == AF_INET) { + tor_addr_from_in(addr, + &((struct sockaddr_in*)best->ai_addr)->sin_addr); + result = 0; + } else if (best->ai_family == AF_INET6) { + tor_addr_from_in6(addr, + &((struct sockaddr_in6*)best->ai_addr)->sin6_addr); + result = 0; + } + tor_freeaddrinfo(res); + return result; + } + return (err == EAI_AGAIN) ? 1 : -1; +#else /* !(defined(HAVE_GETADDRINFO)) */ + struct hostent *ent; + int err; +#ifdef HAVE_GETHOSTBYNAME_R_6_ARG + char buf[2048]; + struct hostent hostent; + int r; + r = gethostbyname_r(name, &hostent, buf, sizeof(buf), &ent, &err); +#elif defined(HAVE_GETHOSTBYNAME_R_5_ARG) + char buf[2048]; + struct hostent hostent; + ent = gethostbyname_r(name, &hostent, buf, sizeof(buf), &err); +#elif defined(HAVE_GETHOSTBYNAME_R_3_ARG) + struct hostent_data data; + struct hostent hent; + memset(&data, 0, sizeof(data)); + err = gethostbyname_r(name, &hent, &data); + ent = err ? NULL : &hent; +#else + ent = gethostbyname(name); +#ifdef _WIN32 + err = WSAGetLastError(); +#else + err = h_errno; +#endif +#endif /* defined(HAVE_GETHOSTBYNAME_R_6_ARG) || ... */ + if (ent) { + if (ent->h_addrtype == AF_INET) { + tor_addr_from_in(addr, (struct in_addr*) ent->h_addr); + } else if (ent->h_addrtype == AF_INET6) { + tor_addr_from_in6(addr, (struct in6_addr*) ent->h_addr); + } else { + tor_assert(0); // LCOV_EXCL_LINE: gethostbyname() returned bizarre type + } + return 0; + } +#ifdef _WIN32 + return (err == WSATRY_AGAIN) ? 1 : -1; +#else + return (err == TRY_AGAIN) ? 1 : -1; +#endif +#endif /* defined(HAVE_GETADDRINFO) */ + } +} + +/** Parse an address or address-port combination from <b>s</b>, resolve the + * address as needed, and put the result in <b>addr_out</b> and (optionally) + * <b>port_out</b>. Return 0 on success, negative on failure. */ +int +tor_addr_port_lookup(const char *s, tor_addr_t *addr_out, uint16_t *port_out) +{ + const char *port; + tor_addr_t addr; + uint16_t portval; + char *tmp = NULL; + + tor_assert(s); + tor_assert(addr_out); + + s = eat_whitespace(s); + + if (*s == '[') { + port = strstr(s, "]"); + if (!port) + goto err; + tmp = tor_strndup(s+1, port-(s+1)); + port = port+1; + if (*port == ':') + port++; + else + port = NULL; + } else { + port = strchr(s, ':'); + if (port) + tmp = tor_strndup(s, port-s); + else + tmp = tor_strdup(s); + if (port) + ++port; + } + + if (tor_addr_lookup(tmp, AF_UNSPEC, &addr) != 0) + goto err; + tor_free(tmp); + + if (port) { + portval = (int) tor_parse_long(port, 10, 1, 65535, NULL, NULL); + if (!portval) + goto err; + } else { + portval = 0; + } + + if (port_out) + *port_out = portval; + tor_addr_copy(addr_out, &addr); + + return 0; + err: + tor_free(tmp); + return -1; +} + +#ifdef USE_SANDBOX_GETADDRINFO +/** True if we should only return cached values */ +static int sandbox_getaddrinfo_is_active = 0; + +/** Cache entry for getaddrinfo results; used when sandboxing is implemented + * so that we can consult the cache when the sandbox prevents us from doing + * getaddrinfo. + * + * We support only a limited range of getaddrinfo calls, where servname is null + * and hints contains only socktype=SOCK_STREAM, family in INET,INET6,UNSPEC. + */ +typedef struct cached_getaddrinfo_item_t { + HT_ENTRY(cached_getaddrinfo_item_t) node; + char *name; + int family; + /** set if no error; otherwise NULL */ + struct addrinfo *res; + /** 0 for no error; otherwise an EAI_* value */ + int err; +} cached_getaddrinfo_item_t; + +static unsigned +cached_getaddrinfo_item_hash(const cached_getaddrinfo_item_t *item) +{ + return (unsigned)siphash24g(item->name, strlen(item->name)) + item->family; +} + +static unsigned +cached_getaddrinfo_items_eq(const cached_getaddrinfo_item_t *a, + const cached_getaddrinfo_item_t *b) +{ + return (a->family == b->family) && 0 == strcmp(a->name, b->name); +} + +#define cached_getaddrinfo_item_free(item) \ + FREE_AND_NULL(cached_getaddrinfo_item_t, \ + cached_getaddrinfo_item_free_, (item)) + +static void +cached_getaddrinfo_item_free_(cached_getaddrinfo_item_t *item) +{ + if (item == NULL) + return; + + tor_free(item->name); + if (item->res) + freeaddrinfo(item->res); + tor_free(item); +} + +static HT_HEAD(getaddrinfo_cache, cached_getaddrinfo_item_t) + getaddrinfo_cache = HT_INITIALIZER(); + +HT_PROTOTYPE(getaddrinfo_cache, cached_getaddrinfo_item_t, node, + cached_getaddrinfo_item_hash, + cached_getaddrinfo_items_eq) +HT_GENERATE2(getaddrinfo_cache, cached_getaddrinfo_item_t, node, + cached_getaddrinfo_item_hash, + cached_getaddrinfo_items_eq, + 0.6, tor_reallocarray_, tor_free_) + +/** If true, don't try to cache getaddrinfo results. */ +static int sandbox_getaddrinfo_cache_disabled = 0; + +/** Tell the sandbox layer not to try to cache getaddrinfo results. Used as in + * tor-resolve, when we have no intention of initializing crypto or of + * installing the sandbox.*/ +void +sandbox_disable_getaddrinfo_cache(void) +{ + sandbox_getaddrinfo_cache_disabled = 1; +} + +void +tor_freeaddrinfo(struct addrinfo *ai) +{ + if (sandbox_getaddrinfo_cache_disabled) + freeaddrinfo(ai); +} + +int +tor_getaddrinfo(const char *name, const char *servname, + const struct addrinfo *hints, + struct addrinfo **res) +{ + int err; + struct cached_getaddrinfo_item_t search, *item; + + if (sandbox_getaddrinfo_cache_disabled) { + return getaddrinfo(name, NULL, hints, res); + } + + if (servname != NULL) { + log_warn(LD_BUG, "called with non-NULL servname"); + return EAI_NONAME; + } + if (name == NULL) { + log_warn(LD_BUG, "called with NULL name"); + return EAI_NONAME; + } + + *res = NULL; + + memset(&search, 0, sizeof(search)); + search.name = (char *) name; + search.family = hints ? hints->ai_family : AF_UNSPEC; + item = HT_FIND(getaddrinfo_cache, &getaddrinfo_cache, &search); + + if (! sandbox_getaddrinfo_is_active) { + /* If the sandbox is not turned on yet, then getaddrinfo and store the + result. */ + + err = getaddrinfo(name, NULL, hints, res); + log_info(LD_NET,"(Sandbox) getaddrinfo %s.", err ? "failed" : "succeeded"); + + if (! item) { + item = tor_malloc_zero(sizeof(*item)); + item->name = tor_strdup(name); + item->family = hints ? hints->ai_family : AF_UNSPEC; + HT_INSERT(getaddrinfo_cache, &getaddrinfo_cache, item); + } + + if (item->res) { + freeaddrinfo(item->res); + item->res = NULL; + } + item->res = *res; + item->err = err; + return err; + } + + /* Otherwise, the sandbox is on. If we have an item, yield its cached + result. */ + if (item) { + *res = item->res; + return item->err; + } + + /* getting here means something went wrong */ + log_err(LD_BUG,"(Sandbox) failed to get address %s!", name); + return EAI_NONAME; +} + +int +tor_add_addrinfo(const char *name) +{ + struct addrinfo *res; + struct addrinfo hints; + int i; + static const int families[] = { AF_INET, AF_INET6, AF_UNSPEC }; + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + for (i = 0; i < 3; ++i) { + hints.ai_family = families[i]; + + res = NULL; + (void) tor_getaddrinfo(name, NULL, &hints, &res); + if (res) + tor_freeaddrinfo(res); + } + + return 0; +} + +void +tor_free_getaddrinfo_cache(void) +{ + cached_getaddrinfo_item_t **next, **item, *this; + + for (item = HT_START(getaddrinfo_cache, &getaddrinfo_cache); + item; + item = next) { + this = *item; + next = HT_NEXT_RMV(getaddrinfo_cache, &getaddrinfo_cache, item); + cached_getaddrinfo_item_free(this); + } + + HT_CLEAR(getaddrinfo_cache, &getaddrinfo_cache); +} + +void +tor_make_getaddrinfo_cache_active(void) +{ + sandbox_getaddrinfo_is_active = 1; +} +#endif diff --git a/src/lib/net/resolve.h b/src/lib/net/resolve.h new file mode 100644 index 0000000000..47a283c81c --- /dev/null +++ b/src/lib/net/resolve.h @@ -0,0 +1,58 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file resolve.h + * \brief Header for resolve.c + **/ + +#ifndef TOR_RESOLVE_H +#define TOR_RESOLVE_H + +#include "orconfig.h" +#include "lib/cc/torint.h" +#include "lib/testsupport/testsupport.h" +#ifdef _WIN32 +#include <winsock2.h> +#endif + +#if defined(HAVE_SECCOMP_H) && defined(__linux__) +#define USE_SANDBOX_GETADDRINFO +#endif + +struct tor_addr_t; + +MOCK_DECL(int, tor_lookup_hostname,(const char *name, uint32_t *addr)); +MOCK_DECL(int, tor_addr_lookup,(const char *name, uint16_t family, + struct tor_addr_t *addr_out)); +int tor_addr_port_lookup(const char *s, struct tor_addr_t *addr_out, + uint16_t *port_out); + +struct addrinfo; +#ifdef USE_SANDBOX_GETADDRINFO +/** Pre-calls getaddrinfo in order to pre-record result. */ +int tor_add_addrinfo(const char *addr); + +struct addrinfo; +/** Replacement for getaddrinfo(), using pre-recorded results. */ +int tor_getaddrinfo(const char *name, const char *servname, + const struct addrinfo *hints, + struct addrinfo **res); +void tor_freeaddrinfo(struct addrinfo *addrinfo); +void tor_free_getaddrinfo_cache(void); +void tor_make_getaddrinfo_cache_active(void); +#else /* !(defined(USE_SANDBOX_GETADDRINFO)) */ +#define tor_getaddrinfo(name, servname, hints, res) \ + getaddrinfo((name),(servname), (hints),(res)) +#define tor_add_addrinfo(name) \ + ((void)(name)) +#define tor_freeaddrinfo(addrinfo) \ + freeaddrinfo((addrinfo)) +#define tor_free_getaddrinfo_cache() +#endif /* defined(USE_SANDBOX_GETADDRINFO) */ + +void sandbox_disable_getaddrinfo_cache(void); + +#endif diff --git a/src/lib/net/socket.c b/src/lib/net/socket.c new file mode 100644 index 0000000000..fba90b7506 --- /dev/null +++ b/src/lib/net/socket.c @@ -0,0 +1,697 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file socket.c + * \brief Compatibility and utility functions for working with network + * sockets. + **/ + +#define SOCKET_PRIVATE +#include "lib/net/socket.h" +#include "lib/net/socketpair.h" +#include "lib/net/address.h" +#include "lib/cc/compat_compiler.h" +#include "lib/err/torerr.h" +#include "lib/lock/compat_mutex.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include <stddef.h> +#include <string.h> + +/** Called before we make any calls to network-related functions. + * (Some operating systems require their network libraries to be + * initialized.) */ +int +network_init(void) +{ +#ifdef _WIN32 + /* This silly exercise is necessary before windows will allow + * gethostbyname to work. */ + WSADATA WSAData; + int r; + r = WSAStartup(0x101,&WSAData); + if (r) { + log_warn(LD_NET,"Error initializing windows network layer: code was %d",r); + return -1; + } + if (sizeof(SOCKET) != sizeof(tor_socket_t)) { + log_warn(LD_BUG,"The tor_socket_t type does not match SOCKET in size; Tor " + "might not work. (Sizes are %d and %d respectively.)", + (int)sizeof(tor_socket_t), (int)sizeof(SOCKET)); + } + /* WSAData.iMaxSockets might show the max sockets we're allowed to use. + * We might use it to complain if we're trying to be a server but have + * too few sockets available. */ +#endif /* defined(_WIN32) */ + return 0; +} + +/* When set_max_file_sockets() is called, update this with the max file + * descriptor value so we can use it to check the limit when opening a new + * socket. Default value is what Debian sets as the default hard limit. */ +static int max_sockets = 1024; + +/** Return the maximum number of allowed sockets. */ +int +get_max_sockets(void) +{ + return max_sockets; +} + +/** Set the maximum number of allowed sockets to <b>n</b> */ +void +set_max_sockets(int n) +{ + max_sockets = n; +} + +#undef DEBUG_SOCKET_COUNTING +#ifdef DEBUG_SOCKET_COUNTING +#include "lib/container/bitarray.h" + +/** A bitarray of all fds that should be passed to tor_socket_close(). Only + * used if DEBUG_SOCKET_COUNTING is defined. */ +static bitarray_t *open_sockets = NULL; +/** The size of <b>open_sockets</b>, in bits. */ +static int max_socket = -1; +#endif /* defined(DEBUG_SOCKET_COUNTING) */ + +/** Count of number of sockets currently open. (Undercounts sockets opened by + * eventdns and libevent.) */ +static int n_sockets_open = 0; + +/** Mutex to protect open_sockets, max_socket, and n_sockets_open. */ +static tor_mutex_t *socket_accounting_mutex = NULL; + +/** Helper: acquire the socket accounting lock. */ +static inline void +socket_accounting_lock(void) +{ + if (PREDICT_UNLIKELY(!socket_accounting_mutex)) + socket_accounting_mutex = tor_mutex_new(); + tor_mutex_acquire(socket_accounting_mutex); +} + +/** Helper: release the socket accounting lock. */ +static inline void +socket_accounting_unlock(void) +{ + tor_mutex_release(socket_accounting_mutex); +} + +/** As close(), but guaranteed to work for sockets across platforms (including + * Windows, where close()ing a socket doesn't work. Returns 0 on success and + * the socket error code on failure. */ +int +tor_close_socket_simple(tor_socket_t s) +{ + int r = 0; + + /* On Windows, you have to call close() on fds returned by open(), + * and closesocket() on fds returned by socket(). On Unix, everything + * gets close()'d. We abstract this difference by always using + * tor_close_socket to close sockets, and always using close() on + * files. + */ + #if defined(_WIN32) + r = closesocket(s); + #else + r = close(s); + #endif + + if (r != 0) { + int err = tor_socket_errno(-1); + log_info(LD_NET, "Close returned an error: %s", tor_socket_strerror(err)); + return err; + } + + return r; +} + +/** @{ */ +#ifdef DEBUG_SOCKET_COUNTING +/** Helper: if DEBUG_SOCKET_COUNTING is enabled, remember that <b>s</b> is + * now an open socket. */ +static inline void +mark_socket_open(tor_socket_t s) +{ + /* XXXX This bitarray business will NOT work on windows: sockets aren't + small ints there. */ + if (s > max_socket) { + if (max_socket == -1) { + open_sockets = bitarray_init_zero(s+128); + max_socket = s+128; + } else { + open_sockets = bitarray_expand(open_sockets, max_socket, s+128); + max_socket = s+128; + } + } + if (bitarray_is_set(open_sockets, s)) { + log_warn(LD_BUG, "I thought that %d was already open, but socket() just " + "gave it to me!", s); + } + bitarray_set(open_sockets, s); +} +static inline void +mark_socket_closed(tor_socket_t s) +{ + if (s > max_socket || ! bitarray_is_set(open_sockets, s)) { + log_warn(LD_BUG, "Closing a socket (%d) that wasn't returned by tor_open_" + "socket(), or that was already closed or something.", s); + } else { + tor_assert(open_sockets && s <= max_socket); + bitarray_clear(open_sockets, s); + } +} +#else /* !(defined(DEBUG_SOCKET_COUNTING)) */ +#define mark_socket_open(s) ((void) (s)) +#define mark_socket_closed(s) ((void) (s)) +#endif /* defined(DEBUG_SOCKET_COUNTING) */ +/** @} */ + +/** As tor_close_socket_simple(), but keeps track of the number + * of open sockets. Returns 0 on success, -1 on failure. */ +MOCK_IMPL(int, +tor_close_socket,(tor_socket_t s)) +{ + int r = tor_close_socket_simple(s); + + socket_accounting_lock(); + mark_socket_closed(s); + if (r == 0) { + --n_sockets_open; + } else { +#ifdef _WIN32 + if (r != WSAENOTSOCK) + --n_sockets_open; +#else + if (r != EBADF) + --n_sockets_open; // LCOV_EXCL_LINE -- EIO and EINTR too hard to force. +#endif /* defined(_WIN32) */ + r = -1; + } + + tor_assert_nonfatal(n_sockets_open >= 0); + socket_accounting_unlock(); + return r; +} + +/** As socket(), but counts the number of open sockets. */ +MOCK_IMPL(tor_socket_t, +tor_open_socket,(int domain, int type, int protocol)) +{ + return tor_open_socket_with_extensions(domain, type, protocol, 1, 0); +} + +/** Mockable wrapper for connect(). */ +MOCK_IMPL(tor_socket_t, +tor_connect_socket,(tor_socket_t sock, const struct sockaddr *address, + socklen_t address_len)) +{ + return connect(sock,address,address_len); +} + +/** As socket(), but creates a nonblocking socket and + * counts the number of open sockets. */ +tor_socket_t +tor_open_socket_nonblocking(int domain, int type, int protocol) +{ + return tor_open_socket_with_extensions(domain, type, protocol, 1, 1); +} + +/** As socket(), but counts the number of open sockets and handles + * socket creation with either of SOCK_CLOEXEC and SOCK_NONBLOCK specified. + * <b>cloexec</b> and <b>nonblock</b> should be either 0 or 1 to indicate + * if the corresponding extension should be used.*/ +tor_socket_t +tor_open_socket_with_extensions(int domain, int type, int protocol, + int cloexec, int nonblock) +{ + tor_socket_t s; + + /* We are about to create a new file descriptor so make sure we have + * enough of them. */ + if (get_n_open_sockets() >= max_sockets - 1) { +#ifdef _WIN32 + WSASetLastError(WSAEMFILE); +#else + errno = EMFILE; +#endif + return TOR_INVALID_SOCKET; + } + +#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) + int ext_flags = (cloexec ? SOCK_CLOEXEC : 0) | + (nonblock ? SOCK_NONBLOCK : 0); + s = socket(domain, type|ext_flags, protocol); + if (SOCKET_OK(s)) + goto socket_ok; + /* If we got an error, see if it is EINVAL. EINVAL might indicate that, + * even though we were built on a system with SOCK_CLOEXEC and SOCK_NONBLOCK + * support, we are running on one without. */ + if (errno != EINVAL) + return s; +#endif /* defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) */ + + s = socket(domain, type, protocol); + if (! SOCKET_OK(s)) + return s; + +#if defined(FD_CLOEXEC) + if (cloexec) { + if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) { + log_warn(LD_FS,"Couldn't set FD_CLOEXEC: %s", strerror(errno)); + tor_close_socket_simple(s); + return TOR_INVALID_SOCKET; + } + } +#else /* !(defined(FD_CLOEXEC)) */ + (void)cloexec; +#endif /* defined(FD_CLOEXEC) */ + + if (nonblock) { + if (set_socket_nonblocking(s) == -1) { + tor_close_socket_simple(s); + return TOR_INVALID_SOCKET; + } + } + + goto socket_ok; /* So that socket_ok will not be unused. */ + + socket_ok: + tor_take_socket_ownership(s); + return s; +} + +/** + * For socket accounting: remember that we are the owner of the socket + * <b>s</b>. This will prevent us from overallocating sockets, and prevent us + * from asserting later when we close the socket <b>s</b>. + */ +void +tor_take_socket_ownership(tor_socket_t s) +{ + socket_accounting_lock(); + ++n_sockets_open; + mark_socket_open(s); + socket_accounting_unlock(); +} + +/** + * For socket accounting: declare that we are no longer the owner of the + * socket <b>s</b>. This will prevent us from overallocating sockets, and + * prevent us from asserting later when we close the socket <b>s</b>. + */ +void +tor_release_socket_ownership(tor_socket_t s) +{ + socket_accounting_lock(); + --n_sockets_open; + mark_socket_closed(s); + socket_accounting_unlock(); +} + +/** As accept(), but counts the number of open sockets. */ +tor_socket_t +tor_accept_socket(tor_socket_t sockfd, struct sockaddr *addr, socklen_t *len) +{ + return tor_accept_socket_with_extensions(sockfd, addr, len, 1, 0); +} + +/** As accept(), but returns a nonblocking socket and + * counts the number of open sockets. */ +tor_socket_t +tor_accept_socket_nonblocking(tor_socket_t sockfd, struct sockaddr *addr, + socklen_t *len) +{ + return tor_accept_socket_with_extensions(sockfd, addr, len, 1, 1); +} + +/** As accept(), but counts the number of open sockets and handles + * socket creation with either of SOCK_CLOEXEC and SOCK_NONBLOCK specified. + * <b>cloexec</b> and <b>nonblock</b> should be either 0 or 1 to indicate + * if the corresponding extension should be used.*/ +tor_socket_t +tor_accept_socket_with_extensions(tor_socket_t sockfd, struct sockaddr *addr, + socklen_t *len, int cloexec, int nonblock) +{ + tor_socket_t s; + + /* We are about to create a new file descriptor so make sure we have + * enough of them. */ + if (get_n_open_sockets() >= max_sockets - 1) { +#ifdef _WIN32 + WSASetLastError(WSAEMFILE); +#else + errno = EMFILE; +#endif + return TOR_INVALID_SOCKET; + } + +#if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) \ + && defined(SOCK_NONBLOCK) + int ext_flags = (cloexec ? SOCK_CLOEXEC : 0) | + (nonblock ? SOCK_NONBLOCK : 0); + s = accept4(sockfd, addr, len, ext_flags); + if (SOCKET_OK(s)) + goto socket_ok; + /* If we got an error, see if it is ENOSYS. ENOSYS indicates that, + * even though we were built on a system with accept4 support, we + * are running on one without. Also, check for EINVAL, which indicates that + * we are missing SOCK_CLOEXEC/SOCK_NONBLOCK support. */ + if (errno != EINVAL && errno != ENOSYS) + return s; +#endif /* defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) ... */ + + s = accept(sockfd, addr, len); + if (!SOCKET_OK(s)) + return s; + +#if defined(FD_CLOEXEC) + if (cloexec) { + if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) { + log_warn(LD_NET, "Couldn't set FD_CLOEXEC: %s", strerror(errno)); + tor_close_socket_simple(s); + return TOR_INVALID_SOCKET; + } + } +#else /* !(defined(FD_CLOEXEC)) */ + (void)cloexec; +#endif /* defined(FD_CLOEXEC) */ + + if (nonblock) { + if (set_socket_nonblocking(s) == -1) { + tor_close_socket_simple(s); + return TOR_INVALID_SOCKET; + } + } + + goto socket_ok; /* So that socket_ok will not be unused. */ + + socket_ok: + tor_take_socket_ownership(s); + return s; +} + +/** Return the number of sockets we currently have opened. */ +int +get_n_open_sockets(void) +{ + int n; + socket_accounting_lock(); + n = n_sockets_open; + socket_accounting_unlock(); + return n; +} + +/** + * Allocate a pair of connected sockets. (Like socketpair(family, + * type,protocol,fd), but works on systems that don't have + * socketpair.) + * + * Currently, only (AF_UNIX, SOCK_STREAM, 0) sockets are supported. + * + * Note that on systems without socketpair, this call will fail if + * localhost is inaccessible (for example, if the networking + * stack is down). And even if it succeeds, the socket pair will not + * be able to read while localhost is down later (the socket pair may + * even close, depending on OS-specific timeouts). + * + * Returns 0 on success and -errno on failure; do not rely on the value + * of errno or WSAGetLastError(). + **/ +/* It would be nicer just to set errno, but that won't work for windows. */ +int +tor_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) +{ + int r; +//don't use win32 socketpairs (they are always bad) +#if defined(HAVE_SOCKETPAIR) && !defined(_WIN32) + +#ifdef SOCK_CLOEXEC + r = socketpair(family, type|SOCK_CLOEXEC, protocol, fd); + if (r == 0) + goto sockets_ok; + /* If we got an error, see if it is EINVAL. EINVAL might indicate that, + * even though we were built on a system with SOCK_CLOEXEC support, we + * are running on one without. */ + if (errno != EINVAL) + return -errno; +#endif /* defined(SOCK_CLOEXEC) */ + + r = socketpair(family, type, protocol, fd); + if (r < 0) + return -errno; +#else + r = tor_ersatz_socketpair(family, type, protocol, fd); + if (r < 0) + return -r; +#endif + +#if defined(FD_CLOEXEC) + if (SOCKET_OK(fd[0])) { + r = fcntl(fd[0], F_SETFD, FD_CLOEXEC); + if (r == -1) { + close(fd[0]); + close(fd[1]); + return -errno; + } + } + if (SOCKET_OK(fd[1])) { + r = fcntl(fd[1], F_SETFD, FD_CLOEXEC); + if (r == -1) { + close(fd[0]); + close(fd[1]); + return -errno; + } + } +#endif /* defined(FD_CLOEXEC) */ + goto sockets_ok; /* So that sockets_ok will not be unused. */ + + sockets_ok: + socket_accounting_lock(); + if (SOCKET_OK(fd[0])) { + ++n_sockets_open; + mark_socket_open(fd[0]); + } + if (SOCKET_OK(fd[1])) { + ++n_sockets_open; + mark_socket_open(fd[1]); + } + socket_accounting_unlock(); + + return 0; +} + +/** Mockable wrapper for getsockname(). */ +MOCK_IMPL(int, +tor_getsockname,(tor_socket_t sock, struct sockaddr *address, + socklen_t *address_len)) +{ + return getsockname(sock, address, address_len); +} + +/** + * Find the local address associated with the socket <b>sock</b>, and + * place it in *<b>addr_out</b>. Return 0 on success, -1 on failure. + * + * (As tor_getsockname, but instead places the result in a tor_addr_t.) */ +int +tor_addr_from_getsockname(struct tor_addr_t *addr_out, tor_socket_t sock) +{ + struct sockaddr_storage ss; + socklen_t ss_len = sizeof(ss); + memset(&ss, 0, sizeof(ss)); + + if (tor_getsockname(sock, (struct sockaddr *) &ss, &ss_len) < 0) + return -1; + + return tor_addr_from_sockaddr(addr_out, (struct sockaddr *)&ss, NULL); +} + +/** Turn <b>socket</b> into a nonblocking socket. Return 0 on success, -1 + * on failure. + */ +int +set_socket_nonblocking(tor_socket_t sock) +{ +#if defined(_WIN32) + unsigned long nonblocking = 1; + ioctlsocket(sock, FIONBIO, (unsigned long*) &nonblocking); +#else + int flags; + + flags = fcntl(sock, F_GETFL, 0); + if (flags == -1) { + log_warn(LD_NET, "Couldn't get file status flags: %s", strerror(errno)); + return -1; + } + flags |= O_NONBLOCK; + if (fcntl(sock, F_SETFL, flags) == -1) { + log_warn(LD_NET, "Couldn't set file status flags: %s", strerror(errno)); + return -1; + } +#endif /* defined(_WIN32) */ + + return 0; +} + +/** Read from <b>sock</b> to <b>buf</b>, until we get <b>count</b> bytes or + * reach the end of the file. Return the number of bytes read, or -1 on + * error. Only use if fd is a blocking fd. */ +ssize_t +read_all_from_socket(tor_socket_t sock, char *buf, size_t count) +{ + size_t numread = 0; + ssize_t result; + + if (count > SIZE_T_CEILING || count > SSIZE_MAX) { + errno = EINVAL; + return -1; + } + + while (numread < count) { + result = tor_socket_recv(sock, buf+numread, count-numread, 0); + if (result<0) + return -1; + else if (result == 0) + break; + numread += result; + } + return (ssize_t)numread; +} + +/** Write <b>count</b> bytes from <b>buf</b> to <b>sock</b>. Return the number + * of bytes written, or -1 on error. Only use if fd is a blocking fd. */ +ssize_t +write_all_to_socket(tor_socket_t fd, const char *buf, size_t count) +{ + size_t written = 0; + ssize_t result; + raw_assert(count < SSIZE_MAX); + + while (written != count) { + result = tor_socket_send(fd, buf+written, count-written, 0); + if (result<0) + return -1; + written += result; + } + return (ssize_t)count; +} + +/** + * On Windows, WSAEWOULDBLOCK is not always correct: when you see it, + * you need to ask the socket for its actual errno. Also, you need to + * get your errors from WSAGetLastError, not errno. (If you supply a + * socket of -1, we check WSAGetLastError, but don't correct + * WSAEWOULDBLOCKs.) + * + * The upshot of all of this is that when a socket call fails, you + * should call tor_socket_errno <em>at most once</em> on the failing + * socket to get the error. + */ +#if defined(_WIN32) +int +tor_socket_errno(tor_socket_t sock) +{ + int optval, optvallen=sizeof(optval); + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK && SOCKET_OK(sock)) { + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*)&optval, &optvallen)) + return err; + if (optval) + return optval; + } + return err; +} +#endif /* defined(_WIN32) */ + +#if defined(_WIN32) +#define E(code, s) { code, (s " [" #code " ]") } +struct { int code; const char *msg; } windows_socket_errors[] = { + E(WSAEINTR, "Interrupted function call"), + E(WSAEACCES, "Permission denied"), + E(WSAEFAULT, "Bad address"), + E(WSAEINVAL, "Invalid argument"), + E(WSAEMFILE, "Too many open files"), + E(WSAEWOULDBLOCK, "Resource temporarily unavailable"), + E(WSAEINPROGRESS, "Operation now in progress"), + E(WSAEALREADY, "Operation already in progress"), + E(WSAENOTSOCK, "Socket operation on nonsocket"), + E(WSAEDESTADDRREQ, "Destination address required"), + E(WSAEMSGSIZE, "Message too long"), + E(WSAEPROTOTYPE, "Protocol wrong for socket"), + E(WSAENOPROTOOPT, "Bad protocol option"), + E(WSAEPROTONOSUPPORT, "Protocol not supported"), + E(WSAESOCKTNOSUPPORT, "Socket type not supported"), + /* What's the difference between NOTSUPP and NOSUPPORT? :) */ + E(WSAEOPNOTSUPP, "Operation not supported"), + E(WSAEPFNOSUPPORT, "Protocol family not supported"), + E(WSAEAFNOSUPPORT, "Address family not supported by protocol family"), + E(WSAEADDRINUSE, "Address already in use"), + E(WSAEADDRNOTAVAIL, "Cannot assign requested address"), + E(WSAENETDOWN, "Network is down"), + E(WSAENETUNREACH, "Network is unreachable"), + E(WSAENETRESET, "Network dropped connection on reset"), + E(WSAECONNABORTED, "Software caused connection abort"), + E(WSAECONNRESET, "Connection reset by peer"), + E(WSAENOBUFS, "No buffer space available"), + E(WSAEISCONN, "Socket is already connected"), + E(WSAENOTCONN, "Socket is not connected"), + E(WSAESHUTDOWN, "Cannot send after socket shutdown"), + E(WSAETIMEDOUT, "Connection timed out"), + E(WSAECONNREFUSED, "Connection refused"), + E(WSAEHOSTDOWN, "Host is down"), + E(WSAEHOSTUNREACH, "No route to host"), + E(WSAEPROCLIM, "Too many processes"), + /* Yes, some of these start with WSA, not WSAE. No, I don't know why. */ + E(WSASYSNOTREADY, "Network subsystem is unavailable"), + E(WSAVERNOTSUPPORTED, "Winsock.dll out of range"), + E(WSANOTINITIALISED, "Successful WSAStartup not yet performed"), + E(WSAEDISCON, "Graceful shutdown now in progress"), +#ifdef WSATYPE_NOT_FOUND + E(WSATYPE_NOT_FOUND, "Class type not found"), +#endif + E(WSAHOST_NOT_FOUND, "Host not found"), + E(WSATRY_AGAIN, "Nonauthoritative host not found"), + E(WSANO_RECOVERY, "This is a nonrecoverable error"), + E(WSANO_DATA, "Valid name, no data record of requested type)"), + + /* There are some more error codes whose numeric values are marked + * <b>OS dependent</b>. They start with WSA_, apparently for the same + * reason that practitioners of some craft traditions deliberately + * introduce imperfections into their baskets and rugs "to allow the + * evil spirits to escape." If we catch them, then our binaries + * might not report consistent results across versions of Windows. + * Thus, I'm going to let them all fall through. + */ + { -1, NULL }, +}; +/** There does not seem to be a strerror equivalent for Winsock errors. + * Naturally, we have to roll our own. + */ +const char * +tor_socket_strerror(int e) +{ + int i; + for (i=0; windows_socket_errors[i].code >= 0; ++i) { + if (e == windows_socket_errors[i].code) + return windows_socket_errors[i].msg; + } + return strerror(e); +} +#endif /* defined(_WIN32) */ diff --git a/src/lib/net/socket.h b/src/lib/net/socket.h new file mode 100644 index 0000000000..0909619510 --- /dev/null +++ b/src/lib/net/socket.h @@ -0,0 +1,118 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file socket.h + * \brief Header for socket.c + **/ + +#ifndef TOR_SOCKET_H +#define TOR_SOCKET_H + +#include "orconfig.h" +#include "lib/cc/torint.h" +#include "lib/net/nettypes.h" +#include "lib/testsupport/testsupport.h" + +#include <errno.h> + +struct sockaddr; + +int tor_close_socket_simple(tor_socket_t s); +MOCK_DECL(int, tor_close_socket, (tor_socket_t s)); +void tor_take_socket_ownership(tor_socket_t s); +void tor_release_socket_ownership(tor_socket_t s); +tor_socket_t tor_open_socket_with_extensions( + int domain, int type, int protocol, + int cloexec, int nonblock); +MOCK_DECL(tor_socket_t,tor_open_socket,(int domain, int type, int protocol)); +tor_socket_t tor_open_socket_nonblocking(int domain, int type, int protocol); +tor_socket_t tor_accept_socket(tor_socket_t sockfd, struct sockaddr *addr, + socklen_t *len); +tor_socket_t tor_accept_socket_nonblocking(tor_socket_t sockfd, + struct sockaddr *addr, + socklen_t *len); +tor_socket_t tor_accept_socket_with_extensions(tor_socket_t sockfd, + struct sockaddr *addr, + socklen_t *len, + int cloexec, int nonblock); +MOCK_DECL(tor_socket_t, tor_connect_socket,(tor_socket_t socket, + const struct sockaddr *address, + socklen_t address_len)); +int get_n_open_sockets(void); + +MOCK_DECL(int,tor_getsockname,(tor_socket_t socket, struct sockaddr *address, + socklen_t *address_len)); +struct tor_addr_t; +int tor_addr_from_getsockname(struct tor_addr_t *addr_out, tor_socket_t sock); + +#define tor_socket_send(s, buf, len, flags) send(s, buf, len, flags) +#define tor_socket_recv(s, buf, len, flags) recv(s, buf, len, flags) + +int set_socket_nonblocking(tor_socket_t socket); +int tor_socketpair(int family, int type, int protocol, tor_socket_t fd[2]); +int network_init(void); + +int get_max_sockets(void); +void set_max_sockets(int); + +ssize_t write_all_to_socket(tor_socket_t fd, const char *buf, size_t count); +ssize_t read_all_from_socket(tor_socket_t fd, char *buf, size_t count); + +/* For stupid historical reasons, windows sockets have an independent + * set of errnos, and an independent way to get them. Also, you can't + * always believe WSAEWOULDBLOCK. Use the macros below to compare + * errnos against expected values, and use tor_socket_errno to find + * the actual errno after a socket operation fails. + */ +#if defined(_WIN32) +/** Expands to WSA<b>e</b> on Windows, and to <b>e</b> elsewhere. */ +#define SOCK_ERRNO(e) WSA##e +/** Return true if e is EAGAIN or the local equivalent. */ +#define ERRNO_IS_EAGAIN(e) ((e) == EAGAIN || (e) == WSAEWOULDBLOCK) +/** Return true if e is EINPROGRESS or the local equivalent. */ +#define ERRNO_IS_EINPROGRESS(e) ((e) == WSAEINPROGRESS) +/** Return true if e is EINPROGRESS or the local equivalent as returned by + * a call to connect(). */ +#define ERRNO_IS_CONN_EINPROGRESS(e) \ + ((e) == WSAEINPROGRESS || (e)== WSAEINVAL || (e) == WSAEWOULDBLOCK) +/** Return true if e is EAGAIN or another error indicating that a call to + * accept() has no pending connections to return. */ +#define ERRNO_IS_ACCEPT_EAGAIN(e) ERRNO_IS_EAGAIN(e) +/** Return true if e is EMFILE or another error indicating that a call to + * accept() has failed because we're out of fds or something. */ +#define ERRNO_IS_RESOURCE_LIMIT(e) \ + ((e) == WSAEMFILE || (e) == WSAENOBUFS) +/** Return true if e is EADDRINUSE or the local equivalent. */ +#define ERRNO_IS_EADDRINUSE(e) ((e) == WSAEADDRINUSE) +/** Return true if e is EINTR or the local equivalent */ +#define ERRNO_IS_EINTR(e) ((e) == WSAEINTR || 0) +int tor_socket_errno(tor_socket_t sock); +const char *tor_socket_strerror(int e); +#else /* !(defined(_WIN32)) */ +#define SOCK_ERRNO(e) e +#if EAGAIN == EWOULDBLOCK +/* || 0 is for -Wparentheses-equality (-Wall?) appeasement under clang */ +#define ERRNO_IS_EAGAIN(e) ((e) == EAGAIN || 0) +#else +#define ERRNO_IS_EAGAIN(e) ((e) == EAGAIN || (e) == EWOULDBLOCK) +#endif /* EAGAIN == EWOULDBLOCK */ +#define ERRNO_IS_EINTR(e) ((e) == EINTR || 0) +#define ERRNO_IS_EINPROGRESS(e) ((e) == EINPROGRESS || 0) +#define ERRNO_IS_CONN_EINPROGRESS(e) ((e) == EINPROGRESS || 0) +#define ERRNO_IS_ACCEPT_EAGAIN(e) \ + (ERRNO_IS_EAGAIN(e) || (e) == ECONNABORTED) +#define ERRNO_IS_RESOURCE_LIMIT(e) \ + ((e) == EMFILE || (e) == ENFILE || (e) == ENOBUFS || (e) == ENOMEM) +#define ERRNO_IS_EADDRINUSE(e) (((e) == EADDRINUSE) || 0) +#define tor_socket_errno(sock) (errno) +#define tor_socket_strerror(e) strerror(e) +#endif /* defined(_WIN32) */ + +#if defined(_WIN32) && !defined(SIO_IDEAL_SEND_BACKLOG_QUERY) +#define SIO_IDEAL_SEND_BACKLOG_QUERY 0x4004747b +#endif + +#endif diff --git a/src/lib/net/socketpair.c b/src/lib/net/socketpair.c new file mode 100644 index 0000000000..10eb749735 --- /dev/null +++ b/src/lib/net/socketpair.c @@ -0,0 +1,214 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ + +#include "lib/cc/torint.h" +#include "lib/net/socketpair.h" +#include "lib/net/inaddr_st.h" +#include "lib/arch/bytes.h" + +#include <errno.h> +#include <string.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#define socket_errno() (WSAGetLastError()) +#define SOCKET_EPROTONOSUPPORT WSAEPROTONOSUPPORT +#else +#define closesocket(x) close(x) +#define socket_errno() (errno) +#define SOCKET_EPROTONOSUPPORT EPROTONOSUPPORT +#endif + +#ifdef NEED_ERSATZ_SOCKETPAIR + +// Avoid warning about call to memcmp. +#define raw_memcmp memcmp + +/** + * Return a new socket that is bound and listening on the loopback interface + * of family <b>family</b> for a socket of type <b>type</b>. On failure return + * TOR_INVALID_SOCKET. + */ +static tor_socket_t +get_local_listener(int family, int type) +{ + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct sockaddr *sa; + int len; + + memset(&sin, 0, sizeof(sin)); + memset(&sin6, 0, sizeof(sin6)); + + tor_socket_t sock = TOR_INVALID_SOCKET; + sock = socket(family, type, 0); + if (!SOCKET_OK(sock)) { + return TOR_INVALID_SOCKET; + } + + if (family == AF_INET) { + sa = (struct sockaddr *) &sin; + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = tor_htonl(0x7f000001); + len = sizeof(sin); + } else { + sa = (struct sockaddr *) &sin6; + sin6.sin6_family = AF_INET6; + sin6.sin6_addr.s6_addr[15] = 1; + len = sizeof(sin6); + } + + if (bind(sock, sa, len) == -1) + goto err; + if (listen(sock, 1) == -1) + goto err; + + return sock; + err: + closesocket(sock); + return TOR_INVALID_SOCKET; +} + +/** + * Return true iff sa1 and sa2 are equivalent AF_INET or AF_INET6 addresses. + */ +static int +sockaddr_eq(struct sockaddr *sa1, struct sockaddr *sa2) +{ + if (sa1->sa_family != sa2->sa_family) + return 0; + + if (sa1->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6_1 = (struct sockaddr_in6 *) sa1; + struct sockaddr_in6 *sin6_2 = (struct sockaddr_in6 *) sa2; + return sin6_1->sin6_port == sin6_2->sin6_port && + 0==raw_memcmp(sin6_1->sin6_addr.s6_addr, sin6_2->sin6_addr.s6_addr, 16); + } else if (sa1->sa_family == AF_INET) { + struct sockaddr_in *sin_1 = (struct sockaddr_in *) sa1; + struct sockaddr_in *sin_2 = (struct sockaddr_in *) sa2; + return sin_1->sin_port == sin_2->sin_port && + sin_1->sin_addr.s_addr == sin_2->sin_addr.s_addr; + } else { + return 0; + } +} + +/** + * Helper used to implement socketpair on systems that lack it, by + * making a direct connection to localhost. + */ +int +tor_ersatz_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) +{ + /* This socketpair does not work when localhost is down. So + * it's really not the same thing at all. But it's close enough + * for now, and really, when localhost is down sometimes, we + * have other problems too. + */ + tor_socket_t listener = TOR_INVALID_SOCKET; + tor_socket_t connector = TOR_INVALID_SOCKET; + tor_socket_t acceptor = TOR_INVALID_SOCKET; + struct sockaddr_storage accepted_addr_ss; + struct sockaddr_storage connect_addr_ss; + struct sockaddr *connect_addr = (struct sockaddr *) &connect_addr_ss; + struct sockaddr *accepted_addr = (struct sockaddr *) &accepted_addr_ss; + socklen_t size; + int saved_errno = -1; + int ersatz_domain = AF_INET; + socklen_t addrlen = sizeof(struct sockaddr_in); + + memset(&accepted_addr_ss, 0, sizeof(accepted_addr_ss)); + memset(&connect_addr_ss, 0, sizeof(connect_addr_ss)); + + if (protocol +#ifdef AF_UNIX + || family != AF_UNIX +#endif + ) { +#ifdef _WIN32 + return -WSAEAFNOSUPPORT; +#else + return -EAFNOSUPPORT; +#endif + } + if (!fd) { + return -EINVAL; + } + + listener = get_local_listener(ersatz_domain, type); + if (!SOCKET_OK(listener)) { + int first_errno = socket_errno(); + if (first_errno == SOCKET_EPROTONOSUPPORT) { + /* Assume we're on an IPv6-only system */ + ersatz_domain = AF_INET6; + addrlen = sizeof(struct sockaddr_in6); + listener = get_local_listener(ersatz_domain, type); + } + if (!SOCKET_OK(listener)) { + /* Keep the previous behaviour, which was to return the IPv4 error. + * (This may be less informative on IPv6-only systems.) + * XX/teor - is there a better way to decide which errno to return? + * (I doubt we care much either way, once there is an error.) + */ + return -first_errno; + } + } + + connector = socket(ersatz_domain, type, 0); + if (!SOCKET_OK(connector)) + goto tidy_up_and_fail; + /* We want to find out the port number to connect to. */ + size = sizeof(connect_addr_ss); + if (getsockname(listener, connect_addr, &size) == -1) + goto tidy_up_and_fail; + if (size != addrlen) + goto abort_tidy_up_and_fail; + if (connect(connector, connect_addr, size) == -1) + goto tidy_up_and_fail; + + size = sizeof(accepted_addr_ss); + acceptor = accept(listener, accepted_addr, &size); + if (!SOCKET_OK(acceptor)) + goto tidy_up_and_fail; + if (size != addrlen) + goto abort_tidy_up_and_fail; + /* Now check we are talking to ourself by matching port and host on the + two sockets. */ + if (getsockname(connector, connect_addr, &size) == -1) + goto tidy_up_and_fail; + /* Set *_tor_addr and *_port to the address and port that was used */ + if (!sockaddr_eq(accepted_addr, connect_addr)) + goto abort_tidy_up_and_fail; + closesocket(listener); + fd[0] = connector; + fd[1] = acceptor; + return 0; + + abort_tidy_up_and_fail: +#ifdef _WIN32 + saved_errno = WSAECONNABORTED; +#else + saved_errno = ECONNABORTED; /* I hope this is portable and appropriate. */ +#endif + tidy_up_and_fail: + if (saved_errno < 0) + saved_errno = errno; + if (SOCKET_OK(listener)) + closesocket(listener); + if (SOCKET_OK(connector)) + closesocket(connector); + if (SOCKET_OK(acceptor)) + closesocket(acceptor); + return -saved_errno; +} + +#endif /* defined(NEED_ERSATZ_SOCKETPAIR) */ diff --git a/src/lib/net/socketpair.h b/src/lib/net/socketpair.h new file mode 100644 index 0000000000..6be0803881 --- /dev/null +++ b/src/lib/net/socketpair.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_SOCKETPAIR_H +#define TOR_SOCKETPAIR_H + +#include "orconfig.h" +#include "lib/testsupport/testsupport.h" +#include "lib/net/nettypes.h" + +#if !defined(HAVE_SOCKETPAIR) || defined(_WIN32) || defined(TOR_UNIT_TESTS) +#define NEED_ERSATZ_SOCKETPAIR +int tor_ersatz_socketpair(int family, int type, int protocol, + tor_socket_t fd[2]); +#endif + +#endif diff --git a/src/lib/net/socks5_status.h b/src/lib/net/socks5_status.h new file mode 100644 index 0000000000..e55242ce66 --- /dev/null +++ b/src/lib/net/socks5_status.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file socks5_status.h + * \brief Status codes used by the SOCKS5 protocol. + **/ + +/* NOTE: it probably isn't necessary to put this header in lib/net, but + * we need it in _some_ lower-level layer for now, since it is used by + * tools/tor-resolve.c. + */ + +#ifndef TOR_SOCKS5_STATUS_H +#define TOR_SOCKS5_STATUS_H + +/** Specified SOCKS5 status codes. */ +typedef enum { + SOCKS5_SUCCEEDED = 0x00, + SOCKS5_GENERAL_ERROR = 0x01, + SOCKS5_NOT_ALLOWED = 0x02, + SOCKS5_NET_UNREACHABLE = 0x03, + SOCKS5_HOST_UNREACHABLE = 0x04, + SOCKS5_CONNECTION_REFUSED = 0x05, + SOCKS5_TTL_EXPIRED = 0x06, + SOCKS5_COMMAND_NOT_SUPPORTED = 0x07, + SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED = 0x08, +} socks5_reply_status_t; + +#endif diff --git a/src/lib/osinfo/.may_include b/src/lib/osinfo/.may_include new file mode 100644 index 0000000000..058f6eb89c --- /dev/null +++ b/src/lib/osinfo/.may_include @@ -0,0 +1,5 @@ +orconfig.h + +lib/osinfo/*.h +lib/string/*.h +lib/testsupport/*.h diff --git a/src/lib/osinfo/include.am b/src/lib/osinfo/include.am new file mode 100644 index 0000000000..16c5812604 --- /dev/null +++ b/src/lib/osinfo/include.am @@ -0,0 +1,17 @@ + +noinst_LIBRARIES += src/lib/libtor-osinfo.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-osinfo-testing.a +endif + +src_lib_libtor_osinfo_a_SOURCES = \ + src/lib/osinfo/uname.c + +src_lib_libtor_osinfo_testing_a_SOURCES = \ + $(src_lib_libtor_osinfo_a_SOURCES) +src_lib_libtor_osinfo_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_osinfo_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/osinfo/uname.h diff --git a/src/lib/osinfo/uname.c b/src/lib/osinfo/uname.c new file mode 100644 index 0000000000..2b37ff136c --- /dev/null +++ b/src/lib/osinfo/uname.c @@ -0,0 +1,149 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file uname.c + * \brief Look up a description of the operating system. + **/ + +#include "orconfig.h" +#include "lib/osinfo/uname.h" + +#include "lib/string/compat_string.h" +#include "lib/string/printf.h" + +#ifdef HAVE_UNAME +#include <sys/utsname.h> +#endif +#ifdef _WIN32 +#include <windows.h> +#endif +#include <string.h> + +/** Hold the result of our call to <b>uname</b>. */ +static char uname_result[256]; +/** True iff uname_result is set. */ +static int uname_result_is_set = 0; + +/** Return a pointer to a description of our platform. + */ +MOCK_IMPL(const char *, +get_uname,(void)) +{ +#ifdef HAVE_UNAME + struct utsname u; +#endif + if (!uname_result_is_set) { +#ifdef HAVE_UNAME + if (uname(&u) != -1) { + /* (Linux says 0 is success, Solaris says 1 is success) */ + strlcpy(uname_result, u.sysname, sizeof(uname_result)); + } else +#endif /* defined(HAVE_UNAME) */ + { +#ifdef _WIN32 + OSVERSIONINFOEX info; + int i; + int is_client = 0; + int is_server = 0; + const char *plat = NULL; + static struct { + unsigned major; unsigned minor; + const char *client_version; const char *server_version; + } win_version_table[] = { + /* This table must be sorted in descending order. + * Sources: + * https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions + * https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ + * ns-winnt-_osversioninfoexa#remarks + */ + /* Windows Server 2019 is indistinguishable from Windows Server 2016 + * using GetVersionEx(). + { 10, 0, NULL, "Windows Server 2019" }, */ + { 10, 0, "Windows 10", "Windows Server 2016" }, + { 6, 3, "Windows 8.1", "Windows Server 2012 R2" }, + { 6, 2, "Windows 8", "Windows Server 2012" }, + { 6, 1, "Windows 7", "Windows Server 2008 R2" }, + { 6, 0, "Windows Vista", "Windows Server 2008" }, + { 5, 2, "Windows XP Professional", "Windows Server 2003" }, + /* Windows XP did not have a server version, but we need something here */ + { 5, 1, "Windows XP", "Windows XP Server" }, + { 5, 0, "Windows 2000 Professional", "Windows 2000 Server" }, + /* Earlier versions are not supported by GetVersionEx(). */ + { 0, 0, NULL, NULL } + }; + memset(&info, 0, sizeof(info)); + info.dwOSVersionInfoSize = sizeof(info); + if (! GetVersionEx((LPOSVERSIONINFO)&info)) { + strlcpy(uname_result, "Bizarre version of Windows where GetVersionEx" + " doesn't work.", sizeof(uname_result)); + uname_result_is_set = 1; + return uname_result; + } +#ifdef VER_NT_SERVER + if (info.wProductType == VER_NT_SERVER || + info.wProductType == VER_NT_DOMAIN_CONTROLLER) { + is_server = 1; + } else { + is_client = 1; + } +#endif /* defined(VER_NT_SERVER) */ + /* Search the version table for a matching version */ + for (i=0; win_version_table[i].major>0; ++i) { + if (win_version_table[i].major == info.dwMajorVersion && + win_version_table[i].minor == info.dwMinorVersion) { + if (is_server) { + plat = win_version_table[i].server_version; + } else { + /* Use client versions for clients, and when we don't know if it + * is a client or a server. */ + plat = win_version_table[i].client_version; + } + break; + } + } + if (plat) { + strlcpy(uname_result, plat, sizeof(uname_result)); + } else { + if (info.dwMajorVersion > win_version_table[0].major || + (info.dwMajorVersion == win_version_table[0].major && + info.dwMinorVersion > win_version_table[0].minor)) + tor_snprintf(uname_result, sizeof(uname_result), + "Very recent version of Windows [major=%d,minor=%d]", + (int)info.dwMajorVersion,(int)info.dwMinorVersion); + else + tor_snprintf(uname_result, sizeof(uname_result), + "Unrecognized version of Windows [major=%d,minor=%d]", + (int)info.dwMajorVersion,(int)info.dwMinorVersion); + } + /* Now append extra information to the name. + * + * Microsoft's API documentation says that on Windows 8.1 and later, + * GetVersionEx returns Windows 8 (6.2) for applications without an + * app compatibility manifest (including tor's default build). + * + * But in our testing, we have seen the actual Windows version on + * Windows Server 2012 R2, even without a manifest. */ + if (info.dwMajorVersion > 6 || + (info.dwMajorVersion == 6 && info.dwMinorVersion >= 2)) { + /* When GetVersionEx() returns Windows 8, the actual OS may be any + * later version. */ + strlcat(uname_result, " [or later]", sizeof(uname_result)); + } + /* When we don't know if the OS is a client or server version, we use + * the client version, and this qualifier. */ + if (!is_server && !is_client) { + strlcat(uname_result, " [client or server]", sizeof(uname_result)); + } +#else /* !(defined(_WIN32)) */ + /* LCOV_EXCL_START -- can't provoke uname failure */ + strlcpy(uname_result, "Unknown platform", sizeof(uname_result)); + /* LCOV_EXCL_STOP */ +#endif /* defined(_WIN32) */ + } + uname_result_is_set = 1; + } + return uname_result; +} diff --git a/src/lib/osinfo/uname.h b/src/lib/osinfo/uname.h new file mode 100644 index 0000000000..fcce629074 --- /dev/null +++ b/src/lib/osinfo/uname.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file uname.h + * \brief Header for uname.c + **/ + +#ifndef HAVE_TOR_UNAME_H +#define HAVE_TOR_UNAME_H + +#include "lib/testsupport/testsupport.h" + +MOCK_DECL(const char *, get_uname,(void)); + +#endif diff --git a/src/lib/process/.may_include b/src/lib/process/.may_include new file mode 100644 index 0000000000..05414d2a96 --- /dev/null +++ b/src/lib/process/.may_include @@ -0,0 +1,17 @@ +orconfig.h + +lib/cc/*.h +lib/container/*.h +lib/ctime/*.h +lib/err/*.h +lib/intmath/*.h +lib/fs/*.h +lib/log/*.h +lib/malloc/*.h +lib/net/*.h +lib/process/*.h +lib/string/*.h +lib/testsupport/*.h +lib/thread/*.h + +ht.h
\ No newline at end of file diff --git a/src/lib/process/daemon.c b/src/lib/process/daemon.c new file mode 100644 index 0000000000..3b90bef671 --- /dev/null +++ b/src/lib/process/daemon.c @@ -0,0 +1,187 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file daemon.c + * \brief Run the tor process in the background (unix only) + **/ + +#include "orconfig.h" +#include "lib/process/daemon.h" + +#ifndef _WIN32 + +#include "lib/fs/files.h" +#include "lib/log/log.h" +#include "lib/thread/threads.h" + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +/* Based on code contributed by christian grothoff */ +/** True iff we've called start_daemon(). */ +static int start_daemon_called = 0; +/** True iff we've called finish_daemon(). */ +static int finish_daemon_called = 0; +/** Socketpair used to communicate between parent and child process while + * daemonizing. */ +static int daemon_filedes[2]; + +/** + * Return true iff we've called start_daemon() at least once. + */ +bool +start_daemon_has_been_called(void) +{ + return start_daemon_called != 0; +} + +/** Start putting the process into daemon mode: fork and drop all resources + * except standard fds. The parent process never returns, but stays around + * until finish_daemon is called. (Note: it's safe to call this more + * than once: calls after the first are ignored.) Return true if we actually + * forked and this is the child; false otherwise. + */ +int +start_daemon(void) +{ + pid_t pid; + + if (start_daemon_called) + return 0; + start_daemon_called = 1; + + if (pipe(daemon_filedes)) { + /* LCOV_EXCL_START */ + log_err(LD_GENERAL,"pipe failed; exiting. Error was %s", strerror(errno)); + exit(1); // exit ok: during daemonize, pipe failed. + /* LCOV_EXCL_STOP */ + } + pid = fork(); + if (pid < 0) { + /* LCOV_EXCL_START */ + log_err(LD_GENERAL,"fork failed. Exiting."); + exit(1); // exit ok: during daemonize, fork failed + /* LCOV_EXCL_STOP */ + } + if (pid) { /* Parent */ + int ok; + char c; + + close(daemon_filedes[1]); /* we only read */ + ok = -1; + while (0 < read(daemon_filedes[0], &c, sizeof(char))) { + if (c == '.') + ok = 1; + } + fflush(stdout); + if (ok == 1) + exit(0); // exit ok: during daemonize, daemonizing. + else + exit(1); /* child reported error. exit ok: daemonize failed. */ + return 0; // LCOV_EXCL_LINE unreachable + } else { /* Child */ + close(daemon_filedes[0]); /* we only write */ + + (void) setsid(); /* Detach from controlling terminal */ + /* + * Fork one more time, so the parent (the session group leader) can exit. + * This means that we, as a non-session group leader, can never regain a + * controlling terminal. This part is recommended by Stevens's + * _Advanced Programming in the Unix Environment_. + */ + if (fork() != 0) { + exit(0); // exit ok: during daemonize, fork failed (2) + } + set_main_thread(); /* We are now the main thread. */ + + return 1; + } +} + +/** Finish putting the process into daemon mode: drop standard fds, and tell + * the parent process to exit. (Note: it's safe to call this more than once: + * calls after the first are ignored. Calls start_daemon first if it hasn't + * been called already.) Return true if we actually did a fork; false if we + * didn't. + */ +int +finish_daemon(const char *desired_cwd) +{ + int nullfd; + char c = '.'; + if (finish_daemon_called) + return 0; + if (!start_daemon_called) + start_daemon(); + finish_daemon_called = 1; + + if (!desired_cwd) + desired_cwd = "/"; + /* Don't hold the wrong FS mounted */ + if (chdir(desired_cwd) < 0) { + log_err(LD_GENERAL,"chdir to \"%s\" failed. Exiting.",desired_cwd); + exit(1); // exit ok: during daemonize, chdir failed. + } + + nullfd = tor_open_cloexec("/dev/null", O_RDWR, 0); + if (nullfd < 0) { + /* LCOV_EXCL_START */ + log_err(LD_GENERAL,"/dev/null can't be opened. Exiting."); + exit(1); // exit ok: during daemonize, couldn't open /dev/null + /* LCOV_EXCL_STOP */ + } + /* close fds linking to invoking terminal, but + * close usual incoming fds, but redirect them somewhere + * useful so the fds don't get reallocated elsewhere. + */ + if (dup2(nullfd,0) < 0 || + dup2(nullfd,1) < 0 || + dup2(nullfd,2) < 0) { + /* LCOV_EXCL_START */ + log_err(LD_GENERAL,"dup2 failed. Exiting."); + exit(1); // exit ok: during daemonize, dup2 failed. + /* LCOV_EXCL_STOP */ + } + if (nullfd > 2) + close(nullfd); + /* signal success */ + if (write(daemon_filedes[1], &c, sizeof(char)) != sizeof(char)) { + log_err(LD_GENERAL,"write failed. Exiting."); + } + close(daemon_filedes[1]); + + return 0; +} +#else /* !(!defined(_WIN32)) */ +/* defined(_WIN32) */ +int +start_daemon(void) +{ + return 0; +} +int +finish_daemon(const char *cp) +{ + (void)cp; + return 0; +} +bool +start_daemon_has_been_called(void) +{ + return false; +} + +#endif /* !defined(_WIN32) */ diff --git a/src/lib/process/daemon.h b/src/lib/process/daemon.h new file mode 100644 index 0000000000..20920e0aae --- /dev/null +++ b/src/lib/process/daemon.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file daemon.h + * \brief Header for daemon.c + **/ + +#ifndef TOR_DAEMON_H +#define TOR_DAEMON_H + +#include <stdbool.h> + +int start_daemon(void); +int finish_daemon(const char *desired_cwd); + +bool start_daemon_has_been_called(void); + +#endif diff --git a/src/lib/process/env.c b/src/lib/process/env.c new file mode 100644 index 0000000000..0060200ba1 --- /dev/null +++ b/src/lib/process/env.c @@ -0,0 +1,224 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file env.c + * \brief Inspect and manipulate the environment variables. + **/ + +#include "orconfig.h" +#include "lib/process/env.h" + +#include "lib/malloc/malloc.h" +#include "lib/ctime/di_ops.h" +#include "lib/container/smartlist.h" +#include "lib/log/util_bug.h" +#include "lib/log/log.h" +#include "lib/malloc/malloc.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_CRT_EXTERNS_H +/* For _NSGetEnviron on macOS */ +#include <crt_externs.h> +#endif + +#ifndef HAVE__NSGETENVIRON +#ifndef HAVE_EXTERN_ENVIRON_DECLARED +/* Some platforms declare environ under some circumstances, others don't. */ +#ifndef RUNNING_DOXYGEN +extern char **environ; +#endif +#endif /* !defined(HAVE_EXTERN_ENVIRON_DECLARED) */ +#endif /* !defined(HAVE__NSGETENVIRON) */ + +/** Return the current environment. This is a portable replacement for + * 'environ'. */ +char ** +get_environment(void) +{ +#ifdef HAVE__NSGETENVIRON + /* This is for compatibility between OSX versions. Otherwise (for example) + * when we do a mostly-static build on OSX 10.7, the resulting binary won't + * work on OSX 10.6. */ + return *_NSGetEnviron(); +#else /* !(defined(HAVE__NSGETENVIRON)) */ + return environ; +#endif /* defined(HAVE__NSGETENVIRON) */ +} + +/** Helper: return the number of characters in <b>s</b> preceding the first + * occurrence of <b>ch</b>. If <b>ch</b> does not occur in <b>s</b>, return + * the length of <b>s</b>. Should be equivalent to strspn(s, "ch"). */ +static inline size_t +str_num_before(const char *s, char ch) +{ + const char *cp = strchr(s, ch); + if (cp) + return cp - s; + else + return strlen(s); +} + +/** Return non-zero iff getenv would consider <b>s1</b> and <b>s2</b> + * to have the same name as strings in a process's environment. */ +int +environment_variable_names_equal(const char *s1, const char *s2) +{ + size_t s1_name_len = str_num_before(s1, '='); + size_t s2_name_len = str_num_before(s2, '='); + + return (s1_name_len == s2_name_len && + tor_memeq(s1, s2, s1_name_len)); +} + +/** Free <b>env</b> (assuming it was produced by + * process_environment_make). */ +void +process_environment_free_(process_environment_t *env) +{ + if (env == NULL) return; + + /* As both an optimization hack to reduce consing on Unixoid systems + * and a nice way to ensure that some otherwise-Windows-specific + * code will always get tested before changes to it get merged, the + * strings which env->unixoid_environment_block points to are packed + * into env->windows_environment_block. */ + tor_free(env->unixoid_environment_block); + tor_free(env->windows_environment_block); + + tor_free(env); +} + +/** Make a process_environment_t containing the environment variables + * specified in <b>env_vars</b> (as C strings of the form + * "NAME=VALUE"). */ +process_environment_t * +process_environment_make(struct smartlist_t *env_vars) +{ + process_environment_t *env = tor_malloc_zero(sizeof(process_environment_t)); + int n_env_vars = smartlist_len(env_vars); + int i; + size_t total_env_length; + smartlist_t *env_vars_sorted; + + tor_assert(n_env_vars + 1 != 0); + env->unixoid_environment_block = tor_calloc(n_env_vars + 1, sizeof(char *)); + /* env->unixoid_environment_block is already NULL-terminated, + * because we assume that NULL == 0 (and check that during compilation). */ + + total_env_length = 1; /* terminating NUL of terminating empty string */ + for (i = 0; i < n_env_vars; ++i) { + const char *s = smartlist_get(env_vars, (int)i); + size_t slen = strlen(s); + + tor_assert(slen + 1 != 0); + tor_assert(slen + 1 < SIZE_MAX - total_env_length); + total_env_length += slen + 1; + } + + env->windows_environment_block = tor_malloc_zero(total_env_length); + /* env->windows_environment_block is already + * (NUL-terminated-empty-string)-terminated. */ + + /* Some versions of Windows supposedly require that environment + * blocks be sorted. Or maybe some Windows programs (or their + * runtime libraries) fail to look up strings in non-sorted + * environment blocks. + * + * Also, sorting strings makes it easy to find duplicate environment + * variables and environment-variable strings without an '=' on all + * OSes, and they can cause badness. Let's complain about those. */ + env_vars_sorted = smartlist_new(); + smartlist_add_all(env_vars_sorted, env_vars); + smartlist_sort_strings(env_vars_sorted); + + /* Now copy the strings into the environment blocks. */ + { + char *cp = env->windows_environment_block; + const char *prev_env_var = NULL; + + for (i = 0; i < n_env_vars; ++i) { + const char *s = smartlist_get(env_vars_sorted, (int)i); + size_t slen = strlen(s); + size_t s_name_len = str_num_before(s, '='); + + if (s_name_len == slen) { + log_warn(LD_GENERAL, + "Preparing an environment containing a variable " + "without a value: %s", + s); + } + if (prev_env_var != NULL && + environment_variable_names_equal(s, prev_env_var)) { + log_warn(LD_GENERAL, + "Preparing an environment containing two variables " + "with the same name: %s and %s", + prev_env_var, s); + } + + prev_env_var = s; + + /* Actually copy the string into the environment. */ + memcpy(cp, s, slen+1); + env->unixoid_environment_block[i] = cp; + cp += slen+1; + } + + tor_assert(cp == env->windows_environment_block + total_env_length - 1); + } + + smartlist_free(env_vars_sorted); + + return env; +} + +/** Return a newly allocated smartlist containing every variable in + * this process's environment, as a NUL-terminated string of the form + * "NAME=VALUE". Note that on some/many/most/all OSes, the parent + * process can put strings not of that form in our environment; + * callers should try to not get crashed by that. + * + * The returned strings are heap-allocated, and must be freed by the + * caller. */ +struct smartlist_t * +get_current_process_environment_variables(void) +{ + smartlist_t *sl = smartlist_new(); + + char **environ_tmp; /* Not const char ** ? Really? */ + for (environ_tmp = get_environment(); *environ_tmp; ++environ_tmp) { + smartlist_add_strdup(sl, *environ_tmp); + } + + return sl; +} + +/** For each string s in <b>env_vars</b> such that + * environment_variable_names_equal(s, <b>new_var</b>), remove it; if + * <b>free_p</b> is non-zero, call <b>free_old</b>(s). If + * <b>new_var</b> contains '=', insert it into <b>env_vars</b>. */ +void +set_environment_variable_in_smartlist(struct smartlist_t *env_vars, + const char *new_var, + void (*free_old)(void*), + int free_p) +{ + SMARTLIST_FOREACH_BEGIN(env_vars, const char *, s) { + if (environment_variable_names_equal(s, new_var)) { + SMARTLIST_DEL_CURRENT(env_vars, s); + if (free_p) { + free_old((void *)s); + } + } + } SMARTLIST_FOREACH_END(s); + + if (strchr(new_var, '=') != NULL) { + smartlist_add(env_vars, (void *)new_var); + } +} diff --git a/src/lib/process/env.h b/src/lib/process/env.h new file mode 100644 index 0000000000..15d59351e0 --- /dev/null +++ b/src/lib/process/env.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file env.h + * \brief Header for env.c + **/ + +#ifndef TOR_ENV_H +#define TOR_ENV_H + +char **get_environment(void); + +struct smartlist_t; + +int environment_variable_names_equal(const char *s1, const char *s2); + +/* DOCDOC process_environment_t */ +typedef struct process_environment_t { + /** A pointer to a sorted empty-string-terminated sequence of + * NUL-terminated strings of the form "NAME=VALUE". */ + char *windows_environment_block; + /** A pointer to a NULL-terminated array of pointers to + * NUL-terminated strings of the form "NAME=VALUE". */ + char **unixoid_environment_block; +} process_environment_t; + +process_environment_t *process_environment_make(struct smartlist_t *env_vars); +void process_environment_free_(process_environment_t *env); +#define process_environment_free(env) \ + FREE_AND_NULL(process_environment_t, process_environment_free_, (env)) + +struct smartlist_t *get_current_process_environment_variables(void); + +void set_environment_variable_in_smartlist(struct smartlist_t *env_vars, + const char *new_var, + void (*free_old)(void*), + int free_p); +#endif diff --git a/src/lib/process/include.am b/src/lib/process/include.am new file mode 100644 index 0000000000..c6cc3a6699 --- /dev/null +++ b/src/lib/process/include.am @@ -0,0 +1,29 @@ + +noinst_LIBRARIES += src/lib/libtor-process.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-process-testing.a +endif + +src_lib_libtor_process_a_SOURCES = \ + src/lib/process/daemon.c \ + src/lib/process/env.c \ + src/lib/process/pidfile.c \ + src/lib/process/restrict.c \ + src/lib/process/setuid.c \ + src/lib/process/subprocess.c \ + src/lib/process/waitpid.c + +src_lib_libtor_process_testing_a_SOURCES = \ + $(src_lib_libtor_process_a_SOURCES) +src_lib_libtor_process_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_process_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/process/daemon.h \ + src/lib/process/env.h \ + src/lib/process/pidfile.h \ + src/lib/process/restrict.h \ + src/lib/process/setuid.h \ + src/lib/process/subprocess.h \ + src/lib/process/waitpid.h diff --git a/src/lib/process/pidfile.c b/src/lib/process/pidfile.c new file mode 100644 index 0000000000..1b9d1c6d25 --- /dev/null +++ b/src/lib/process/pidfile.c @@ -0,0 +1,52 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file pidfile.c + * \brief Record this process's PID to disk. + **/ + +#include "orconfig.h" +#include "lib/process/pidfile.h" + +#include "lib/log/log.h" + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +/** Write the current process ID, followed by NL, into <b>filename</b>. + * Return 0 on success, -1 on failure. + */ +int +write_pidfile(const char *filename) +{ + FILE *pidfile; + + if ((pidfile = fopen(filename, "w")) == NULL) { + log_warn(LD_FS, "Unable to open \"%s\" for writing: %s", filename, + strerror(errno)); + return -1; + } else { +#ifdef _WIN32 + int pid = (int)_getpid(); +#else + int pid = (int)getpid(); +#endif + int rv = 0; + if (fprintf(pidfile, "%d\n", pid) < 0) + rv = -1; + if (fclose(pidfile) < 0) + rv = -1; + return rv; + } +} diff --git a/src/lib/process/pidfile.h b/src/lib/process/pidfile.h new file mode 100644 index 0000000000..dfeb39e046 --- /dev/null +++ b/src/lib/process/pidfile.h @@ -0,0 +1,16 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file pidfile.h + * \brief Header for pidfile.c + **/ + +#ifndef TOR_PIDFILE_H +#define TOR_PIDFILE_H + +int write_pidfile(const char *filename); + +#endif diff --git a/src/lib/process/restrict.c b/src/lib/process/restrict.c new file mode 100644 index 0000000000..534b39d101 --- /dev/null +++ b/src/lib/process/restrict.c @@ -0,0 +1,285 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file restrict.c + * \brief Drop privileges from the current process. + **/ + +#include "orconfig.h" +#include "lib/process/restrict.h" +#include "lib/intmath/cmp.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/net/socket.h" + +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +/* We only use the linux prctl for now. There is no Win32 support; this may + * also work on various BSD systems and Mac OS X - send testing feedback! + * + * On recent Gnu/Linux kernels it is possible to create a system-wide policy + * that will prevent non-root processes from attaching to other processes + * unless they are the parent process; thus gdb can attach to programs that + * they execute but they cannot attach to other processes running as the same + * user. The system wide policy may be set with the sysctl + * kernel.yama.ptrace_scope or by inspecting + * /proc/sys/kernel/yama/ptrace_scope and it is 1 by default on Ubuntu 11.04. + * + * This ptrace scope will be ignored on Gnu/Linux for users with + * CAP_SYS_PTRACE and so it is very likely that root will still be able to + * attach to the Tor process. + */ +/** Attempt to disable debugger attachment: return 1 on success, -1 on + * failure, and 0 if we don't know how to try on this platform. */ +int +tor_disable_debugger_attach(void) +{ + int r = -1; + log_debug(LD_CONFIG, + "Attemping to disable debugger attachment to Tor for " + "unprivileged users."); +#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) \ + && defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) +#define TRIED_TO_DISABLE + r = prctl(PR_SET_DUMPABLE, 0); +#elif defined(__APPLE__) && defined(PT_DENY_ATTACH) +#define TRIED_TO_ATTACH + r = ptrace(PT_DENY_ATTACH, 0, 0, 0); +#endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) ... || ... */ + + // XXX: TODO - Mac OS X has dtrace and this may be disabled. + // XXX: TODO - Windows probably has something similar +#ifdef TRIED_TO_DISABLE + if (r == 0) { + log_debug(LD_CONFIG,"Debugger attachment disabled for " + "unprivileged users."); + return 1; + } else { + log_warn(LD_CONFIG, "Unable to disable debugger attaching: %s", + strerror(errno)); + } +#endif /* defined(TRIED_TO_DISABLE) */ +#undef TRIED_TO_DISABLE + return r; +} + +#if defined(HAVE_MLOCKALL) && HAVE_DECL_MLOCKALL && defined(RLIMIT_MEMLOCK) +#define HAVE_UNIX_MLOCKALL +#endif + +#ifdef HAVE_UNIX_MLOCKALL +/** Attempt to raise the current and max rlimit to infinity for our process. + * This only needs to be done once and can probably only be done when we have + * not already dropped privileges. + */ +static int +tor_set_max_memlock(void) +{ + /* Future consideration for Windows is probably SetProcessWorkingSetSize + * This is similar to setting the memory rlimit of RLIMIT_MEMLOCK + * http://msdn.microsoft.com/en-us/library/ms686234(VS.85).aspx + */ + + struct rlimit limit; + + /* RLIM_INFINITY is -1 on some platforms. */ + limit.rlim_cur = RLIM_INFINITY; + limit.rlim_max = RLIM_INFINITY; + + if (setrlimit(RLIMIT_MEMLOCK, &limit) == -1) { + if (errno == EPERM) { + log_warn(LD_GENERAL, "You appear to lack permissions to change memory " + "limits. Are you root?"); + } + log_warn(LD_GENERAL, "Unable to raise RLIMIT_MEMLOCK: %s", + strerror(errno)); + return -1; + } + + return 0; +} +#endif /* defined(HAVE_UNIX_MLOCKALL) */ + +/** Attempt to lock all current and all future memory pages. + * This should only be called once and while we're privileged. + * Like mlockall() we return 0 when we're successful and -1 when we're not. + * Unlike mlockall() we return 1 if we've already attempted to lock memory. + */ +int +tor_mlockall(void) +{ + static int memory_lock_attempted = 0; + + if (memory_lock_attempted) { + return 1; + } + + memory_lock_attempted = 1; + + /* + * Future consideration for Windows may be VirtualLock + * VirtualLock appears to implement mlock() but not mlockall() + * + * http://msdn.microsoft.com/en-us/library/aa366895(VS.85).aspx + */ + +#ifdef HAVE_UNIX_MLOCKALL + if (tor_set_max_memlock() == 0) { + log_debug(LD_GENERAL, "RLIMIT_MEMLOCK is now set to RLIM_INFINITY."); + } + + if (mlockall(MCL_CURRENT|MCL_FUTURE) == 0) { + log_info(LD_GENERAL, "Insecure OS paging is effectively disabled."); + return 0; + } else { + if (errno == ENOSYS) { + /* Apple - it's 2009! I'm looking at you. Grrr. */ + log_notice(LD_GENERAL, "It appears that mlockall() is not available on " + "your platform."); + } else if (errno == EPERM) { + log_notice(LD_GENERAL, "It appears that you lack the permissions to " + "lock memory. Are you root?"); + } + log_notice(LD_GENERAL, "Unable to lock all current and future memory " + "pages: %s", strerror(errno)); + return -1; + } +#else /* !(defined(HAVE_UNIX_MLOCKALL)) */ + log_warn(LD_GENERAL, "Unable to lock memory pages. mlockall() unsupported?"); + return -1; +#endif /* defined(HAVE_UNIX_MLOCKALL) */ +} + +/** Number of extra file descriptors to keep in reserve beyond those that we + * tell Tor it's allowed to use. */ +#define ULIMIT_BUFFER 32 /* keep 32 extra fd's beyond ConnLimit_ */ + +/** Learn the maximum allowed number of file descriptors, and tell the + * system we want to use up to that number. (Some systems have a low soft + * limit, and let us set it higher.) We compute this by finding the largest + * number that we can use. + * + * If the limit is below the reserved file descriptor value (ULIMIT_BUFFER), + * return -1 and <b>max_out</b> is untouched. + * + * If we can't find a number greater than or equal to <b>limit</b>, then we + * fail by returning -1 and <b>max_out</b> is untouched. + * + * If we are unable to set the limit value because of setrlimit() failing, + * return 0 and <b>max_out</b> is set to the current maximum value returned + * by getrlimit(). + * + * Otherwise, return 0 and store the maximum we found inside <b>max_out</b> + * and set <b>max_sockets</b> with that value as well.*/ +int +set_max_file_descriptors(rlim_t limit, int *max_out) +{ + if (limit < ULIMIT_BUFFER) { + log_warn(LD_CONFIG, + "ConnLimit must be at least %d. Failing.", ULIMIT_BUFFER); + return -1; + } + + /* Define some maximum connections values for systems where we cannot + * automatically determine a limit. Re Cygwin, see + * http://archives.seul.org/or/talk/Aug-2006/msg00210.html + * For an iPhone, 9999 should work. For Windows and all other unknown + * systems we use 15000 as the default. */ +#ifndef HAVE_GETRLIMIT +#if defined(CYGWIN) || defined(__CYGWIN__) + const char *platform = "Cygwin"; + const unsigned long MAX_CONNECTIONS = 3200; +#elif defined(_WIN32) + const char *platform = "Windows"; + const unsigned long MAX_CONNECTIONS = 15000; +#else + const char *platform = "unknown platforms with no getrlimit()"; + const unsigned long MAX_CONNECTIONS = 15000; +#endif /* defined(CYGWIN) || defined(__CYGWIN__) || ... */ + log_fn(LOG_INFO, LD_NET, + "This platform is missing getrlimit(). Proceeding."); + if (limit > MAX_CONNECTIONS) { + log_warn(LD_CONFIG, + "We do not support more than %lu file descriptors " + "on %s. Tried to raise to %lu.", + (unsigned long)MAX_CONNECTIONS, platform, (unsigned long)limit); + return -1; + } + limit = MAX_CONNECTIONS; +#else /* !(!defined(HAVE_GETRLIMIT)) */ + struct rlimit rlim; + + if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) { + log_warn(LD_NET, "Could not get maximum number of file descriptors: %s", + strerror(errno)); + return -1; + } + if (rlim.rlim_max < limit) { + log_warn(LD_CONFIG,"We need %lu file descriptors available, and we're " + "limited to %lu. Please change your ulimit -n.", + (unsigned long)limit, (unsigned long)rlim.rlim_max); + return -1; + } + + if (rlim.rlim_max > rlim.rlim_cur) { + log_info(LD_NET,"Raising max file descriptors from %lu to %lu.", + (unsigned long)rlim.rlim_cur, (unsigned long)rlim.rlim_max); + } + /* Set the current limit value so if the attempt to set the limit to the + * max fails at least we'll have a valid value of maximum sockets. */ + *max_out = (int)rlim.rlim_cur - ULIMIT_BUFFER; + set_max_sockets(*max_out); + rlim.rlim_cur = rlim.rlim_max; + + if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) { + int couldnt_set = 1; + const int setrlimit_errno = errno; +#ifdef OPEN_MAX + uint64_t try_limit = OPEN_MAX - ULIMIT_BUFFER; + if (errno == EINVAL && try_limit < (uint64_t) rlim.rlim_cur) { + /* On some platforms, OPEN_MAX is the real limit, and getrlimit() is + * full of nasty lies. I'm looking at you, OSX 10.5.... */ + rlim.rlim_cur = MIN((rlim_t) try_limit, rlim.rlim_cur); + if (setrlimit(RLIMIT_NOFILE, &rlim) == 0) { + if (rlim.rlim_cur < (rlim_t)limit) { + log_warn(LD_CONFIG, "We are limited to %lu file descriptors by " + "OPEN_MAX (%lu), and ConnLimit is %lu. Changing " + "ConnLimit; sorry.", + (unsigned long)try_limit, (unsigned long)OPEN_MAX, + (unsigned long)limit); + } else { + log_info(LD_CONFIG, "Dropped connection limit to %lu based on " + "OPEN_MAX (%lu); Apparently, %lu was too high and rlimit " + "lied to us.", + (unsigned long)try_limit, (unsigned long)OPEN_MAX, + (unsigned long)rlim.rlim_max); + } + couldnt_set = 0; + } + } +#endif /* defined(OPEN_MAX) */ + if (couldnt_set) { + log_warn(LD_CONFIG,"Couldn't set maximum number of file descriptors: %s", + strerror(setrlimit_errno)); + } + } + /* leave some overhead for logs, etc, */ + limit = rlim.rlim_cur; +#endif /* !defined(HAVE_GETRLIMIT) */ + + if (limit > INT_MAX) + limit = INT_MAX; + tor_assert(max_out); + *max_out = (int)limit - ULIMIT_BUFFER; + set_max_sockets(*max_out); + + return 0; +} diff --git a/src/lib/process/restrict.h b/src/lib/process/restrict.h new file mode 100644 index 0000000000..8491c99044 --- /dev/null +++ b/src/lib/process/restrict.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file restrict.h + * \brief Header for restrict.c + **/ + +#ifndef TOR_RESTRICT_H +#define TOR_RESTRICT_H + +#include "orconfig.h" +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +int tor_disable_debugger_attach(void); +int tor_mlockall(void); + +#if !defined(HAVE_RLIM_T) +typedef unsigned long rlim_t; +#endif +int set_max_file_descriptors(rlim_t limit, int *max_out); + +#endif /* !defined(TOR_RESTRICT_H) */ diff --git a/src/lib/process/setuid.c b/src/lib/process/setuid.c new file mode 100644 index 0000000000..6e8258f279 --- /dev/null +++ b/src/lib/process/setuid.c @@ -0,0 +1,386 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file setuid.c + * \brief Change the user ID after Tor has started (Unix only) + **/ + +#include "orconfig.h" +#include "lib/process/setuid.h" + +#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_CAP_SET_PROC) +#define HAVE_LINUX_CAPABILITIES +#endif + +#include "lib/container/smartlist.h" +#include "lib/fs/userdb.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_SYS_CAPABILITY_H +#include <sys/capability.h> +#endif +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#include <errno.h> +#include <string.h> + +#ifndef _WIN32 +/** Log details of current user and group credentials. Return 0 on + * success. Logs and return -1 on failure. + */ +static int +log_credential_status(void) +{ +/** Log level to use when describing non-error UID/GID status. */ +#define CREDENTIAL_LOG_LEVEL LOG_INFO + /* Real, effective and saved UIDs */ + uid_t ruid, euid, suid; + /* Read, effective and saved GIDs */ + gid_t rgid, egid, sgid; + /* Supplementary groups */ + gid_t *sup_gids = NULL; + int sup_gids_size; + /* Number of supplementary groups */ + int ngids; + + /* log UIDs */ +#ifdef HAVE_GETRESUID + if (getresuid(&ruid, &euid, &suid) != 0 ) { + log_warn(LD_GENERAL, "Error getting changed UIDs: %s", strerror(errno)); + return -1; + } else { + log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, + "UID is %u (real), %u (effective), %u (saved)", + (unsigned)ruid, (unsigned)euid, (unsigned)suid); + } +#else /* !(defined(HAVE_GETRESUID)) */ + /* getresuid is not present on MacOS X, so we can't get the saved (E)UID */ + ruid = getuid(); + euid = geteuid(); + (void)suid; + + log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, + "UID is %u (real), %u (effective), unknown (saved)", + (unsigned)ruid, (unsigned)euid); +#endif /* defined(HAVE_GETRESUID) */ + + /* log GIDs */ +#ifdef HAVE_GETRESGID + if (getresgid(&rgid, &egid, &sgid) != 0 ) { + log_warn(LD_GENERAL, "Error getting changed GIDs: %s", strerror(errno)); + return -1; + } else { + log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, + "GID is %u (real), %u (effective), %u (saved)", + (unsigned)rgid, (unsigned)egid, (unsigned)sgid); + } +#else /* !(defined(HAVE_GETRESGID)) */ + /* getresgid is not present on MacOS X, so we can't get the saved (E)GID */ + rgid = getgid(); + egid = getegid(); + (void)sgid; + log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, + "GID is %u (real), %u (effective), unknown (saved)", + (unsigned)rgid, (unsigned)egid); +#endif /* defined(HAVE_GETRESGID) */ + + /* log supplementary groups */ + sup_gids_size = 64; + sup_gids = tor_calloc(64, sizeof(gid_t)); + while ((ngids = getgroups(sup_gids_size, sup_gids)) < 0 && + errno == EINVAL && + sup_gids_size < NGROUPS_MAX) { + sup_gids_size *= 2; + sup_gids = tor_reallocarray(sup_gids, sizeof(gid_t), sup_gids_size); + } + + if (ngids < 0) { + log_warn(LD_GENERAL, "Error getting supplementary GIDs: %s", + strerror(errno)); + tor_free(sup_gids); + return -1; + } else { + int i, retval = 0; + char *s = NULL; + smartlist_t *elts = smartlist_new(); + + for (i = 0; i<ngids; i++) { + smartlist_add_asprintf(elts, "%u", (unsigned)sup_gids[i]); + } + + s = smartlist_join_strings(elts, " ", 0, NULL); + + log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Supplementary groups are: %s",s); + + tor_free(s); + SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp)); + smartlist_free(elts); + tor_free(sup_gids); + + return retval; + } + + return 0; +} +#endif /* !defined(_WIN32) */ + +/** Return true iff we were compiled with capability support, and capabilities + * seem to work. **/ +int +have_capability_support(void) +{ +#ifdef HAVE_LINUX_CAPABILITIES + cap_t caps = cap_get_proc(); + if (caps == NULL) + return 0; + cap_free(caps); + return 1; +#else /* !(defined(HAVE_LINUX_CAPABILITIES)) */ + return 0; +#endif /* defined(HAVE_LINUX_CAPABILITIES) */ +} + +#ifdef HAVE_LINUX_CAPABILITIES +/** Helper. Drop all capabilities but a small set, and set PR_KEEPCAPS as + * appropriate. + * + * If pre_setuid, retain only CAP_NET_BIND_SERVICE, CAP_SETUID, and + * CAP_SETGID, and use PR_KEEPCAPS to ensure that capabilities persist across + * setuid(). + * + * If not pre_setuid, retain only CAP_NET_BIND_SERVICE, and disable + * PR_KEEPCAPS. + * + * Return 0 on success, and -1 on failure. + */ +static int +drop_capabilities(int pre_setuid) +{ + /* We keep these three capabilities, and these only, as we setuid. + * After we setuid, we drop all but the first. */ + const cap_value_t caplist[] = { + CAP_NET_BIND_SERVICE, CAP_SETUID, CAP_SETGID + }; + const char *where = pre_setuid ? "pre-setuid" : "post-setuid"; + const int n_effective = pre_setuid ? 3 : 1; + const int n_permitted = pre_setuid ? 3 : 1; + const int n_inheritable = 1; + const int keepcaps = pre_setuid ? 1 : 0; + + /* Sets whether we keep capabilities across a setuid. */ + if (prctl(PR_SET_KEEPCAPS, keepcaps) < 0) { + log_warn(LD_CONFIG, "Unable to call prctl() %s: %s", + where, strerror(errno)); + return -1; + } + + cap_t caps = cap_get_proc(); + if (!caps) { + log_warn(LD_CONFIG, "Unable to call cap_get_proc() %s: %s", + where, strerror(errno)); + return -1; + } + cap_clear(caps); + + cap_set_flag(caps, CAP_EFFECTIVE, n_effective, caplist, CAP_SET); + cap_set_flag(caps, CAP_PERMITTED, n_permitted, caplist, CAP_SET); + cap_set_flag(caps, CAP_INHERITABLE, n_inheritable, caplist, CAP_SET); + + int r = cap_set_proc(caps); + cap_free(caps); + if (r < 0) { + log_warn(LD_CONFIG, "No permission to set capabilities %s: %s", + where, strerror(errno)); + return -1; + } + + return 0; +} +#endif /* defined(HAVE_LINUX_CAPABILITIES) */ + +/** Call setuid and setgid to run as <b>user</b> and switch to their + * primary group. Return 0 on success. On failure, log and return -1. + * + * If SWITCH_ID_KEEP_BINDLOW is set in 'flags', try to use the capability + * system to retain the abilitity to bind low ports. + * + * If SWITCH_ID_WARN_IF_NO_CAPS is set in flags, also warn if we have + * don't have capability support. + */ +int +switch_id(const char *user, const unsigned flags) +{ +#ifndef _WIN32 + const struct passwd *pw = NULL; + uid_t old_uid; + gid_t old_gid; + static int have_already_switched_id = 0; + const int keep_bindlow = !!(flags & SWITCH_ID_KEEP_BINDLOW); + const int warn_if_no_caps = !!(flags & SWITCH_ID_WARN_IF_NO_CAPS); + + tor_assert(user); + + if (have_already_switched_id) + return 0; + + /* Log the initial credential state */ + if (log_credential_status()) + return -1; + + log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Changing user and groups"); + + /* Get old UID/GID to check if we changed correctly */ + old_uid = getuid(); + old_gid = getgid(); + + /* Lookup the user and group information, if we have a problem, bail out. */ + pw = tor_getpwnam(user); + if (pw == NULL) { + log_warn(LD_CONFIG, "Error setting configured user: %s not found", user); + return -1; + } + +#ifdef HAVE_LINUX_CAPABILITIES + (void) warn_if_no_caps; + if (keep_bindlow) { + if (drop_capabilities(1)) + return -1; + } +#else /* !(defined(HAVE_LINUX_CAPABILITIES)) */ + (void) keep_bindlow; + if (warn_if_no_caps) { + log_warn(LD_CONFIG, "KeepBindCapabilities set, but no capability support " + "on this system."); + } +#endif /* defined(HAVE_LINUX_CAPABILITIES) */ + + /* Properly switch egid,gid,euid,uid here or bail out */ + if (setgroups(1, &pw->pw_gid)) { + log_warn(LD_GENERAL, "Error setting groups to gid %d: \"%s\".", + (int)pw->pw_gid, strerror(errno)); + if (old_uid == pw->pw_uid) { + log_warn(LD_GENERAL, "Tor is already running as %s. You do not need " + "the \"User\" option if you are already running as the user " + "you want to be. (If you did not set the User option in your " + "torrc, check whether it was specified on the command line " + "by a startup script.)", user); + } else { + log_warn(LD_GENERAL, "If you set the \"User\" option, you must start Tor" + " as root."); + } + return -1; + } + + if (setegid(pw->pw_gid)) { + log_warn(LD_GENERAL, "Error setting egid to %d: %s", + (int)pw->pw_gid, strerror(errno)); + return -1; + } + + if (setgid(pw->pw_gid)) { + log_warn(LD_GENERAL, "Error setting gid to %d: %s", + (int)pw->pw_gid, strerror(errno)); + return -1; + } + + if (setuid(pw->pw_uid)) { + log_warn(LD_GENERAL, "Error setting configured uid to %s (%d): %s", + user, (int)pw->pw_uid, strerror(errno)); + return -1; + } + + if (seteuid(pw->pw_uid)) { + log_warn(LD_GENERAL, "Error setting configured euid to %s (%d): %s", + user, (int)pw->pw_uid, strerror(errno)); + return -1; + } + + /* This is how OpenBSD rolls: + if (setgroups(1, &pw->pw_gid) || setegid(pw->pw_gid) || + setgid(pw->pw_gid) || setuid(pw->pw_uid) || seteuid(pw->pw_uid)) { + setgid(pw->pw_gid) || seteuid(pw->pw_uid) || setuid(pw->pw_uid)) { + log_warn(LD_GENERAL, "Error setting configured UID/GID: %s", + strerror(errno)); + return -1; + } + */ + + /* We've properly switched egid, gid, euid, uid, and supplementary groups if + * we're here. */ +#ifdef HAVE_LINUX_CAPABILITIES + if (keep_bindlow) { + if (drop_capabilities(0)) + return -1; + } +#endif /* defined(HAVE_LINUX_CAPABILITIES) */ + +#if !defined(CYGWIN) && !defined(__CYGWIN__) + /* If we tried to drop privilege to a group/user other than root, attempt to + * restore root (E)(U|G)ID, and abort if the operation succeeds */ + + /* Only check for privilege dropping if we were asked to be non-root */ + if (pw->pw_uid) { + /* Try changing GID/EGID */ + if (pw->pw_gid != old_gid && + (setgid(old_gid) != -1 || setegid(old_gid) != -1)) { + log_warn(LD_GENERAL, "Was able to restore group credentials even after " + "switching GID: this means that the setgid code didn't work."); + return -1; + } + + /* Try changing UID/EUID */ + if (pw->pw_uid != old_uid && + (setuid(old_uid) != -1 || seteuid(old_uid) != -1)) { + log_warn(LD_GENERAL, "Was able to restore user credentials even after " + "switching UID: this means that the setuid code didn't work."); + return -1; + } + } +#endif /* !defined(CYGWIN) && !defined(__CYGWIN__) */ + + /* Check what really happened */ + if (log_credential_status()) { + return -1; + } + + have_already_switched_id = 1; /* mark success so we never try again */ + +#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && \ + defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) + if (pw->pw_uid) { + /* Re-enable core dumps if we're not running as root. */ + log_info(LD_CONFIG, "Re-enabling coredumps"); + if (prctl(PR_SET_DUMPABLE, 1)) { + log_warn(LD_CONFIG, "Unable to re-enable coredumps: %s",strerror(errno)); + } + } +#endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && ... */ + return 0; + +#else /* !(!defined(_WIN32)) */ + (void)user; + (void)flags; + + log_warn(LD_CONFIG, "Switching users is unsupported on your OS."); + return -1; +#endif /* !defined(_WIN32) */ +} diff --git a/src/lib/process/setuid.h b/src/lib/process/setuid.h new file mode 100644 index 0000000000..7d03e1f025 --- /dev/null +++ b/src/lib/process/setuid.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file setuid.h + * \brief Header for setuid.c + **/ + +#ifndef TOR_SETUID_H +#define TOR_SETUID_H + +int have_capability_support(void); + +/** Flag for switch_id; see switch_id() for documentation */ +#define SWITCH_ID_KEEP_BINDLOW (1<<0) +/** Flag for switch_id; see switch_id() for documentation */ +#define SWITCH_ID_WARN_IF_NO_CAPS (1<<1) +int switch_id(const char *user, unsigned flags); + +#endif diff --git a/src/lib/process/subprocess.c b/src/lib/process/subprocess.c new file mode 100644 index 0000000000..f4429d7f76 --- /dev/null +++ b/src/lib/process/subprocess.c @@ -0,0 +1,1236 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file subprocess.c + * \brief Launch and monitor other processes. + **/ + +#define SUBPROCESS_PRIVATE +#include "lib/process/subprocess.h" + +#include "lib/container/smartlist.h" +#include "lib/err/torerr.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/log/win32err.h" +#include "lib/malloc/malloc.h" +#include "lib/process/env.h" +#include "lib/process/waitpid.h" +#include "lib/string/compat_ctype.h" + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SIGNAL_H +#include <signal.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#include <errno.h> +#include <string.h> + +/** Format a single argument for being put on a Windows command line. + * Returns a newly allocated string */ +static char * +format_win_cmdline_argument(const char *arg) +{ + char *formatted_arg; + char need_quotes; + const char *c; + int i; + int bs_counter = 0; + /* Backslash we can point to when one is inserted into the string */ + const char backslash = '\\'; + + /* Smartlist of *char */ + smartlist_t *arg_chars; + arg_chars = smartlist_new(); + + /* Quote string if it contains whitespace or is empty */ + need_quotes = (strchr(arg, ' ') || strchr(arg, '\t') || '\0' == arg[0]); + + /* Build up smartlist of *chars */ + for (c=arg; *c != '\0'; c++) { + if ('"' == *c) { + /* Double up backslashes preceding a quote */ + for (i=0; i<(bs_counter*2); i++) + smartlist_add(arg_chars, (void*)&backslash); + bs_counter = 0; + /* Escape the quote */ + smartlist_add(arg_chars, (void*)&backslash); + smartlist_add(arg_chars, (void*)c); + } else if ('\\' == *c) { + /* Count backslashes until we know whether to double up */ + bs_counter++; + } else { + /* Don't double up slashes preceding a non-quote */ + for (i=0; i<bs_counter; i++) + smartlist_add(arg_chars, (void*)&backslash); + bs_counter = 0; + smartlist_add(arg_chars, (void*)c); + } + } + /* Don't double up trailing backslashes */ + for (i=0; i<bs_counter; i++) + smartlist_add(arg_chars, (void*)&backslash); + + /* Allocate space for argument, quotes (if needed), and terminator */ + const size_t formatted_arg_len = smartlist_len(arg_chars) + + (need_quotes ? 2 : 0) + 1; + formatted_arg = tor_malloc_zero(formatted_arg_len); + + /* Add leading quote */ + i=0; + if (need_quotes) + formatted_arg[i++] = '"'; + + /* Add characters */ + SMARTLIST_FOREACH(arg_chars, char*, ch, + { + formatted_arg[i++] = *ch; + }); + + /* Add trailing quote */ + if (need_quotes) + formatted_arg[i++] = '"'; + formatted_arg[i] = '\0'; + + smartlist_free(arg_chars); + return formatted_arg; +} + +/** Format a command line for use on Windows, which takes the command as a + * string rather than string array. Follows the rules from "Parsing C++ + * Command-Line Arguments" in MSDN. Algorithm based on list2cmdline in the + * Python subprocess module. Returns a newly allocated string */ +char * +tor_join_win_cmdline(const char *argv[]) +{ + smartlist_t *argv_list; + char *joined_argv; + int i; + + /* Format each argument and put the result in a smartlist */ + argv_list = smartlist_new(); + for (i=0; argv[i] != NULL; i++) { + smartlist_add(argv_list, (void *)format_win_cmdline_argument(argv[i])); + } + + /* Join the arguments with whitespace */ + joined_argv = smartlist_join_strings(argv_list, " ", 0, NULL); + + /* Free the newly allocated arguments, and the smartlist */ + SMARTLIST_FOREACH(argv_list, char *, arg, + { + tor_free(arg); + }); + smartlist_free(argv_list); + + return joined_argv; +} + +#ifndef _WIN32 +/** Format <b>child_state</b> and <b>saved_errno</b> as a hex string placed in + * <b>hex_errno</b>. Called between fork and _exit, so must be signal-handler + * safe. + * + * <b>hex_errno</b> must have at least HEX_ERRNO_SIZE+1 bytes available. + * + * The format of <b>hex_errno</b> is: "CHILD_STATE/ERRNO\n", left-padded + * with spaces. CHILD_STATE indicates where + * in the process of starting the child process did the failure occur (see + * CHILD_STATE_* macros for definition), and SAVED_ERRNO is the value of + * errno when the failure occurred. + * + * On success return the number of characters added to hex_errno, not counting + * the terminating NUL; return -1 on error. + */ +STATIC int +format_helper_exit_status(unsigned char child_state, int saved_errno, + char *hex_errno) +{ + unsigned int unsigned_errno; + int written, left; + char *cur; + size_t i; + int res = -1; + + /* Fill hex_errno with spaces, and a trailing newline (memset may + not be signal handler safe, so we can't use it) */ + for (i = 0; i < (HEX_ERRNO_SIZE - 1); i++) + hex_errno[i] = ' '; + hex_errno[HEX_ERRNO_SIZE - 1] = '\n'; + + /* Convert errno to be unsigned for hex conversion */ + if (saved_errno < 0) { + // Avoid overflow on the cast to unsigned int when result is INT_MIN + // by adding 1 to the signed int negative value, + // then, after it has been negated and cast to unsigned, + // adding the original 1 back (the double-addition is intentional). + // Otherwise, the cast to signed could cause a temporary int + // to equal INT_MAX + 1, which is undefined. + unsigned_errno = ((unsigned int) -(saved_errno + 1)) + 1; + } else { + unsigned_errno = (unsigned int) saved_errno; + } + + /* + * Count how many chars of space we have left, and keep a pointer into the + * current point in the buffer. + */ + left = HEX_ERRNO_SIZE+1; + cur = hex_errno; + + /* Emit child_state */ + written = format_hex_number_sigsafe(child_state, cur, left); + + if (written <= 0) + goto err; + + /* Adjust left and cur */ + left -= written; + cur += written; + if (left <= 0) + goto err; + + /* Now the '/' */ + *cur = '/'; + + /* Adjust left and cur */ + ++cur; + --left; + if (left <= 0) + goto err; + + /* Need minus? */ + if (saved_errno < 0) { + *cur = '-'; + ++cur; + --left; + if (left <= 0) + goto err; + } + + /* Emit unsigned_errno */ + written = format_hex_number_sigsafe(unsigned_errno, cur, left); + + if (written <= 0) + goto err; + + /* Adjust left and cur */ + left -= written; + cur += written; + + /* Check that we have enough space left for a newline and a NUL */ + if (left <= 1) + goto err; + + /* Emit the newline and NUL */ + *cur++ = '\n'; + *cur++ = '\0'; + + res = (int)(cur - hex_errno - 1); + + goto done; + + err: + /* + * In error exit, just write a '\0' in the first char so whatever called + * this at least won't fall off the end. + */ + *hex_errno = '\0'; + + done: + return res; +} +#endif /* !defined(_WIN32) */ + +/* Maximum number of file descriptors, if we cannot get it via sysconf() */ +#define DEFAULT_MAX_FD 256 + +/** Terminate the process of <b>process_handle</b>, if that process has not + * already exited. + * + * Return 0 if we succeeded in terminating the process (or if the process + * already exited), and -1 if we tried to kill the process but failed. + * + * Based on code originally borrowed from Python's os.kill. */ +int +tor_terminate_process(process_handle_t *process_handle) +{ +#ifdef _WIN32 + if (tor_get_exit_code(process_handle, 0, NULL) == PROCESS_EXIT_RUNNING) { + HANDLE handle = process_handle->pid.hProcess; + + if (!TerminateProcess(handle, 0)) + return -1; + else + return 0; + } +#else /* !(defined(_WIN32)) */ + if (process_handle->waitpid_cb) { + /* We haven't got a waitpid yet, so we can just kill off the process. */ + return kill(process_handle->pid, SIGTERM); + } +#endif /* defined(_WIN32) */ + + return 0; /* We didn't need to kill the process, so report success */ +} + +/** Return the Process ID of <b>process_handle</b>. */ +int +tor_process_get_pid(process_handle_t *process_handle) +{ +#ifdef _WIN32 + return (int) process_handle->pid.dwProcessId; +#else + return (int) process_handle->pid; +#endif +} + +#ifdef _WIN32 +HANDLE +tor_process_get_stdout_pipe(process_handle_t *process_handle) +{ + return process_handle->stdout_pipe; +} +#else /* !(defined(_WIN32)) */ +/* DOCDOC tor_process_get_stdout_pipe */ +int +tor_process_get_stdout_pipe(process_handle_t *process_handle) +{ + return process_handle->stdout_pipe; +} +#endif /* defined(_WIN32) */ + +/* DOCDOC process_handle_new */ +static process_handle_t * +process_handle_new(void) +{ + process_handle_t *out = tor_malloc_zero(sizeof(process_handle_t)); + +#ifdef _WIN32 + out->stdin_pipe = INVALID_HANDLE_VALUE; + out->stdout_pipe = INVALID_HANDLE_VALUE; + out->stderr_pipe = INVALID_HANDLE_VALUE; +#else + out->stdin_pipe = -1; + out->stdout_pipe = -1; + out->stderr_pipe = -1; +#endif /* defined(_WIN32) */ + + return out; +} + +#ifndef _WIN32 +/** Invoked when a process that we've launched via tor_spawn_background() has + * been found to have terminated. + */ +static void +process_handle_waitpid_cb(int status, void *arg) +{ + process_handle_t *process_handle = arg; + + process_handle->waitpid_exit_status = status; + clear_waitpid_callback(process_handle->waitpid_cb); + if (process_handle->status == PROCESS_STATUS_RUNNING) + process_handle->status = PROCESS_STATUS_NOTRUNNING; + process_handle->waitpid_cb = 0; +} +#endif /* !defined(_WIN32) */ + +/** + * @name child-process states + * + * Each of these values represents a possible state that a child process can + * be in. They're used to determine what to say when telling the parent how + * far along we were before failure. + * + * @{ + */ +#define CHILD_STATE_INIT 0 +#define CHILD_STATE_PIPE 1 +#define CHILD_STATE_MAXFD 2 +#define CHILD_STATE_FORK 3 +#define CHILD_STATE_DUPOUT 4 +#define CHILD_STATE_DUPERR 5 +#define CHILD_STATE_DUPIN 6 +#define CHILD_STATE_CLOSEFD 7 +#define CHILD_STATE_EXEC 8 +#define CHILD_STATE_FAILEXEC 9 +/** @} */ +/** + * Boolean. If true, then Tor may call execve or CreateProcess via + * tor_spawn_background. + **/ +static int may_spawn_background_process = 1; +/** + * Turn off may_spawn_background_process, so that all future calls to + * tor_spawn_background are guaranteed to fail. + **/ +void +tor_disable_spawning_background_processes(void) +{ + may_spawn_background_process = 0; +} +/** Start a program in the background. If <b>filename</b> contains a '/', then + * it will be treated as an absolute or relative path. Otherwise, on + * non-Windows systems, the system path will be searched for <b>filename</b>. + * On Windows, only the current directory will be searched. Here, to search the + * system path (as well as the application directory, current working + * directory, and system directories), set filename to NULL. + * + * The strings in <b>argv</b> will be passed as the command line arguments of + * the child program (following convention, argv[0] should normally be the + * filename of the executable, and this must be the case if <b>filename</b> is + * NULL). The last element of argv must be NULL. A handle to the child process + * will be returned in process_handle (which must be non-NULL). Read + * process_handle.status to find out if the process was successfully launched. + * For convenience, process_handle.status is returned by this function. + * + * Some parts of this code are based on the POSIX subprocess module from + * Python, and example code from + * http://msdn.microsoft.com/en-us/library/ms682499%28v=vs.85%29.aspx. + */ +int +tor_spawn_background(const char *const filename, const char **argv, + process_environment_t *env, + process_handle_t **process_handle_out) +{ + if (BUG(may_spawn_background_process == 0)) { + /* We should never reach this point if we're forbidden to spawn + * processes. Instead we should have caught the attempt earlier. */ + return PROCESS_STATUS_ERROR; + } + +#ifdef _WIN32 + HANDLE stdout_pipe_read = NULL; + HANDLE stdout_pipe_write = NULL; + HANDLE stderr_pipe_read = NULL; + HANDLE stderr_pipe_write = NULL; + HANDLE stdin_pipe_read = NULL; + HANDLE stdin_pipe_write = NULL; + process_handle_t *process_handle; + int status; + + STARTUPINFOA siStartInfo; + BOOL retval = FALSE; + + SECURITY_ATTRIBUTES saAttr; + char *joined_argv; + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + /* TODO: should we set explicit security attributes? (#2046, comment 5) */ + saAttr.lpSecurityDescriptor = NULL; + + /* Assume failure to start process */ + status = PROCESS_STATUS_ERROR; + + /* Set up pipe for stdout */ + if (!CreatePipe(&stdout_pipe_read, &stdout_pipe_write, &saAttr, 0)) { + log_warn(LD_GENERAL, + "Failed to create pipe for stdout communication with child process: %s", + format_win32_error(GetLastError())); + return status; + } + if (!SetHandleInformation(stdout_pipe_read, HANDLE_FLAG_INHERIT, 0)) { + log_warn(LD_GENERAL, + "Failed to configure pipe for stdout communication with child " + "process: %s", format_win32_error(GetLastError())); + return status; + } + + /* Set up pipe for stderr */ + if (!CreatePipe(&stderr_pipe_read, &stderr_pipe_write, &saAttr, 0)) { + log_warn(LD_GENERAL, + "Failed to create pipe for stderr communication with child process: %s", + format_win32_error(GetLastError())); + return status; + } + if (!SetHandleInformation(stderr_pipe_read, HANDLE_FLAG_INHERIT, 0)) { + log_warn(LD_GENERAL, + "Failed to configure pipe for stderr communication with child " + "process: %s", format_win32_error(GetLastError())); + return status; + } + + /* Set up pipe for stdin */ + if (!CreatePipe(&stdin_pipe_read, &stdin_pipe_write, &saAttr, 0)) { + log_warn(LD_GENERAL, + "Failed to create pipe for stdin communication with child process: %s", + format_win32_error(GetLastError())); + return status; + } + if (!SetHandleInformation(stdin_pipe_write, HANDLE_FLAG_INHERIT, 0)) { + log_warn(LD_GENERAL, + "Failed to configure pipe for stdin communication with child " + "process: %s", format_win32_error(GetLastError())); + return status; + } + + /* Create the child process */ + + /* Windows expects argv to be a whitespace delimited string, so join argv up + */ + joined_argv = tor_join_win_cmdline(argv); + + process_handle = process_handle_new(); + process_handle->status = status; + + ZeroMemory(&(process_handle->pid), sizeof(PROCESS_INFORMATION)); + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdError = stderr_pipe_write; + siStartInfo.hStdOutput = stdout_pipe_write; + siStartInfo.hStdInput = stdin_pipe_read; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + /* Create the child process */ + + retval = CreateProcessA(filename, // module name + joined_argv, // command line + /* TODO: should we set explicit security attributes? (#2046, comment 5) */ + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + /*(TODO: set CREATE_NEW CONSOLE/PROCESS_GROUP to make GetExitCodeProcess() + * work?) */ + CREATE_NO_WINDOW, // creation flags + (env==NULL) ? NULL : env->windows_environment_block, + NULL, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &(process_handle->pid)); // receives PROCESS_INFORMATION + + tor_free(joined_argv); + + if (!retval) { + log_warn(LD_GENERAL, + "Failed to create child process %s: %s", filename?filename:argv[0], + format_win32_error(GetLastError())); + tor_free(process_handle); + } else { + /* TODO: Close hProcess and hThread in process_handle->pid? */ + process_handle->stdout_pipe = stdout_pipe_read; + process_handle->stderr_pipe = stderr_pipe_read; + process_handle->stdin_pipe = stdin_pipe_write; + status = process_handle->status = PROCESS_STATUS_RUNNING; + } + + /* TODO: Close pipes on exit */ + *process_handle_out = process_handle; + return status; +#else /* !(defined(_WIN32)) */ + pid_t pid; + int stdout_pipe[2]; + int stderr_pipe[2]; + int stdin_pipe[2]; + int fd, retval; + process_handle_t *process_handle; + int status; + + const char *error_message = SPAWN_ERROR_MESSAGE; + size_t error_message_length; + + /* Represents where in the process of spawning the program is; + this is used for printing out the error message */ + unsigned char child_state = CHILD_STATE_INIT; + + char hex_errno[HEX_ERRNO_SIZE + 2]; /* + 1 should be sufficient actually */ + + static int max_fd = -1; + + status = PROCESS_STATUS_ERROR; + + /* We do the strlen here because strlen() is not signal handler safe, + and we are not allowed to use unsafe functions between fork and exec */ + error_message_length = strlen(error_message); + + // child_state = CHILD_STATE_PIPE; + + /* Set up pipe for redirecting stdout, stderr, and stdin of child */ + retval = pipe(stdout_pipe); + if (-1 == retval) { + log_warn(LD_GENERAL, + "Failed to set up pipe for stdout communication with child process: %s", + strerror(errno)); + return status; + } + + retval = pipe(stderr_pipe); + if (-1 == retval) { + log_warn(LD_GENERAL, + "Failed to set up pipe for stderr communication with child process: %s", + strerror(errno)); + + close(stdout_pipe[0]); + close(stdout_pipe[1]); + + return status; + } + + retval = pipe(stdin_pipe); + if (-1 == retval) { + log_warn(LD_GENERAL, + "Failed to set up pipe for stdin communication with child process: %s", + strerror(errno)); + + close(stdout_pipe[0]); + close(stdout_pipe[1]); + close(stderr_pipe[0]); + close(stderr_pipe[1]); + + return status; + } + + // child_state = CHILD_STATE_MAXFD; + +#ifdef _SC_OPEN_MAX + if (-1 == max_fd) { + max_fd = (int) sysconf(_SC_OPEN_MAX); + if (max_fd == -1) { + max_fd = DEFAULT_MAX_FD; + log_warn(LD_GENERAL, + "Cannot find maximum file descriptor, assuming %d", max_fd); + } + } +#else /* !(defined(_SC_OPEN_MAX)) */ + max_fd = DEFAULT_MAX_FD; +#endif /* defined(_SC_OPEN_MAX) */ + + // child_state = CHILD_STATE_FORK; + + pid = fork(); + if (0 == pid) { + /* In child */ + +#if defined(HAVE_SYS_PRCTL_H) && defined(__linux__) + /* Attempt to have the kernel issue a SIGTERM if the parent + * goes away. Certain attributes of the binary being execve()ed + * will clear this during the execve() call, but it's better + * than nothing. + */ + prctl(PR_SET_PDEATHSIG, SIGTERM); +#endif /* defined(HAVE_SYS_PRCTL_H) && defined(__linux__) */ + + child_state = CHILD_STATE_DUPOUT; + + /* Link child stdout to the write end of the pipe */ + retval = dup2(stdout_pipe[1], STDOUT_FILENO); + if (-1 == retval) + goto error; + + child_state = CHILD_STATE_DUPERR; + + /* Link child stderr to the write end of the pipe */ + retval = dup2(stderr_pipe[1], STDERR_FILENO); + if (-1 == retval) + goto error; + + child_state = CHILD_STATE_DUPIN; + + /* Link child stdin to the read end of the pipe */ + retval = dup2(stdin_pipe[0], STDIN_FILENO); + if (-1 == retval) + goto error; + + // child_state = CHILD_STATE_CLOSEFD; + + close(stderr_pipe[0]); + close(stderr_pipe[1]); + close(stdout_pipe[0]); + close(stdout_pipe[1]); + close(stdin_pipe[0]); + close(stdin_pipe[1]); + + /* Close all other fds, including the read end of the pipe */ + /* XXX: We should now be doing enough FD_CLOEXEC setting to make + * this needless. */ + for (fd = STDERR_FILENO + 1; fd < max_fd; fd++) { + close(fd); + } + + // child_state = CHILD_STATE_EXEC; + + /* Call the requested program. We need the cast because + execvp doesn't define argv as const, even though it + does not modify the arguments */ + if (env) + execve(filename, (char *const *) argv, env->unixoid_environment_block); + else { + static char *new_env[] = { NULL }; + execve(filename, (char *const *) argv, new_env); + } + + /* If we got here, the exec or open(/dev/null) failed */ + + child_state = CHILD_STATE_FAILEXEC; + + error: + { + /* XXX: are we leaking fds from the pipe? */ + int n, err=0; + ssize_t nbytes; + + n = format_helper_exit_status(child_state, errno, hex_errno); + + if (n >= 0) { + /* Write the error message. GCC requires that we check the return + value, but there is nothing we can do if it fails */ + /* TODO: Don't use STDOUT, use a pipe set up just for this purpose */ + nbytes = write(STDOUT_FILENO, error_message, error_message_length); + err = (nbytes < 0); + nbytes = write(STDOUT_FILENO, hex_errno, n); + err += (nbytes < 0); + } + + _exit(err?254:255); // exit ok: in child. + } + + /* Never reached, but avoids compiler warning */ + return status; // LCOV_EXCL_LINE + } + + /* In parent */ + + if (-1 == pid) { + log_warn(LD_GENERAL, "Failed to fork child process: %s", strerror(errno)); + close(stdin_pipe[0]); + close(stdin_pipe[1]); + close(stdout_pipe[0]); + close(stdout_pipe[1]); + close(stderr_pipe[0]); + close(stderr_pipe[1]); + return status; + } + + process_handle = process_handle_new(); + process_handle->status = status; + process_handle->pid = pid; + + /* TODO: If the child process forked but failed to exec, waitpid it */ + + /* Return read end of the pipes to caller, and close write end */ + process_handle->stdout_pipe = stdout_pipe[0]; + retval = close(stdout_pipe[1]); + + if (-1 == retval) { + log_warn(LD_GENERAL, + "Failed to close write end of stdout pipe in parent process: %s", + strerror(errno)); + } + + process_handle->waitpid_cb = set_waitpid_callback(pid, + process_handle_waitpid_cb, + process_handle); + + process_handle->stderr_pipe = stderr_pipe[0]; + retval = close(stderr_pipe[1]); + + if (-1 == retval) { + log_warn(LD_GENERAL, + "Failed to close write end of stderr pipe in parent process: %s", + strerror(errno)); + } + + /* Return write end of the stdin pipe to caller, and close the read end */ + process_handle->stdin_pipe = stdin_pipe[1]; + retval = close(stdin_pipe[0]); + + if (-1 == retval) { + log_warn(LD_GENERAL, + "Failed to close read end of stdin pipe in parent process: %s", + strerror(errno)); + } + + status = process_handle->status = PROCESS_STATUS_RUNNING; + /* Set stdin/stdout/stderr pipes to be non-blocking */ + if (fcntl(process_handle->stdout_pipe, F_SETFL, O_NONBLOCK) < 0 || + fcntl(process_handle->stderr_pipe, F_SETFL, O_NONBLOCK) < 0 || + fcntl(process_handle->stdin_pipe, F_SETFL, O_NONBLOCK) < 0) { + log_warn(LD_GENERAL, "Failed to set stderror/stdout/stdin pipes " + "nonblocking in parent process: %s", strerror(errno)); + } + + *process_handle_out = process_handle; + return status; +#endif /* defined(_WIN32) */ +} + +/** Destroy all resources allocated by the process handle in + * <b>process_handle</b>. + * If <b>also_terminate_process</b> is true, also terminate the + * process of the process handle. */ +MOCK_IMPL(void, +tor_process_handle_destroy,(process_handle_t *process_handle, + int also_terminate_process)) +{ + if (!process_handle) + return; + + if (also_terminate_process) { + if (tor_terminate_process(process_handle) < 0) { + const char *errstr = +#ifdef _WIN32 + format_win32_error(GetLastError()); +#else + strerror(errno); +#endif + log_notice(LD_GENERAL, "Failed to terminate process with " + "PID '%d' ('%s').", tor_process_get_pid(process_handle), + errstr); + } else { + log_info(LD_GENERAL, "Terminated process with PID '%d'.", + tor_process_get_pid(process_handle)); + } + } + + process_handle->status = PROCESS_STATUS_NOTRUNNING; + +#ifdef _WIN32 + if (process_handle->stdout_pipe) + CloseHandle(process_handle->stdout_pipe); + + if (process_handle->stderr_pipe) + CloseHandle(process_handle->stderr_pipe); + + if (process_handle->stdin_pipe) + CloseHandle(process_handle->stdin_pipe); +#else /* !(defined(_WIN32)) */ + close(process_handle->stdout_pipe); + close(process_handle->stderr_pipe); + close(process_handle->stdin_pipe); + + clear_waitpid_callback(process_handle->waitpid_cb); +#endif /* defined(_WIN32) */ + + memset(process_handle, 0x0f, sizeof(process_handle_t)); + tor_free(process_handle); +} + +/** Get the exit code of a process specified by <b>process_handle</b> and store + * it in <b>exit_code</b>, if set to a non-NULL value. If <b>block</b> is set + * to true, the call will block until the process has exited. Otherwise if + * the process is still running, the function will return + * PROCESS_EXIT_RUNNING, and exit_code will be left unchanged. Returns + * PROCESS_EXIT_EXITED if the process did exit. If there is a failure, + * PROCESS_EXIT_ERROR will be returned and the contents of exit_code (if + * non-NULL) will be undefined. N.B. Under *nix operating systems, this will + * probably not work in Tor, because waitpid() is called in main.c to reap any + * terminated child processes.*/ +int +tor_get_exit_code(process_handle_t *process_handle, + int block, int *exit_code) +{ +#ifdef _WIN32 + DWORD retval; + BOOL success; + + if (block) { + /* Wait for the process to exit */ + retval = WaitForSingleObject(process_handle->pid.hProcess, INFINITE); + if (retval != WAIT_OBJECT_0) { + log_warn(LD_GENERAL, "WaitForSingleObject() failed (%d): %s", + (int)retval, format_win32_error(GetLastError())); + return PROCESS_EXIT_ERROR; + } + } else { + retval = WaitForSingleObject(process_handle->pid.hProcess, 0); + if (WAIT_TIMEOUT == retval) { + /* Process has not exited */ + return PROCESS_EXIT_RUNNING; + } else if (retval != WAIT_OBJECT_0) { + log_warn(LD_GENERAL, "WaitForSingleObject() failed (%d): %s", + (int)retval, format_win32_error(GetLastError())); + return PROCESS_EXIT_ERROR; + } + } + + if (exit_code != NULL) { + success = GetExitCodeProcess(process_handle->pid.hProcess, + (PDWORD)exit_code); + if (!success) { + log_warn(LD_GENERAL, "GetExitCodeProcess() failed: %s", + format_win32_error(GetLastError())); + return PROCESS_EXIT_ERROR; + } + } +#else /* !(defined(_WIN32)) */ + int stat_loc; + int retval; + + if (process_handle->waitpid_cb) { + /* We haven't processed a SIGCHLD yet. */ + retval = waitpid(process_handle->pid, &stat_loc, block?0:WNOHANG); + if (retval == process_handle->pid) { + clear_waitpid_callback(process_handle->waitpid_cb); + process_handle->waitpid_cb = NULL; + process_handle->waitpid_exit_status = stat_loc; + } + } else { + /* We already got a SIGCHLD for this process, and handled it. */ + retval = process_handle->pid; + stat_loc = process_handle->waitpid_exit_status; + } + + if (!block && 0 == retval) { + /* Process has not exited */ + return PROCESS_EXIT_RUNNING; + } else if (retval != process_handle->pid) { + log_warn(LD_GENERAL, "waitpid() failed for PID %d: %s", + (int)process_handle->pid, strerror(errno)); + return PROCESS_EXIT_ERROR; + } + + if (!WIFEXITED(stat_loc)) { + log_warn(LD_GENERAL, "Process %d did not exit normally", + (int)process_handle->pid); + return PROCESS_EXIT_ERROR; + } + + if (exit_code != NULL) + *exit_code = WEXITSTATUS(stat_loc); +#endif /* defined(_WIN32) */ + + return PROCESS_EXIT_EXITED; +} + +#ifdef _WIN32 +/** Read from a handle <b>h</b> into <b>buf</b>, up to <b>count</b> bytes. If + * <b>hProcess</b> is NULL, the function will return immediately if there is + * nothing more to read. Otherwise <b>hProcess</b> should be set to the handle + * to the process owning the <b>h</b>. In this case, the function will exit + * only once the process has exited, or <b>count</b> bytes are read. Returns + * the number of bytes read, or -1 on error. */ +ssize_t +tor_read_all_handle(HANDLE h, char *buf, size_t count, + const process_handle_t *process) +{ + size_t numread = 0; + BOOL retval; + DWORD byte_count; + BOOL process_exited = FALSE; + + if (count > SIZE_T_CEILING || count > SSIZE_MAX) + return -1; + + while (numread < count) { + /* Check if there is anything to read */ + retval = PeekNamedPipe(h, NULL, 0, NULL, &byte_count, NULL); + if (!retval) { + log_warn(LD_GENERAL, + "Failed to peek from handle: %s", + format_win32_error(GetLastError())); + return -1; + } else if (0 == byte_count) { + /* Nothing available: process exited or it is busy */ + + /* Exit if we don't know whether the process is running */ + if (NULL == process) + break; + + /* The process exited and there's nothing left to read from it */ + if (process_exited) + break; + + /* If process is not running, check for output one more time in case + it wrote something after the peek was performed. Otherwise keep on + waiting for output */ + tor_assert(process != NULL); + byte_count = WaitForSingleObject(process->pid.hProcess, 0); + if (WAIT_TIMEOUT != byte_count) + process_exited = TRUE; + + continue; + } + + /* There is data to read; read it */ + retval = ReadFile(h, buf+numread, count-numread, &byte_count, NULL); + tor_assert(byte_count + numread <= count); + if (!retval) { + log_warn(LD_GENERAL, "Failed to read from handle: %s", + format_win32_error(GetLastError())); + return -1; + } else if (0 == byte_count) { + /* End of file */ + break; + } + numread += byte_count; + } + return (ssize_t)numread; +} +#else /* !(defined(_WIN32)) */ +/** Read from a handle <b>fd</b> into <b>buf</b>, up to <b>count</b> bytes. If + * <b>process</b> is NULL, the function will return immediately if there is + * nothing more to read. Otherwise data will be read until end of file, or + * <b>count</b> bytes are read. Returns the number of bytes read, or -1 on + * error. Sets <b>eof</b> to true if <b>eof</b> is not NULL and the end of the + * file has been reached. */ +ssize_t +tor_read_all_handle(int fd, char *buf, size_t count, + const process_handle_t *process, + int *eof) +{ + size_t numread = 0; + ssize_t result; + + if (eof) + *eof = 0; + + if (count > SIZE_T_CEILING || count > SSIZE_MAX) + return -1; + + while (numread < count) { + result = read(fd, buf+numread, count-numread); + + if (result == 0) { + log_debug(LD_GENERAL, "read() reached end of file"); + if (eof) + *eof = 1; + break; + } else if (result < 0 && errno == EAGAIN) { + if (process) + continue; + else + break; + } else if (result < 0) { + log_warn(LD_GENERAL, "read() failed: %s", strerror(errno)); + return -1; + } + + numread += result; + } + + log_debug(LD_GENERAL, "read() read %d bytes from handle", (int)numread); + return (ssize_t)numread; +} +#endif /* defined(_WIN32) */ + +/** Read from stdout of a process until the process exits. */ +ssize_t +tor_read_all_from_process_stdout(const process_handle_t *process_handle, + char *buf, size_t count) +{ +#ifdef _WIN32 + return tor_read_all_handle(process_handle->stdout_pipe, buf, count, + process_handle); +#else + return tor_read_all_handle(process_handle->stdout_pipe, buf, count, + process_handle, NULL); +#endif /* defined(_WIN32) */ +} + +/** Read from stdout of a process until the process exits. */ +ssize_t +tor_read_all_from_process_stderr(const process_handle_t *process_handle, + char *buf, size_t count) +{ +#ifdef _WIN32 + return tor_read_all_handle(process_handle->stderr_pipe, buf, count, + process_handle); +#else + return tor_read_all_handle(process_handle->stderr_pipe, buf, count, + process_handle, NULL); +#endif /* defined(_WIN32) */ +} + +/** Return a string corresponding to <b>stream_status</b>. */ +const char * +stream_status_to_string(enum stream_status stream_status) +{ + switch (stream_status) { + case IO_STREAM_OKAY: + return "okay"; + case IO_STREAM_EAGAIN: + return "temporarily unavailable"; + case IO_STREAM_TERM: + return "terminated"; + case IO_STREAM_CLOSED: + return "closed"; + default: + tor_fragile_assert(); + return "unknown"; + } +} + +/** Split buf into lines, and add to smartlist. The buffer <b>buf</b> will be + * modified. The resulting smartlist will consist of pointers to buf, so there + * is no need to free the contents of sl. <b>buf</b> must be a NUL-terminated + * string. <b>len</b> should be set to the length of the buffer excluding the + * NUL. Non-printable characters (including NUL) will be replaced with "." */ +int +tor_split_lines(smartlist_t *sl, char *buf, int len) +{ + /* Index in buf of the start of the current line */ + int start = 0; + /* Index in buf of the current character being processed */ + int cur = 0; + /* Are we currently in a line */ + char in_line = 0; + + /* Loop over string */ + while (cur < len) { + /* Loop until end of line or end of string */ + for (; cur < len; cur++) { + if (in_line) { + if ('\r' == buf[cur] || '\n' == buf[cur]) { + /* End of line */ + buf[cur] = '\0'; + /* Point cur to the next line */ + cur++; + /* Line starts at start and ends with a nul */ + break; + } else { + if (!TOR_ISPRINT(buf[cur])) + buf[cur] = '.'; + } + } else { + if ('\r' == buf[cur] || '\n' == buf[cur]) { + /* Skip leading vertical space */ + ; + } else { + in_line = 1; + start = cur; + if (!TOR_ISPRINT(buf[cur])) + buf[cur] = '.'; + } + } + } + /* We are at the end of the line or end of string. If in_line is true there + * is a line which starts at buf+start and ends at a NUL. cur points to + * the character after the NUL. */ + if (in_line) + smartlist_add(sl, (void *)(buf+start)); + in_line = 0; + } + return smartlist_len(sl); +} + +#ifdef _WIN32 + +/** Return a smartlist containing lines outputted from + * <b>handle</b>. Return NULL on error, and set + * <b>stream_status_out</b> appropriately. */ +MOCK_IMPL(smartlist_t *, +tor_get_lines_from_handle, (HANDLE *handle, + enum stream_status *stream_status_out)) +{ + int pos; + char stdout_buf[600] = {0}; + smartlist_t *lines = NULL; + + tor_assert(stream_status_out); + + *stream_status_out = IO_STREAM_TERM; + + pos = tor_read_all_handle(handle, stdout_buf, sizeof(stdout_buf) - 1, NULL); + if (pos < 0) { + *stream_status_out = IO_STREAM_TERM; + return NULL; + } + if (pos == 0) { + *stream_status_out = IO_STREAM_EAGAIN; + return NULL; + } + + /* End with a null even if there isn't a \r\n at the end */ + /* TODO: What if this is a partial line? */ + stdout_buf[pos] = '\0'; + + /* Split up the buffer */ + lines = smartlist_new(); + tor_split_lines(lines, stdout_buf, pos); + + /* Currently 'lines' is populated with strings residing on the + stack. Replace them with their exact copies on the heap: */ + SMARTLIST_FOREACH(lines, char *, line, + SMARTLIST_REPLACE_CURRENT(lines, line, tor_strdup(line))); + + *stream_status_out = IO_STREAM_OKAY; + + return lines; +} + +#else /* !(defined(_WIN32)) */ + +/** Return a smartlist containing lines outputted from + * <b>fd</b>. Return NULL on error, and set + * <b>stream_status_out</b> appropriately. */ +MOCK_IMPL(smartlist_t *, +tor_get_lines_from_handle, (int fd, enum stream_status *stream_status_out)) +{ + enum stream_status stream_status; + char stdout_buf[400]; + smartlist_t *lines = NULL; + + while (1) { + memset(stdout_buf, 0, sizeof(stdout_buf)); + + stream_status = get_string_from_pipe(fd, + stdout_buf, sizeof(stdout_buf) - 1); + if (stream_status != IO_STREAM_OKAY) + goto done; + + if (!lines) lines = smartlist_new(); + smartlist_split_string(lines, stdout_buf, "\n", 0, 0); + } + + done: + *stream_status_out = stream_status; + return lines; +} + +#endif /* defined(_WIN32) */ + +/** Reads from <b>fd</b> and stores input in <b>buf_out</b> making + * sure it's below <b>count</b> bytes. + * If the string has a trailing newline, we strip it off. + * + * This function is specifically created to handle input from managed + * proxies, according to the pluggable transports spec. Make sure it + * fits your needs before using it. + * + * Returns: + * IO_STREAM_CLOSED: If the stream is closed. + * IO_STREAM_EAGAIN: If there is nothing to read and we should check back + * later. + * IO_STREAM_TERM: If something is wrong with the stream. + * IO_STREAM_OKAY: If everything went okay and we got a string + * in <b>buf_out</b>. */ +enum stream_status +get_string_from_pipe(int fd, char *buf_out, size_t count) +{ + ssize_t ret; + + tor_assert(count <= INT_MAX); + + ret = read(fd, buf_out, count); + + if (ret == 0) + return IO_STREAM_CLOSED; + else if (ret < 0 && errno == EAGAIN) + return IO_STREAM_EAGAIN; + else if (ret < 0) + return IO_STREAM_TERM; + + if (buf_out[ret - 1] == '\n') { + /* Remove the trailing newline */ + buf_out[ret - 1] = '\0'; + } else + buf_out[ret] = '\0'; + + return IO_STREAM_OKAY; +} diff --git a/src/lib/process/subprocess.h b/src/lib/process/subprocess.h new file mode 100644 index 0000000000..aa3127d62d --- /dev/null +++ b/src/lib/process/subprocess.h @@ -0,0 +1,134 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file subprocess.h + * \brief Header for subprocess.c + **/ + +#ifndef TOR_SUBPROCESS_H +#define TOR_SUBPROCESS_H + +#include "lib/cc/torint.h" +#include "lib/testsupport/testsupport.h" +#include <stddef.h> +#ifdef _WIN32 +#include <windows.h> +#endif + +struct smartlist_t; + +void tor_disable_spawning_background_processes(void); + +typedef struct process_handle_t process_handle_t; +struct process_environment_t; +int tor_spawn_background(const char *const filename, const char **argv, + struct process_environment_t *env, + process_handle_t **process_handle_out); + +#define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code " + +/** Status of an I/O stream. */ +enum stream_status { + IO_STREAM_OKAY, + IO_STREAM_EAGAIN, + IO_STREAM_TERM, + IO_STREAM_CLOSED +}; + +const char *stream_status_to_string(enum stream_status stream_status); + +enum stream_status get_string_from_pipe(int fd, char *buf, size_t count); + +/* Values of process_handle_t.status. */ +#define PROCESS_STATUS_NOTRUNNING 0 +#define PROCESS_STATUS_RUNNING 1 +#define PROCESS_STATUS_ERROR -1 + +#ifdef SUBPROCESS_PRIVATE +struct waitpid_callback_t; + +/** Structure to represent the state of a process with which Tor is + * communicating. The contents of this structure are private to util.c */ +struct process_handle_t { + /** One of the PROCESS_STATUS_* values */ + int status; +#ifdef _WIN32 + HANDLE stdin_pipe; + HANDLE stdout_pipe; + HANDLE stderr_pipe; + PROCESS_INFORMATION pid; +#else /* !(defined(_WIN32)) */ + int stdin_pipe; + int stdout_pipe; + int stderr_pipe; + pid_t pid; + /** If the process has not given us a SIGCHLD yet, this has the + * waitpid_callback_t that gets invoked once it has. Otherwise this + * contains NULL. */ + struct waitpid_callback_t *waitpid_cb; + /** The exit status reported by waitpid. */ + int waitpid_exit_status; +#endif /* defined(_WIN32) */ +}; +#endif /* defined(SUBPROCESS_PRIVATE) */ + +/* Return values of tor_get_exit_code() */ +#define PROCESS_EXIT_RUNNING 1 +#define PROCESS_EXIT_EXITED 0 +#define PROCESS_EXIT_ERROR -1 +int tor_get_exit_code(process_handle_t *process_handle, + int block, int *exit_code); +int tor_split_lines(struct smartlist_t *sl, char *buf, int len); +#ifdef _WIN32 +ssize_t tor_read_all_handle(HANDLE h, char *buf, size_t count, + const process_handle_t *process); +#else +ssize_t tor_read_all_handle(int fd, char *buf, size_t count, + const process_handle_t *process, + int *eof); +#endif /* defined(_WIN32) */ +ssize_t tor_read_all_from_process_stdout( + const process_handle_t *process_handle, char *buf, size_t count); +ssize_t tor_read_all_from_process_stderr( + const process_handle_t *process_handle, char *buf, size_t count); +char *tor_join_win_cmdline(const char *argv[]); + +int tor_process_get_pid(process_handle_t *process_handle); +#ifdef _WIN32 +HANDLE tor_process_get_stdout_pipe(process_handle_t *process_handle); +#else +int tor_process_get_stdout_pipe(process_handle_t *process_handle); +#endif + +#ifdef _WIN32 +MOCK_DECL(struct smartlist_t *, tor_get_lines_from_handle,(HANDLE *handle, + enum stream_status *stream_status)); +#else +MOCK_DECL(struct smartlist_t *, tor_get_lines_from_handle,(int fd, + enum stream_status *stream_status)); +#endif /* defined(_WIN32) */ + +int tor_terminate_process(process_handle_t *process_handle); + +MOCK_DECL(void, tor_process_handle_destroy,(process_handle_t *process_handle, + int also_terminate_process)); + +#ifdef SUBPROCESS_PRIVATE +/* Prototypes for private functions only used by util.c (and unit tests) */ + +#ifndef _WIN32 +STATIC int format_helper_exit_status(unsigned char child_state, + int saved_errno, char *hex_errno); + +/* Space for hex values of child state, a slash, saved_errno (with + leading minus) and newline (no null) */ +#define HEX_ERRNO_SIZE (sizeof(char) * 2 + 1 + \ + 1 + sizeof(int) * 2 + 1) +#endif /* !defined(_WIN32) */ + +#endif /* defined(SUBPROCESS_PRIVATE) */ + +#endif diff --git a/src/lib/process/waitpid.c b/src/lib/process/waitpid.c new file mode 100644 index 0000000000..9b626394d2 --- /dev/null +++ b/src/lib/process/waitpid.c @@ -0,0 +1,154 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file waitpid.c + * \brief Convenience structures for handlers for handling waitpid(). + **/ + +#include "orconfig.h" + +#ifndef _WIN32 + +#include "lib/process/waitpid.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "ht.h" + +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif + +#include <string.h> + +/* ================================================== */ +/* Convenience structures for handlers for waitpid(). + * + * The tor_process_monitor*() code above doesn't use them, since it is for + * monitoring a non-child process. + */ + +/** Mapping from a PID to a userfn/userdata pair. */ +struct waitpid_callback_t { + HT_ENTRY(waitpid_callback_t) node; + pid_t pid; + + void (*userfn)(int, void *userdata); + void *userdata; + + unsigned running; +}; + +static inline unsigned int +process_map_entry_hash_(const waitpid_callback_t *ent) +{ + return (unsigned) ent->pid; +} + +static inline unsigned int +process_map_entries_eq_(const waitpid_callback_t *a, + const waitpid_callback_t *b) +{ + return a->pid == b->pid; +} + +static HT_HEAD(process_map, waitpid_callback_t) process_map = HT_INITIALIZER(); + +HT_PROTOTYPE(process_map, waitpid_callback_t, node, process_map_entry_hash_, + process_map_entries_eq_) +HT_GENERATE2(process_map, waitpid_callback_t, node, process_map_entry_hash_, + process_map_entries_eq_, 0.6, tor_reallocarray_, tor_free_) + +/** + * Begin monitoring the child pid <b>pid</b> to see if we get a SIGCHLD for + * it. If we eventually do, call <b>fn</b>, passing it the exit status (as + * yielded by waitpid) and the pointer <b>arg</b>. + * + * To cancel this, or clean up after it has triggered, call + * clear_waitpid_callback(). + */ +waitpid_callback_t * +set_waitpid_callback(pid_t pid, void (*fn)(int, void *), void *arg) +{ + waitpid_callback_t *old_ent; + waitpid_callback_t *ent = tor_malloc_zero(sizeof(waitpid_callback_t)); + ent->pid = pid; + ent->userfn = fn; + ent->userdata = arg; + ent->running = 1; + + old_ent = HT_REPLACE(process_map, &process_map, ent); + if (old_ent) { + log_warn(LD_BUG, "Replaced a waitpid monitor on pid %u. That should be " + "impossible.", (unsigned) pid); + old_ent->running = 0; + } + + return ent; +} + +/** + * Cancel a waitpid_callback_t, or clean up after one has triggered. Releases + * all storage held by <b>ent</b>. + */ +void +clear_waitpid_callback(waitpid_callback_t *ent) +{ + waitpid_callback_t *old_ent; + if (ent == NULL) + return; + + if (ent->running) { + old_ent = HT_REMOVE(process_map, &process_map, ent); + if (old_ent != ent) { + log_warn(LD_BUG, "Couldn't remove waitpid monitor for pid %u.", + (unsigned) ent->pid); + return; + } + } + + tor_free(ent); +} + +/** Helper: find the callack for <b>pid</b>; if there is one, run it, + * reporting the exit status as <b>status</b>. */ +static void +notify_waitpid_callback_by_pid(pid_t pid, int status) +{ + waitpid_callback_t search, *ent; + + search.pid = pid; + ent = HT_REMOVE(process_map, &process_map, &search); + if (!ent || !ent->running) { + log_info(LD_GENERAL, "Child process %u has exited; no callback was " + "registered", (unsigned)pid); + return; + } + + log_info(LD_GENERAL, "Child process %u has exited; running callback.", + (unsigned)pid); + + ent->running = 0; + ent->userfn(status, ent->userdata); +} + +/** Use waitpid() to wait for all children that have exited, and invoke any + * callbacks registered for them. */ +void +notify_pending_waitpid_callbacks(void) +{ + /* I was going to call this function reap_zombie_children(), but + * that makes it sound way more exciting than it really is. */ + pid_t child; + int status = 0; + + while ((child = waitpid(-1, &status, WNOHANG)) > 0) { + notify_waitpid_callback_by_pid(child, status); + status = 0; /* should be needless */ + } +} + +#endif /* !defined(_WIN32) */ diff --git a/src/lib/process/waitpid.h b/src/lib/process/waitpid.h new file mode 100644 index 0000000000..5faef468c1 --- /dev/null +++ b/src/lib/process/waitpid.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2011-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file waitpid.h + * \brief Headers for waitpid.c + **/ + +#ifndef TOR_WAITPID_H +#define TOR_WAITPID_H + +#ifndef _WIN32 +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +/** A callback structure waiting for us to get a SIGCHLD informing us that a + * PID has been closed. Created by set_waitpid_callback. Cancelled or cleaned- + * up from clear_waitpid_callback(). Do not access outside of the main thread; + * do not access from inside a signal handler. */ +typedef struct waitpid_callback_t waitpid_callback_t; + +waitpid_callback_t *set_waitpid_callback(pid_t pid, + void (*fn)(int, void *), void *arg); +void clear_waitpid_callback(waitpid_callback_t *ent); +void notify_pending_waitpid_callbacks(void); +#endif /* !defined(_WIN32) */ + +#endif /* !defined(TOR_WAITPID_H) */ diff --git a/src/lib/sandbox/.may_include b/src/lib/sandbox/.may_include new file mode 100644 index 0000000000..84906dfb3d --- /dev/null +++ b/src/lib/sandbox/.may_include @@ -0,0 +1,15 @@ +orconfig.h + +lib/cc/*.h +lib/container/*.h +lib/err/*.h +lib/log/*.h +lib/malloc/*.h +lib/net/*.h +lib/sandbox/*.h +lib/sandbox/*.inc +lib/string/*.h + +ht.h +siphash.h +tor_queue.h diff --git a/src/lib/sandbox/include.am b/src/lib/sandbox/include.am new file mode 100644 index 0000000000..adfda6bde5 --- /dev/null +++ b/src/lib/sandbox/include.am @@ -0,0 +1,18 @@ + +noinst_LIBRARIES += src/lib/libtor-sandbox.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-sandbox-testing.a +endif + +src_lib_libtor_sandbox_a_SOURCES = \ + src/lib/sandbox/sandbox.c + +src_lib_libtor_sandbox_testing_a_SOURCES = \ + $(src_lib_libtor_sandbox_a_SOURCES) +src_lib_libtor_sandbox_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_sandbox_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/sandbox/linux_syscalls.inc \ + src/lib/sandbox/sandbox.h diff --git a/src/lib/sandbox/linux_syscalls.inc b/src/lib/sandbox/linux_syscalls.inc new file mode 100644 index 0000000000..cf47c73809 --- /dev/null +++ b/src/lib/sandbox/linux_syscalls.inc @@ -0,0 +1,1153 @@ +/* Automatically generated with + gen_linux_syscalls.pl /usr/include/asm/unistd*.h + Do not edit. + */ +static const struct { + int syscall_num; const char *syscall_name; +} SYSCALLS_BY_NUMBER[] = { +#ifdef __NR__llseek + { __NR__llseek, "_llseek" }, +#endif +#ifdef __NR__newselect + { __NR__newselect, "_newselect" }, +#endif +#ifdef __NR__sysctl + { __NR__sysctl, "_sysctl" }, +#endif +#ifdef __NR_accept + { __NR_accept, "accept" }, +#endif +#ifdef __NR_accept4 + { __NR_accept4, "accept4" }, +#endif +#ifdef __NR_access + { __NR_access, "access" }, +#endif +#ifdef __NR_acct + { __NR_acct, "acct" }, +#endif +#ifdef __NR_add_key + { __NR_add_key, "add_key" }, +#endif +#ifdef __NR_adjtimex + { __NR_adjtimex, "adjtimex" }, +#endif +#ifdef __NR_afs_syscall + { __NR_afs_syscall, "afs_syscall" }, +#endif +#ifdef __NR_alarm + { __NR_alarm, "alarm" }, +#endif +#ifdef __NR_arch_prctl + { __NR_arch_prctl, "arch_prctl" }, +#endif +#ifdef __NR_bdflush + { __NR_bdflush, "bdflush" }, +#endif +#ifdef __NR_bind + { __NR_bind, "bind" }, +#endif +#ifdef __NR_break + { __NR_break, "break" }, +#endif +#ifdef __NR_brk + { __NR_brk, "brk" }, +#endif +#ifdef __NR_capget + { __NR_capget, "capget" }, +#endif +#ifdef __NR_capset + { __NR_capset, "capset" }, +#endif +#ifdef __NR_chdir + { __NR_chdir, "chdir" }, +#endif +#ifdef __NR_chmod + { __NR_chmod, "chmod" }, +#endif +#ifdef __NR_chown + { __NR_chown, "chown" }, +#endif +#ifdef __NR_chown32 + { __NR_chown32, "chown32" }, +#endif +#ifdef __NR_chroot + { __NR_chroot, "chroot" }, +#endif +#ifdef __NR_clock_adjtime + { __NR_clock_adjtime, "clock_adjtime" }, +#endif +#ifdef __NR_clock_getres + { __NR_clock_getres, "clock_getres" }, +#endif +#ifdef __NR_clock_gettime + { __NR_clock_gettime, "clock_gettime" }, +#endif +#ifdef __NR_clock_nanosleep + { __NR_clock_nanosleep, "clock_nanosleep" }, +#endif +#ifdef __NR_clock_settime + { __NR_clock_settime, "clock_settime" }, +#endif +#ifdef __NR_clone + { __NR_clone, "clone" }, +#endif +#ifdef __NR_close + { __NR_close, "close" }, +#endif +#ifdef __NR_connect + { __NR_connect, "connect" }, +#endif +#ifdef __NR_creat + { __NR_creat, "creat" }, +#endif +#ifdef __NR_create_module + { __NR_create_module, "create_module" }, +#endif +#ifdef __NR_delete_module + { __NR_delete_module, "delete_module" }, +#endif +#ifdef __NR_dup + { __NR_dup, "dup" }, +#endif +#ifdef __NR_dup2 + { __NR_dup2, "dup2" }, +#endif +#ifdef __NR_dup3 + { __NR_dup3, "dup3" }, +#endif +#ifdef __NR_epoll_create + { __NR_epoll_create, "epoll_create" }, +#endif +#ifdef __NR_epoll_create1 + { __NR_epoll_create1, "epoll_create1" }, +#endif +#ifdef __NR_epoll_ctl + { __NR_epoll_ctl, "epoll_ctl" }, +#endif +#ifdef __NR_epoll_ctl_old + { __NR_epoll_ctl_old, "epoll_ctl_old" }, +#endif +#ifdef __NR_epoll_pwait + { __NR_epoll_pwait, "epoll_pwait" }, +#endif +#ifdef __NR_epoll_wait + { __NR_epoll_wait, "epoll_wait" }, +#endif +#ifdef __NR_epoll_wait_old + { __NR_epoll_wait_old, "epoll_wait_old" }, +#endif +#ifdef __NR_eventfd + { __NR_eventfd, "eventfd" }, +#endif +#ifdef __NR_eventfd2 + { __NR_eventfd2, "eventfd2" }, +#endif +#ifdef __NR_execve + { __NR_execve, "execve" }, +#endif +#ifdef __NR_exit + { __NR_exit, "exit" }, +#endif +#ifdef __NR_exit_group + { __NR_exit_group, "exit_group" }, +#endif +#ifdef __NR_faccessat + { __NR_faccessat, "faccessat" }, +#endif +#ifdef __NR_fadvise64 + { __NR_fadvise64, "fadvise64" }, +#endif +#ifdef __NR_fadvise64_64 + { __NR_fadvise64_64, "fadvise64_64" }, +#endif +#ifdef __NR_fallocate + { __NR_fallocate, "fallocate" }, +#endif +#ifdef __NR_fanotify_init + { __NR_fanotify_init, "fanotify_init" }, +#endif +#ifdef __NR_fanotify_mark + { __NR_fanotify_mark, "fanotify_mark" }, +#endif +#ifdef __NR_fchdir + { __NR_fchdir, "fchdir" }, +#endif +#ifdef __NR_fchmod + { __NR_fchmod, "fchmod" }, +#endif +#ifdef __NR_fchmodat + { __NR_fchmodat, "fchmodat" }, +#endif +#ifdef __NR_fchown + { __NR_fchown, "fchown" }, +#endif +#ifdef __NR_fchown32 + { __NR_fchown32, "fchown32" }, +#endif +#ifdef __NR_fchownat + { __NR_fchownat, "fchownat" }, +#endif +#ifdef __NR_fcntl + { __NR_fcntl, "fcntl" }, +#endif +#ifdef __NR_fcntl64 + { __NR_fcntl64, "fcntl64" }, +#endif +#ifdef __NR_fdatasync + { __NR_fdatasync, "fdatasync" }, +#endif +#ifdef __NR_fgetxattr + { __NR_fgetxattr, "fgetxattr" }, +#endif +#ifdef __NR_finit_module + { __NR_finit_module, "finit_module" }, +#endif +#ifdef __NR_flistxattr + { __NR_flistxattr, "flistxattr" }, +#endif +#ifdef __NR_flock + { __NR_flock, "flock" }, +#endif +#ifdef __NR_fork + { __NR_fork, "fork" }, +#endif +#ifdef __NR_fremovexattr + { __NR_fremovexattr, "fremovexattr" }, +#endif +#ifdef __NR_fsetxattr + { __NR_fsetxattr, "fsetxattr" }, +#endif +#ifdef __NR_fstat + { __NR_fstat, "fstat" }, +#endif +#ifdef __NR_fstat64 + { __NR_fstat64, "fstat64" }, +#endif +#ifdef __NR_fstatat64 + { __NR_fstatat64, "fstatat64" }, +#endif +#ifdef __NR_fstatfs + { __NR_fstatfs, "fstatfs" }, +#endif +#ifdef __NR_fstatfs64 + { __NR_fstatfs64, "fstatfs64" }, +#endif +#ifdef __NR_fsync + { __NR_fsync, "fsync" }, +#endif +#ifdef __NR_ftime + { __NR_ftime, "ftime" }, +#endif +#ifdef __NR_ftruncate + { __NR_ftruncate, "ftruncate" }, +#endif +#ifdef __NR_ftruncate64 + { __NR_ftruncate64, "ftruncate64" }, +#endif +#ifdef __NR_futex + { __NR_futex, "futex" }, +#endif +#ifdef __NR_futimesat + { __NR_futimesat, "futimesat" }, +#endif +#ifdef __NR_get_kernel_syms + { __NR_get_kernel_syms, "get_kernel_syms" }, +#endif +#ifdef __NR_get_mempolicy + { __NR_get_mempolicy, "get_mempolicy" }, +#endif +#ifdef __NR_get_robust_list + { __NR_get_robust_list, "get_robust_list" }, +#endif +#ifdef __NR_get_thread_area + { __NR_get_thread_area, "get_thread_area" }, +#endif +#ifdef __NR_getcpu + { __NR_getcpu, "getcpu" }, +#endif +#ifdef __NR_getcwd + { __NR_getcwd, "getcwd" }, +#endif +#ifdef __NR_getdents + { __NR_getdents, "getdents" }, +#endif +#ifdef __NR_getdents64 + { __NR_getdents64, "getdents64" }, +#endif +#ifdef __NR_getegid + { __NR_getegid, "getegid" }, +#endif +#ifdef __NR_getegid32 + { __NR_getegid32, "getegid32" }, +#endif +#ifdef __NR_geteuid + { __NR_geteuid, "geteuid" }, +#endif +#ifdef __NR_geteuid32 + { __NR_geteuid32, "geteuid32" }, +#endif +#ifdef __NR_getgid + { __NR_getgid, "getgid" }, +#endif +#ifdef __NR_getgid32 + { __NR_getgid32, "getgid32" }, +#endif +#ifdef __NR_getgroups + { __NR_getgroups, "getgroups" }, +#endif +#ifdef __NR_getgroups32 + { __NR_getgroups32, "getgroups32" }, +#endif +#ifdef __NR_getitimer + { __NR_getitimer, "getitimer" }, +#endif +#ifdef __NR_getpeername + { __NR_getpeername, "getpeername" }, +#endif +#ifdef __NR_getpgid + { __NR_getpgid, "getpgid" }, +#endif +#ifdef __NR_getpgrp + { __NR_getpgrp, "getpgrp" }, +#endif +#ifdef __NR_getpid + { __NR_getpid, "getpid" }, +#endif +#ifdef __NR_getpmsg + { __NR_getpmsg, "getpmsg" }, +#endif +#ifdef __NR_getppid + { __NR_getppid, "getppid" }, +#endif +#ifdef __NR_getpriority + { __NR_getpriority, "getpriority" }, +#endif +#ifdef __NR_getresgid + { __NR_getresgid, "getresgid" }, +#endif +#ifdef __NR_getresgid32 + { __NR_getresgid32, "getresgid32" }, +#endif +#ifdef __NR_getresuid + { __NR_getresuid, "getresuid" }, +#endif +#ifdef __NR_getresuid32 + { __NR_getresuid32, "getresuid32" }, +#endif +#ifdef __NR_getrlimit + { __NR_getrlimit, "getrlimit" }, +#endif +#ifdef __NR_getrusage + { __NR_getrusage, "getrusage" }, +#endif +#ifdef __NR_getsid + { __NR_getsid, "getsid" }, +#endif +#ifdef __NR_getsockname + { __NR_getsockname, "getsockname" }, +#endif +#ifdef __NR_getsockopt + { __NR_getsockopt, "getsockopt" }, +#endif +#ifdef __NR_gettid + { __NR_gettid, "gettid" }, +#endif +#ifdef __NR_gettimeofday + { __NR_gettimeofday, "gettimeofday" }, +#endif +#ifdef __NR_getuid + { __NR_getuid, "getuid" }, +#endif +#ifdef __NR_getuid32 + { __NR_getuid32, "getuid32" }, +#endif +#ifdef __NR_getxattr + { __NR_getxattr, "getxattr" }, +#endif +#ifdef __NR_gtty + { __NR_gtty, "gtty" }, +#endif +#ifdef __NR_idle + { __NR_idle, "idle" }, +#endif +#ifdef __NR_init_module + { __NR_init_module, "init_module" }, +#endif +#ifdef __NR_inotify_add_watch + { __NR_inotify_add_watch, "inotify_add_watch" }, +#endif +#ifdef __NR_inotify_init + { __NR_inotify_init, "inotify_init" }, +#endif +#ifdef __NR_inotify_init1 + { __NR_inotify_init1, "inotify_init1" }, +#endif +#ifdef __NR_inotify_rm_watch + { __NR_inotify_rm_watch, "inotify_rm_watch" }, +#endif +#ifdef __NR_io_cancel + { __NR_io_cancel, "io_cancel" }, +#endif +#ifdef __NR_io_destroy + { __NR_io_destroy, "io_destroy" }, +#endif +#ifdef __NR_io_getevents + { __NR_io_getevents, "io_getevents" }, +#endif +#ifdef __NR_io_setup + { __NR_io_setup, "io_setup" }, +#endif +#ifdef __NR_io_submit + { __NR_io_submit, "io_submit" }, +#endif +#ifdef __NR_ioctl + { __NR_ioctl, "ioctl" }, +#endif +#ifdef __NR_ioperm + { __NR_ioperm, "ioperm" }, +#endif +#ifdef __NR_iopl + { __NR_iopl, "iopl" }, +#endif +#ifdef __NR_ioprio_get + { __NR_ioprio_get, "ioprio_get" }, +#endif +#ifdef __NR_ioprio_set + { __NR_ioprio_set, "ioprio_set" }, +#endif +#ifdef __NR_ipc + { __NR_ipc, "ipc" }, +#endif +#ifdef __NR_kcmp + { __NR_kcmp, "kcmp" }, +#endif +#ifdef __NR_kexec_load + { __NR_kexec_load, "kexec_load" }, +#endif +#ifdef __NR_keyctl + { __NR_keyctl, "keyctl" }, +#endif +#ifdef __NR_kill + { __NR_kill, "kill" }, +#endif +#ifdef __NR_lchown + { __NR_lchown, "lchown" }, +#endif +#ifdef __NR_lchown32 + { __NR_lchown32, "lchown32" }, +#endif +#ifdef __NR_lgetxattr + { __NR_lgetxattr, "lgetxattr" }, +#endif +#ifdef __NR_link + { __NR_link, "link" }, +#endif +#ifdef __NR_linkat + { __NR_linkat, "linkat" }, +#endif +#ifdef __NR_listen + { __NR_listen, "listen" }, +#endif +#ifdef __NR_listxattr + { __NR_listxattr, "listxattr" }, +#endif +#ifdef __NR_llistxattr + { __NR_llistxattr, "llistxattr" }, +#endif +#ifdef __NR_lock + { __NR_lock, "lock" }, +#endif +#ifdef __NR_lookup_dcookie + { __NR_lookup_dcookie, "lookup_dcookie" }, +#endif +#ifdef __NR_lremovexattr + { __NR_lremovexattr, "lremovexattr" }, +#endif +#ifdef __NR_lseek + { __NR_lseek, "lseek" }, +#endif +#ifdef __NR_lsetxattr + { __NR_lsetxattr, "lsetxattr" }, +#endif +#ifdef __NR_lstat + { __NR_lstat, "lstat" }, +#endif +#ifdef __NR_lstat64 + { __NR_lstat64, "lstat64" }, +#endif +#ifdef __NR_madvise + { __NR_madvise, "madvise" }, +#endif +#ifdef __NR_mbind + { __NR_mbind, "mbind" }, +#endif +#ifdef __NR_migrate_pages + { __NR_migrate_pages, "migrate_pages" }, +#endif +#ifdef __NR_mincore + { __NR_mincore, "mincore" }, +#endif +#ifdef __NR_mkdir + { __NR_mkdir, "mkdir" }, +#endif +#ifdef __NR_mkdirat + { __NR_mkdirat, "mkdirat" }, +#endif +#ifdef __NR_mknod + { __NR_mknod, "mknod" }, +#endif +#ifdef __NR_mknodat + { __NR_mknodat, "mknodat" }, +#endif +#ifdef __NR_mlock + { __NR_mlock, "mlock" }, +#endif +#ifdef __NR_mlockall + { __NR_mlockall, "mlockall" }, +#endif +#ifdef __NR_mmap + { __NR_mmap, "mmap" }, +#endif +#ifdef __NR_mmap2 + { __NR_mmap2, "mmap2" }, +#endif +#ifdef __NR_modify_ldt + { __NR_modify_ldt, "modify_ldt" }, +#endif +#ifdef __NR_mount + { __NR_mount, "mount" }, +#endif +#ifdef __NR_move_pages + { __NR_move_pages, "move_pages" }, +#endif +#ifdef __NR_mprotect + { __NR_mprotect, "mprotect" }, +#endif +#ifdef __NR_mpx + { __NR_mpx, "mpx" }, +#endif +#ifdef __NR_mq_getsetattr + { __NR_mq_getsetattr, "mq_getsetattr" }, +#endif +#ifdef __NR_mq_notify + { __NR_mq_notify, "mq_notify" }, +#endif +#ifdef __NR_mq_open + { __NR_mq_open, "mq_open" }, +#endif +#ifdef __NR_mq_timedreceive + { __NR_mq_timedreceive, "mq_timedreceive" }, +#endif +#ifdef __NR_mq_timedsend + { __NR_mq_timedsend, "mq_timedsend" }, +#endif +#ifdef __NR_mq_unlink + { __NR_mq_unlink, "mq_unlink" }, +#endif +#ifdef __NR_mremap + { __NR_mremap, "mremap" }, +#endif +#ifdef __NR_msgctl + { __NR_msgctl, "msgctl" }, +#endif +#ifdef __NR_msgget + { __NR_msgget, "msgget" }, +#endif +#ifdef __NR_msgrcv + { __NR_msgrcv, "msgrcv" }, +#endif +#ifdef __NR_msgsnd + { __NR_msgsnd, "msgsnd" }, +#endif +#ifdef __NR_msync + { __NR_msync, "msync" }, +#endif +#ifdef __NR_munlock + { __NR_munlock, "munlock" }, +#endif +#ifdef __NR_munlockall + { __NR_munlockall, "munlockall" }, +#endif +#ifdef __NR_munmap + { __NR_munmap, "munmap" }, +#endif +#ifdef __NR_name_to_handle_at + { __NR_name_to_handle_at, "name_to_handle_at" }, +#endif +#ifdef __NR_nanosleep + { __NR_nanosleep, "nanosleep" }, +#endif +#ifdef __NR_newfstatat + { __NR_newfstatat, "newfstatat" }, +#endif +#ifdef __NR_nfsservctl + { __NR_nfsservctl, "nfsservctl" }, +#endif +#ifdef __NR_nice + { __NR_nice, "nice" }, +#endif +#ifdef __NR_oldfstat + { __NR_oldfstat, "oldfstat" }, +#endif +#ifdef __NR_oldlstat + { __NR_oldlstat, "oldlstat" }, +#endif +#ifdef __NR_oldolduname + { __NR_oldolduname, "oldolduname" }, +#endif +#ifdef __NR_oldstat + { __NR_oldstat, "oldstat" }, +#endif +#ifdef __NR_olduname + { __NR_olduname, "olduname" }, +#endif +#ifdef __NR_open + { __NR_open, "open" }, +#endif +#ifdef __NR_open_by_handle_at + { __NR_open_by_handle_at, "open_by_handle_at" }, +#endif +#ifdef __NR_openat + { __NR_openat, "openat" }, +#endif +#ifdef __NR_pause + { __NR_pause, "pause" }, +#endif +#ifdef __NR_perf_event_open + { __NR_perf_event_open, "perf_event_open" }, +#endif +#ifdef __NR_personality + { __NR_personality, "personality" }, +#endif +#ifdef __NR_pipe + { __NR_pipe, "pipe" }, +#endif +#ifdef __NR_pipe2 + { __NR_pipe2, "pipe2" }, +#endif +#ifdef __NR_pivot_root + { __NR_pivot_root, "pivot_root" }, +#endif +#ifdef __NR_poll + { __NR_poll, "poll" }, +#endif +#ifdef __NR_ppoll + { __NR_ppoll, "ppoll" }, +#endif +#ifdef __NR_prctl + { __NR_prctl, "prctl" }, +#endif +#ifdef __NR_pread64 + { __NR_pread64, "pread64" }, +#endif +#ifdef __NR_preadv + { __NR_preadv, "preadv" }, +#endif +#ifdef __NR_prlimit64 + { __NR_prlimit64, "prlimit64" }, +#endif +#ifdef __NR_process_vm_readv + { __NR_process_vm_readv, "process_vm_readv" }, +#endif +#ifdef __NR_process_vm_writev + { __NR_process_vm_writev, "process_vm_writev" }, +#endif +#ifdef __NR_prof + { __NR_prof, "prof" }, +#endif +#ifdef __NR_profil + { __NR_profil, "profil" }, +#endif +#ifdef __NR_pselect6 + { __NR_pselect6, "pselect6" }, +#endif +#ifdef __NR_ptrace + { __NR_ptrace, "ptrace" }, +#endif +#ifdef __NR_putpmsg + { __NR_putpmsg, "putpmsg" }, +#endif +#ifdef __NR_pwrite64 + { __NR_pwrite64, "pwrite64" }, +#endif +#ifdef __NR_pwritev + { __NR_pwritev, "pwritev" }, +#endif +#ifdef __NR_query_module + { __NR_query_module, "query_module" }, +#endif +#ifdef __NR_quotactl + { __NR_quotactl, "quotactl" }, +#endif +#ifdef __NR_read + { __NR_read, "read" }, +#endif +#ifdef __NR_readahead + { __NR_readahead, "readahead" }, +#endif +#ifdef __NR_readdir + { __NR_readdir, "readdir" }, +#endif +#ifdef __NR_readlink + { __NR_readlink, "readlink" }, +#endif +#ifdef __NR_readlinkat + { __NR_readlinkat, "readlinkat" }, +#endif +#ifdef __NR_readv + { __NR_readv, "readv" }, +#endif +#ifdef __NR_reboot + { __NR_reboot, "reboot" }, +#endif +#ifdef __NR_recvfrom + { __NR_recvfrom, "recvfrom" }, +#endif +#ifdef __NR_recvmmsg + { __NR_recvmmsg, "recvmmsg" }, +#endif +#ifdef __NR_recvmsg + { __NR_recvmsg, "recvmsg" }, +#endif +#ifdef __NR_remap_file_pages + { __NR_remap_file_pages, "remap_file_pages" }, +#endif +#ifdef __NR_removexattr + { __NR_removexattr, "removexattr" }, +#endif +#ifdef __NR_rename + { __NR_rename, "rename" }, +#endif +#ifdef __NR_renameat + { __NR_renameat, "renameat" }, +#endif +#ifdef __NR_request_key + { __NR_request_key, "request_key" }, +#endif +#ifdef __NR_restart_syscall + { __NR_restart_syscall, "restart_syscall" }, +#endif +#ifdef __NR_rmdir + { __NR_rmdir, "rmdir" }, +#endif +#ifdef __NR_rt_sigaction + { __NR_rt_sigaction, "rt_sigaction" }, +#endif +#ifdef __NR_rt_sigpending + { __NR_rt_sigpending, "rt_sigpending" }, +#endif +#ifdef __NR_rt_sigprocmask + { __NR_rt_sigprocmask, "rt_sigprocmask" }, +#endif +#ifdef __NR_rt_sigqueueinfo + { __NR_rt_sigqueueinfo, "rt_sigqueueinfo" }, +#endif +#ifdef __NR_rt_sigreturn + { __NR_rt_sigreturn, "rt_sigreturn" }, +#endif +#ifdef __NR_rt_sigsuspend + { __NR_rt_sigsuspend, "rt_sigsuspend" }, +#endif +#ifdef __NR_rt_sigtimedwait + { __NR_rt_sigtimedwait, "rt_sigtimedwait" }, +#endif +#ifdef __NR_rt_tgsigqueueinfo + { __NR_rt_tgsigqueueinfo, "rt_tgsigqueueinfo" }, +#endif +#ifdef __NR_sched_get_priority_max + { __NR_sched_get_priority_max, "sched_get_priority_max" }, +#endif +#ifdef __NR_sched_get_priority_min + { __NR_sched_get_priority_min, "sched_get_priority_min" }, +#endif +#ifdef __NR_sched_getaffinity + { __NR_sched_getaffinity, "sched_getaffinity" }, +#endif +#ifdef __NR_sched_getparam + { __NR_sched_getparam, "sched_getparam" }, +#endif +#ifdef __NR_sched_getscheduler + { __NR_sched_getscheduler, "sched_getscheduler" }, +#endif +#ifdef __NR_sched_rr_get_interval + { __NR_sched_rr_get_interval, "sched_rr_get_interval" }, +#endif +#ifdef __NR_sched_setaffinity + { __NR_sched_setaffinity, "sched_setaffinity" }, +#endif +#ifdef __NR_sched_setparam + { __NR_sched_setparam, "sched_setparam" }, +#endif +#ifdef __NR_sched_setscheduler + { __NR_sched_setscheduler, "sched_setscheduler" }, +#endif +#ifdef __NR_sched_yield + { __NR_sched_yield, "sched_yield" }, +#endif +#ifdef __NR_security + { __NR_security, "security" }, +#endif +#ifdef __NR_select + { __NR_select, "select" }, +#endif +#ifdef __NR_semctl + { __NR_semctl, "semctl" }, +#endif +#ifdef __NR_semget + { __NR_semget, "semget" }, +#endif +#ifdef __NR_semop + { __NR_semop, "semop" }, +#endif +#ifdef __NR_semtimedop + { __NR_semtimedop, "semtimedop" }, +#endif +#ifdef __NR_sendfile + { __NR_sendfile, "sendfile" }, +#endif +#ifdef __NR_sendfile64 + { __NR_sendfile64, "sendfile64" }, +#endif +#ifdef __NR_sendmmsg + { __NR_sendmmsg, "sendmmsg" }, +#endif +#ifdef __NR_sendmsg + { __NR_sendmsg, "sendmsg" }, +#endif +#ifdef __NR_sendto + { __NR_sendto, "sendto" }, +#endif +#ifdef __NR_set_mempolicy + { __NR_set_mempolicy, "set_mempolicy" }, +#endif +#ifdef __NR_set_robust_list + { __NR_set_robust_list, "set_robust_list" }, +#endif +#ifdef __NR_set_thread_area + { __NR_set_thread_area, "set_thread_area" }, +#endif +#ifdef __NR_set_tid_address + { __NR_set_tid_address, "set_tid_address" }, +#endif +#ifdef __NR_setdomainname + { __NR_setdomainname, "setdomainname" }, +#endif +#ifdef __NR_setfsgid + { __NR_setfsgid, "setfsgid" }, +#endif +#ifdef __NR_setfsgid32 + { __NR_setfsgid32, "setfsgid32" }, +#endif +#ifdef __NR_setfsuid + { __NR_setfsuid, "setfsuid" }, +#endif +#ifdef __NR_setfsuid32 + { __NR_setfsuid32, "setfsuid32" }, +#endif +#ifdef __NR_setgid + { __NR_setgid, "setgid" }, +#endif +#ifdef __NR_setgid32 + { __NR_setgid32, "setgid32" }, +#endif +#ifdef __NR_setgroups + { __NR_setgroups, "setgroups" }, +#endif +#ifdef __NR_setgroups32 + { __NR_setgroups32, "setgroups32" }, +#endif +#ifdef __NR_sethostname + { __NR_sethostname, "sethostname" }, +#endif +#ifdef __NR_setitimer + { __NR_setitimer, "setitimer" }, +#endif +#ifdef __NR_setns + { __NR_setns, "setns" }, +#endif +#ifdef __NR_setpgid + { __NR_setpgid, "setpgid" }, +#endif +#ifdef __NR_setpriority + { __NR_setpriority, "setpriority" }, +#endif +#ifdef __NR_setregid + { __NR_setregid, "setregid" }, +#endif +#ifdef __NR_setregid32 + { __NR_setregid32, "setregid32" }, +#endif +#ifdef __NR_setresgid + { __NR_setresgid, "setresgid" }, +#endif +#ifdef __NR_setresgid32 + { __NR_setresgid32, "setresgid32" }, +#endif +#ifdef __NR_setresuid + { __NR_setresuid, "setresuid" }, +#endif +#ifdef __NR_setresuid32 + { __NR_setresuid32, "setresuid32" }, +#endif +#ifdef __NR_setreuid + { __NR_setreuid, "setreuid" }, +#endif +#ifdef __NR_setreuid32 + { __NR_setreuid32, "setreuid32" }, +#endif +#ifdef __NR_setrlimit + { __NR_setrlimit, "setrlimit" }, +#endif +#ifdef __NR_setsid + { __NR_setsid, "setsid" }, +#endif +#ifdef __NR_setsockopt + { __NR_setsockopt, "setsockopt" }, +#endif +#ifdef __NR_settimeofday + { __NR_settimeofday, "settimeofday" }, +#endif +#ifdef __NR_setuid + { __NR_setuid, "setuid" }, +#endif +#ifdef __NR_setuid32 + { __NR_setuid32, "setuid32" }, +#endif +#ifdef __NR_setxattr + { __NR_setxattr, "setxattr" }, +#endif +#ifdef __NR_sgetmask + { __NR_sgetmask, "sgetmask" }, +#endif +#ifdef __NR_shmat + { __NR_shmat, "shmat" }, +#endif +#ifdef __NR_shmctl + { __NR_shmctl, "shmctl" }, +#endif +#ifdef __NR_shmdt + { __NR_shmdt, "shmdt" }, +#endif +#ifdef __NR_shmget + { __NR_shmget, "shmget" }, +#endif +#ifdef __NR_shutdown + { __NR_shutdown, "shutdown" }, +#endif +#ifdef __NR_sigaction + { __NR_sigaction, "sigaction" }, +#endif +#ifdef __NR_sigaltstack + { __NR_sigaltstack, "sigaltstack" }, +#endif +#ifdef __NR_signal + { __NR_signal, "signal" }, +#endif +#ifdef __NR_signalfd + { __NR_signalfd, "signalfd" }, +#endif +#ifdef __NR_signalfd4 + { __NR_signalfd4, "signalfd4" }, +#endif +#ifdef __NR_sigpending + { __NR_sigpending, "sigpending" }, +#endif +#ifdef __NR_sigprocmask + { __NR_sigprocmask, "sigprocmask" }, +#endif +#ifdef __NR_sigreturn + { __NR_sigreturn, "sigreturn" }, +#endif +#ifdef __NR_sigsuspend + { __NR_sigsuspend, "sigsuspend" }, +#endif +#ifdef __NR_socket + { __NR_socket, "socket" }, +#endif +#ifdef __NR_socketcall + { __NR_socketcall, "socketcall" }, +#endif +#ifdef __NR_socketpair + { __NR_socketpair, "socketpair" }, +#endif +#ifdef __NR_splice + { __NR_splice, "splice" }, +#endif +#ifdef __NR_ssetmask + { __NR_ssetmask, "ssetmask" }, +#endif +#ifdef __NR_stat + { __NR_stat, "stat" }, +#endif +#ifdef __NR_stat64 + { __NR_stat64, "stat64" }, +#endif +#ifdef __NR_statfs + { __NR_statfs, "statfs" }, +#endif +#ifdef __NR_statfs64 + { __NR_statfs64, "statfs64" }, +#endif +#ifdef __NR_stime + { __NR_stime, "stime" }, +#endif +#ifdef __NR_stty + { __NR_stty, "stty" }, +#endif +#ifdef __NR_swapoff + { __NR_swapoff, "swapoff" }, +#endif +#ifdef __NR_swapon + { __NR_swapon, "swapon" }, +#endif +#ifdef __NR_symlink + { __NR_symlink, "symlink" }, +#endif +#ifdef __NR_symlinkat + { __NR_symlinkat, "symlinkat" }, +#endif +#ifdef __NR_sync + { __NR_sync, "sync" }, +#endif +#ifdef __NR_sync_file_range + { __NR_sync_file_range, "sync_file_range" }, +#endif +#ifdef __NR_syncfs + { __NR_syncfs, "syncfs" }, +#endif +#ifdef __NR_sysfs + { __NR_sysfs, "sysfs" }, +#endif +#ifdef __NR_sysinfo + { __NR_sysinfo, "sysinfo" }, +#endif +#ifdef __NR_syslog + { __NR_syslog, "syslog" }, +#endif +#ifdef __NR_tee + { __NR_tee, "tee" }, +#endif +#ifdef __NR_tgkill + { __NR_tgkill, "tgkill" }, +#endif +#ifdef __NR_time + { __NR_time, "time" }, +#endif +#ifdef __NR_timer_create + { __NR_timer_create, "timer_create" }, +#endif +#ifdef __NR_timer_delete + { __NR_timer_delete, "timer_delete" }, +#endif +#ifdef __NR_timer_getoverrun + { __NR_timer_getoverrun, "timer_getoverrun" }, +#endif +#ifdef __NR_timer_gettime + { __NR_timer_gettime, "timer_gettime" }, +#endif +#ifdef __NR_timer_settime + { __NR_timer_settime, "timer_settime" }, +#endif +#ifdef __NR_timerfd_create + { __NR_timerfd_create, "timerfd_create" }, +#endif +#ifdef __NR_timerfd_gettime + { __NR_timerfd_gettime, "timerfd_gettime" }, +#endif +#ifdef __NR_timerfd_settime + { __NR_timerfd_settime, "timerfd_settime" }, +#endif +#ifdef __NR_times + { __NR_times, "times" }, +#endif +#ifdef __NR_tkill + { __NR_tkill, "tkill" }, +#endif +#ifdef __NR_truncate + { __NR_truncate, "truncate" }, +#endif +#ifdef __NR_truncate64 + { __NR_truncate64, "truncate64" }, +#endif +#ifdef __NR_tuxcall + { __NR_tuxcall, "tuxcall" }, +#endif +#ifdef __NR_ugetrlimit + { __NR_ugetrlimit, "ugetrlimit" }, +#endif +#ifdef __NR_ulimit + { __NR_ulimit, "ulimit" }, +#endif +#ifdef __NR_umask + { __NR_umask, "umask" }, +#endif +#ifdef __NR_umount + { __NR_umount, "umount" }, +#endif +#ifdef __NR_umount2 + { __NR_umount2, "umount2" }, +#endif +#ifdef __NR_uname + { __NR_uname, "uname" }, +#endif +#ifdef __NR_unlink + { __NR_unlink, "unlink" }, +#endif +#ifdef __NR_unlinkat + { __NR_unlinkat, "unlinkat" }, +#endif +#ifdef __NR_unshare + { __NR_unshare, "unshare" }, +#endif +#ifdef __NR_uselib + { __NR_uselib, "uselib" }, +#endif +#ifdef __NR_ustat + { __NR_ustat, "ustat" }, +#endif +#ifdef __NR_utime + { __NR_utime, "utime" }, +#endif +#ifdef __NR_utimensat + { __NR_utimensat, "utimensat" }, +#endif +#ifdef __NR_utimes + { __NR_utimes, "utimes" }, +#endif +#ifdef __NR_vfork + { __NR_vfork, "vfork" }, +#endif +#ifdef __NR_vhangup + { __NR_vhangup, "vhangup" }, +#endif +#ifdef __NR_vm86 + { __NR_vm86, "vm86" }, +#endif +#ifdef __NR_vm86old + { __NR_vm86old, "vm86old" }, +#endif +#ifdef __NR_vmsplice + { __NR_vmsplice, "vmsplice" }, +#endif +#ifdef __NR_vserver + { __NR_vserver, "vserver" }, +#endif +#ifdef __NR_wait4 + { __NR_wait4, "wait4" }, +#endif +#ifdef __NR_waitid + { __NR_waitid, "waitid" }, +#endif +#ifdef __NR_waitpid + { __NR_waitpid, "waitpid" }, +#endif +#ifdef __NR_write + { __NR_write, "write" }, +#endif +#ifdef __NR_writev + { __NR_writev, "writev" }, +#endif + {0, NULL} +}; + diff --git a/src/lib/sandbox/sandbox.c b/src/lib/sandbox/sandbox.c new file mode 100644 index 0000000000..e2356a1720 --- /dev/null +++ b/src/lib/sandbox/sandbox.c @@ -0,0 +1,1808 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file sandbox.c + * \brief Code to enable sandboxing. + **/ + +#include "orconfig.h" + +#ifndef _LARGEFILE64_SOURCE +/** + * Temporarily required for O_LARGEFILE flag. Needs to be removed + * with the libevent fix. + */ +#define _LARGEFILE64_SOURCE +#endif /* !defined(_LARGEFILE64_SOURCE) */ + +/** Malloc mprotect limit in bytes. + * + * 28/06/2017: This value was increased from 16 MB to 20 MB after we introduced + * LZMA support in Tor (0.3.1.1-alpha). We limit our LZMA coder to 16 MB, but + * liblzma have a small overhead that we need to compensate for to avoid being + * killed by the sandbox. + */ +#define MALLOC_MP_LIM (20*1024*1024) + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +#include "lib/sandbox/sandbox.h" +#include "lib/container/map.h" +#include "lib/err/torerr.h" +#include "lib/log/log.h" +#include "lib/cc/torint.h" +#include "lib/net/resolve.h" +#include "lib/malloc/malloc.h" +#include "lib/string/scanf.h" + +#include "tor_queue.h" +#include "ht.h" +#include "siphash.h" + +#define DEBUGGING_CLOSE + +#if defined(USE_LIBSECCOMP) + +#include <sys/mman.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/epoll.h> +#include <sys/prctl.h> +#include <linux/futex.h> +#include <sys/file.h> + +#include <stdarg.h> +#include <seccomp.h> +#include <signal.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <poll.h> + +#ifdef HAVE_GNU_LIBC_VERSION_H +#include <gnu/libc-version.h> +#endif +#ifdef HAVE_LINUX_NETFILTER_IPV4_H +#include <linux/netfilter_ipv4.h> +#endif +#ifdef HAVE_LINUX_IF_H +#include <linux/if.h> +#endif +#ifdef HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H +#include <linux/netfilter_ipv6/ip6_tables.h> +#endif + +#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && \ + defined(HAVE_BACKTRACE_SYMBOLS_FD) && defined(HAVE_SIGACTION) +#define USE_BACKTRACE +#define EXPOSE_CLEAN_BACKTRACE +#include "lib/err/backtrace.h" +#endif /* defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && ... */ + +#ifdef USE_BACKTRACE +#include <execinfo.h> +#endif + +/** + * Linux 32 bit definitions + */ +#if defined(__i386__) + +#define REG_SYSCALL REG_EAX +#define M_SYSCALL gregs[REG_SYSCALL] + +/** + * Linux 64 bit definitions + */ +#elif defined(__x86_64__) + +#define REG_SYSCALL REG_RAX +#define M_SYSCALL gregs[REG_SYSCALL] + +#elif defined(__arm__) + +#define M_SYSCALL arm_r7 + +#elif defined(__aarch64__) && defined(__LP64__) + +#define REG_SYSCALL 8 +#define M_SYSCALL regs[REG_SYSCALL] + +#endif /* defined(__i386__) || ... */ + +/**Determines if at least one sandbox is active.*/ +static int sandbox_active = 0; +/** Holds the parameter list configuration for the sandbox.*/ +static sandbox_cfg_t *filter_dynamic = NULL; + +#undef SCMP_CMP +#define SCMP_CMP(a,b,c) ((struct scmp_arg_cmp){(a),(b),(c),0}) +#define SCMP_CMP_STR(a,b,c) \ + ((struct scmp_arg_cmp) {(a),(b),(intptr_t)(void*)(c),0}) +#define SCMP_CMP4(a,b,c,d) ((struct scmp_arg_cmp){(a),(b),(c),(d)}) +/* We use a wrapper here because these masked comparisons seem to be pretty + * verbose. Also, it's important to cast to scmp_datum_t before negating the + * mask, since otherwise the negation might get applied to a 32 bit value, and + * the high bits of the value might get masked out improperly. */ +#define SCMP_CMP_MASKED(a,b,c) \ + SCMP_CMP4((a), SCMP_CMP_MASKED_EQ, ~(scmp_datum_t)(b), (c)) + +/** Variable used for storing all syscall numbers that will be allowed with the + * stage 1 general Tor sandbox. + */ +static int filter_nopar_gen[] = { + SCMP_SYS(access), + SCMP_SYS(brk), + SCMP_SYS(clock_gettime), + SCMP_SYS(close), + SCMP_SYS(clone), + SCMP_SYS(epoll_create), + SCMP_SYS(epoll_wait), +#ifdef __NR_epoll_pwait + SCMP_SYS(epoll_pwait), +#endif +#ifdef HAVE_EVENTFD + SCMP_SYS(eventfd2), +#endif +#ifdef HAVE_PIPE2 + SCMP_SYS(pipe2), +#endif +#ifdef HAVE_PIPE + SCMP_SYS(pipe), +#endif +#ifdef __NR_fchmod + SCMP_SYS(fchmod), +#endif + SCMP_SYS(fcntl), + SCMP_SYS(fstat), +#ifdef __NR_fstat64 + SCMP_SYS(fstat64), +#endif + SCMP_SYS(futex), + SCMP_SYS(getdents), + SCMP_SYS(getdents64), + SCMP_SYS(getegid), +#ifdef __NR_getegid32 + SCMP_SYS(getegid32), +#endif + SCMP_SYS(geteuid), +#ifdef __NR_geteuid32 + SCMP_SYS(geteuid32), +#endif + SCMP_SYS(getgid), +#ifdef __NR_getgid32 + SCMP_SYS(getgid32), +#endif + SCMP_SYS(getpid), +#ifdef __NR_getrlimit + SCMP_SYS(getrlimit), +#endif + SCMP_SYS(gettimeofday), + SCMP_SYS(gettid), + SCMP_SYS(getuid), +#ifdef __NR_getuid32 + SCMP_SYS(getuid32), +#endif + SCMP_SYS(lseek), +#ifdef __NR__llseek + SCMP_SYS(_llseek), +#endif + SCMP_SYS(mkdir), + SCMP_SYS(mlockall), +#ifdef __NR_mmap + /* XXXX restrict this in the same ways as mmap2 */ + SCMP_SYS(mmap), +#endif + SCMP_SYS(munmap), +#ifdef __NR_nanosleep + SCMP_SYS(nanosleep), +#endif +#ifdef __NR_prlimit + SCMP_SYS(prlimit), +#endif +#ifdef __NR_prlimit64 + SCMP_SYS(prlimit64), +#endif + SCMP_SYS(read), + SCMP_SYS(rt_sigreturn), + SCMP_SYS(sched_getaffinity), +#ifdef __NR_sched_yield + SCMP_SYS(sched_yield), +#endif + SCMP_SYS(sendmsg), + SCMP_SYS(set_robust_list), +#ifdef __NR_setrlimit + SCMP_SYS(setrlimit), +#endif + SCMP_SYS(shutdown), +#ifdef __NR_sigaltstack + SCMP_SYS(sigaltstack), +#endif +#ifdef __NR_sigreturn + SCMP_SYS(sigreturn), +#endif + SCMP_SYS(stat), + SCMP_SYS(uname), + SCMP_SYS(wait4), + SCMP_SYS(write), + SCMP_SYS(writev), + SCMP_SYS(exit_group), + SCMP_SYS(exit), + + SCMP_SYS(madvise), +#ifdef __NR_stat64 + // getaddrinfo uses this.. + SCMP_SYS(stat64), +#endif + +#ifdef __NR_getrandom + SCMP_SYS(getrandom), +#endif + +#ifdef __NR_sysinfo + // qsort uses this.. + SCMP_SYS(sysinfo), +#endif + /* + * These socket syscalls are not required on x86_64 and not supported with + * some libseccomp versions (eg: 1.0.1) + */ +#if defined(__i386) + SCMP_SYS(recv), + SCMP_SYS(send), +#endif + + // socket syscalls + SCMP_SYS(bind), + SCMP_SYS(listen), + SCMP_SYS(connect), + SCMP_SYS(getsockname), + SCMP_SYS(recvmsg), + SCMP_SYS(recvfrom), + SCMP_SYS(sendto), + SCMP_SYS(unlink), + SCMP_SYS(poll) +}; + +/* These macros help avoid the error where the number of filters we add on a + * single rule don't match the arg_cnt param. */ +#define seccomp_rule_add_0(ctx,act,call) \ + seccomp_rule_add((ctx),(act),(call),0) +#define seccomp_rule_add_1(ctx,act,call,f1) \ + seccomp_rule_add((ctx),(act),(call),1,(f1)) +#define seccomp_rule_add_2(ctx,act,call,f1,f2) \ + seccomp_rule_add((ctx),(act),(call),2,(f1),(f2)) +#define seccomp_rule_add_3(ctx,act,call,f1,f2,f3) \ + seccomp_rule_add((ctx),(act),(call),3,(f1),(f2),(f3)) +#define seccomp_rule_add_4(ctx,act,call,f1,f2,f3,f4) \ + seccomp_rule_add((ctx),(act),(call),4,(f1),(f2),(f3),(f4)) + +/** + * Function responsible for setting up the rt_sigaction syscall for + * the seccomp filter sandbox. + */ +static int +sb_rt_sigaction(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + unsigned i; + int rc; + int param[] = { SIGINT, SIGTERM, SIGPIPE, SIGUSR1, SIGUSR2, SIGHUP, SIGCHLD, +#ifdef SIGXFSZ + SIGXFSZ +#endif + }; + (void) filter; + + for (i = 0; i < ARRAY_LENGTH(param); i++) { + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigaction), + SCMP_CMP(0, SCMP_CMP_EQ, param[i])); + if (rc) + break; + } + + return rc; +} + +/** + * Function responsible for setting up the time syscall for + * the seccomp filter sandbox. + */ +static int +sb_time(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + (void) filter; +#ifdef __NR_time + return seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(time), + SCMP_CMP(0, SCMP_CMP_EQ, 0)); +#else + return 0; +#endif /* defined(__NR_time) */ +} + +/** + * Function responsible for setting up the accept4 syscall for + * the seccomp filter sandbox. + */ +static int +sb_accept4(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void)filter; + +#ifdef __i386__ + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socketcall), + SCMP_CMP(0, SCMP_CMP_EQ, 18)); + if (rc) { + return rc; + } +#endif /* defined(__i386__) */ + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(accept4), + SCMP_CMP_MASKED(3, SOCK_CLOEXEC|SOCK_NONBLOCK, 0)); + if (rc) { + return rc; + } + + return 0; +} + +#ifdef __NR_mmap2 +/** + * Function responsible for setting up the mmap2 syscall for + * the seccomp filter sandbox. + */ +static int +sb_mmap2(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void)filter; + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ), + SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE)); + if (rc) { + return rc; + } + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), + SCMP_CMP(2, SCMP_CMP_EQ, PROT_NONE), + SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE)); + if (rc) { + return rc; + } + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE), + SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_ANONYMOUS)); + if (rc) { + return rc; + } + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE), + SCMP_CMP(3, SCMP_CMP_EQ,MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK)); + if (rc) { + return rc; + } + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE), + SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE)); + if (rc) { + return rc; + } + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE), + SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS)); + if (rc) { + return rc; + } + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap2), + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_EXEC), + SCMP_CMP(3, SCMP_CMP_EQ, MAP_PRIVATE|MAP_DENYWRITE)); + if (rc) { + return rc; + } + + return 0; +} +#endif /* defined(__NR_mmap2) */ + +#ifdef HAVE_GNU_LIBC_VERSION_H +#ifdef HAVE_GNU_GET_LIBC_VERSION +#define CHECK_LIBC_VERSION +#endif +#endif + +/* Return true if we think we're running with a libc that always uses + * openat on linux. */ +static int +libc_uses_openat_for_everything(void) +{ +#ifdef CHECK_LIBC_VERSION + const char *version = gnu_get_libc_version(); + if (version == NULL) + return 0; + + int major = -1; + int minor = -1; + + tor_sscanf(version, "%d.%d", &major, &minor); + if (major >= 3) + return 1; + else if (major == 2 && minor >= 26) + return 1; + else + return 0; +#else /* !(defined(CHECK_LIBC_VERSION)) */ + return 0; +#endif /* defined(CHECK_LIBC_VERSION) */ +} + +/** Allow a single file to be opened. If <b>use_openat</b> is true, + * we're using a libc that remaps all the opens into openats. */ +static int +allow_file_open(scmp_filter_ctx ctx, int use_openat, const char *file) +{ + if (use_openat) { + return seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), + SCMP_CMP(0, SCMP_CMP_EQ, (unsigned int)AT_FDCWD), + SCMP_CMP_STR(1, SCMP_CMP_EQ, file)); + } else { + return seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), + SCMP_CMP_STR(0, SCMP_CMP_EQ, file)); + } +} + +/** + * Function responsible for setting up the open syscall for + * the seccomp filter sandbox. + */ +static int +sb_open(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc; + sandbox_cfg_t *elem = NULL; + + int use_openat = libc_uses_openat_for_everything(); + + // for each dynamic parameter filters + for (elem = filter; elem != NULL; elem = elem->next) { + smp_param_t *param = elem->param; + + if (param != NULL && param->prot == 1 && param->syscall + == SCMP_SYS(open)) { + rc = allow_file_open(ctx, use_openat, param->value); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add open syscall, received " + "libseccomp error %d", rc); + return rc; + } + } + } + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(open), + SCMP_CMP_MASKED(1, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_NOFOLLOW, + O_RDONLY)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add open syscall, received libseccomp " + "error %d", rc); + return rc; + } + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(openat), + SCMP_CMP_MASKED(2, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_NOFOLLOW, + O_RDONLY)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add openat syscall, received " + "libseccomp error %d", rc); + return rc; + } + + return 0; +} + +static int +sb_chmod(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc; + sandbox_cfg_t *elem = NULL; + + // for each dynamic parameter filters + for (elem = filter; elem != NULL; elem = elem->next) { + smp_param_t *param = elem->param; + + if (param != NULL && param->prot == 1 && param->syscall + == SCMP_SYS(chmod)) { + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(chmod), + SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add chmod syscall, received " + "libseccomp error %d", rc); + return rc; + } + } + } + + return 0; +} + +static int +sb_chown(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc; + sandbox_cfg_t *elem = NULL; + + // for each dynamic parameter filters + for (elem = filter; elem != NULL; elem = elem->next) { + smp_param_t *param = elem->param; + + if (param != NULL && param->prot == 1 && param->syscall + == SCMP_SYS(chown)) { + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(chown), + SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add chown syscall, received " + "libseccomp error %d", rc); + return rc; + } + } + } + + return 0; +} + +static int +sb__sysctl(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc; + (void) filter; + (void) ctx; + + rc = seccomp_rule_add_0(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(_sysctl)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add _sysctl syscall, " + "received libseccomp error %d", rc); + return rc; + } + + return 0; +} + +/** + * Function responsible for setting up the rename syscall for + * the seccomp filter sandbox. + */ +static int +sb_rename(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc; + sandbox_cfg_t *elem = NULL; + + // for each dynamic parameter filters + for (elem = filter; elem != NULL; elem = elem->next) { + smp_param_t *param = elem->param; + + if (param != NULL && param->prot == 1 && + param->syscall == SCMP_SYS(rename)) { + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rename), + SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value), + SCMP_CMP_STR(1, SCMP_CMP_EQ, param->value2)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add rename syscall, received " + "libseccomp error %d", rc); + return rc; + } + } + } + + return 0; +} + +/** + * Function responsible for setting up the openat syscall for + * the seccomp filter sandbox. + */ +static int +sb_openat(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc; + sandbox_cfg_t *elem = NULL; + + // for each dynamic parameter filters + for (elem = filter; elem != NULL; elem = elem->next) { + smp_param_t *param = elem->param; + + if (param != NULL && param->prot == 1 && param->syscall + == SCMP_SYS(openat)) { + rc = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), + SCMP_CMP(0, SCMP_CMP_EQ, AT_FDCWD), + SCMP_CMP_STR(1, SCMP_CMP_EQ, param->value), + SCMP_CMP(2, SCMP_CMP_EQ, O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY| + O_CLOEXEC)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add openat syscall, received " + "libseccomp error %d", rc); + return rc; + } + } + } + + return 0; +} + +/** + * Function responsible for setting up the socket syscall for + * the seccomp filter sandbox. + */ +static int +sb_socket(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + int i, j; + (void) filter; + +#ifdef __i386__ + rc = seccomp_rule_add_0(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket)); + if (rc) + return rc; +#endif + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), + SCMP_CMP(0, SCMP_CMP_EQ, PF_FILE), + SCMP_CMP_MASKED(1, SOCK_CLOEXEC|SOCK_NONBLOCK, SOCK_STREAM)); + if (rc) + return rc; + + for (i = 0; i < 2; ++i) { + const int pf = i ? PF_INET : PF_INET6; + for (j=0; j < 3; ++j) { + const int type = (j == 0) ? SOCK_STREAM : + SOCK_DGRAM; + const int protocol = (j == 0) ? IPPROTO_TCP : + (j == 1) ? IPPROTO_IP : + IPPROTO_UDP; + rc = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), + SCMP_CMP(0, SCMP_CMP_EQ, pf), + SCMP_CMP_MASKED(1, SOCK_CLOEXEC|SOCK_NONBLOCK, type), + SCMP_CMP(2, SCMP_CMP_EQ, protocol)); + if (rc) + return rc; + } + } + + rc = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), + SCMP_CMP(0, SCMP_CMP_EQ, PF_UNIX), + SCMP_CMP_MASKED(1, SOCK_CLOEXEC|SOCK_NONBLOCK, SOCK_STREAM), + SCMP_CMP(2, SCMP_CMP_EQ, 0)); + if (rc) + return rc; + + rc = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), + SCMP_CMP(0, SCMP_CMP_EQ, PF_UNIX), + SCMP_CMP_MASKED(1, SOCK_CLOEXEC|SOCK_NONBLOCK, SOCK_DGRAM), + SCMP_CMP(2, SCMP_CMP_EQ, 0)); + if (rc) + return rc; + + rc = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), + SCMP_CMP(0, SCMP_CMP_EQ, PF_NETLINK), + SCMP_CMP_MASKED(1, SOCK_CLOEXEC, SOCK_RAW), + SCMP_CMP(2, SCMP_CMP_EQ, 0)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the socketpair syscall for + * the seccomp filter sandbox. + */ +static int +sb_socketpair(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + +#ifdef __i386__ + rc = seccomp_rule_add_0(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socketpair)); + if (rc) + return rc; +#endif + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socketpair), + SCMP_CMP(0, SCMP_CMP_EQ, PF_FILE), + SCMP_CMP(1, SCMP_CMP_EQ, SOCK_STREAM|SOCK_CLOEXEC)); + if (rc) + return rc; + + return 0; +} + +#ifdef HAVE_KIST_SUPPORT + +#include <linux/sockios.h> + +static int +sb_ioctl(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc; + (void) filter; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), + SCMP_CMP(1, SCMP_CMP_EQ, SIOCOUTQNSD)); + if (rc) + return rc; + return 0; +} + +#endif /* defined(HAVE_KIST_SUPPORT) */ + +/** + * Function responsible for setting up the setsockopt syscall for + * the seccomp filter sandbox. + */ +static int +sb_setsockopt(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + +#ifdef __i386__ + rc = seccomp_rule_add_0(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt)); + if (rc) + return rc; +#endif + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), + SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET), + SCMP_CMP(2, SCMP_CMP_EQ, SO_REUSEADDR)); + if (rc) + return rc; + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), + SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET), + SCMP_CMP(2, SCMP_CMP_EQ, SO_SNDBUF)); + if (rc) + return rc; + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), + SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET), + SCMP_CMP(2, SCMP_CMP_EQ, SO_RCVBUF)); + if (rc) + return rc; + +#ifdef HAVE_SYSTEMD + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), + SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET), + SCMP_CMP(2, SCMP_CMP_EQ, SO_SNDBUFFORCE)); + if (rc) + return rc; +#endif /* defined(HAVE_SYSTEMD) */ + +#ifdef IP_TRANSPARENT + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), + SCMP_CMP(1, SCMP_CMP_EQ, SOL_IP), + SCMP_CMP(2, SCMP_CMP_EQ, IP_TRANSPARENT)); + if (rc) + return rc; +#endif /* defined(IP_TRANSPARENT) */ + +#ifdef IPV6_V6ONLY + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), + SCMP_CMP(1, SCMP_CMP_EQ, IPPROTO_IPV6), + SCMP_CMP(2, SCMP_CMP_EQ, IPV6_V6ONLY)); + if (rc) + return rc; +#endif /* defined(IPV6_V6ONLY) */ + + return 0; +} + +/** + * Function responsible for setting up the getsockopt syscall for + * the seccomp filter sandbox. + */ +static int +sb_getsockopt(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + +#ifdef __i386__ + rc = seccomp_rule_add_0(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt)); + if (rc) + return rc; +#endif + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt), + SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET), + SCMP_CMP(2, SCMP_CMP_EQ, SO_ERROR)); + if (rc) + return rc; + +#ifdef HAVE_SYSTEMD + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt), + SCMP_CMP(1, SCMP_CMP_EQ, SOL_SOCKET), + SCMP_CMP(2, SCMP_CMP_EQ, SO_SNDBUF)); + if (rc) + return rc; +#endif /* defined(HAVE_SYSTEMD) */ + +#ifdef HAVE_LINUX_NETFILTER_IPV4_H + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt), + SCMP_CMP(1, SCMP_CMP_EQ, SOL_IP), + SCMP_CMP(2, SCMP_CMP_EQ, SO_ORIGINAL_DST)); + if (rc) + return rc; +#endif /* defined(HAVE_LINUX_NETFILTER_IPV4_H) */ + +#ifdef HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt), + SCMP_CMP(1, SCMP_CMP_EQ, SOL_IPV6), + SCMP_CMP(2, SCMP_CMP_EQ, IP6T_SO_ORIGINAL_DST)); + if (rc) + return rc; +#endif /* defined(HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H) */ + +#ifdef HAVE_KIST_SUPPORT +#include <netinet/tcp.h> + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt), + SCMP_CMP(1, SCMP_CMP_EQ, SOL_TCP), + SCMP_CMP(2, SCMP_CMP_EQ, TCP_INFO)); + if (rc) + return rc; +#endif /* defined(HAVE_KIST_SUPPORT) */ + + return 0; +} + +#ifdef __NR_fcntl64 +/** + * Function responsible for setting up the fcntl64 syscall for + * the seccomp filter sandbox. + */ +static int +sb_fcntl64(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64), + SCMP_CMP(1, SCMP_CMP_EQ, F_GETFL)); + if (rc) + return rc; + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64), + SCMP_CMP(1, SCMP_CMP_EQ, F_SETFL), + SCMP_CMP(2, SCMP_CMP_EQ, O_RDWR|O_NONBLOCK)); + if (rc) + return rc; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64), + SCMP_CMP(1, SCMP_CMP_EQ, F_GETFD)); + if (rc) + return rc; + + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl64), + SCMP_CMP(1, SCMP_CMP_EQ, F_SETFD), + SCMP_CMP(2, SCMP_CMP_EQ, FD_CLOEXEC)); + if (rc) + return rc; + + return 0; +} +#endif /* defined(__NR_fcntl64) */ + +/** + * Function responsible for setting up the epoll_ctl syscall for + * the seccomp filter sandbox. + * + * Note: basically allows everything but will keep for now.. + */ +static int +sb_epoll_ctl(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_ctl), + SCMP_CMP(1, SCMP_CMP_EQ, EPOLL_CTL_ADD)); + if (rc) + return rc; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_ctl), + SCMP_CMP(1, SCMP_CMP_EQ, EPOLL_CTL_MOD)); + if (rc) + return rc; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_ctl), + SCMP_CMP(1, SCMP_CMP_EQ, EPOLL_CTL_DEL)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the prctl syscall for + * the seccomp filter sandbox. + * + * NOTE: if multiple filters need to be added, the PR_SECCOMP parameter needs + * to be whitelisted in this function. + */ +static int +sb_prctl(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(prctl), + SCMP_CMP(0, SCMP_CMP_EQ, PR_SET_DUMPABLE)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the mprotect syscall for + * the seccomp filter sandbox. + * + * NOTE: does not NEED to be here.. currently only occurs before filter; will + * keep just in case for the future. + */ +static int +sb_mprotect(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ)); + if (rc) + return rc; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), + SCMP_CMP(2, SCMP_CMP_EQ, PROT_NONE)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the rt_sigprocmask syscall for + * the seccomp filter sandbox. + */ +static int +sb_rt_sigprocmask(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask), + SCMP_CMP(0, SCMP_CMP_EQ, SIG_UNBLOCK)); + if (rc) + return rc; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask), + SCMP_CMP(0, SCMP_CMP_EQ, SIG_SETMASK)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the flock syscall for + * the seccomp filter sandbox. + * + * NOTE: does not need to be here, occurs before filter is applied. + */ +static int +sb_flock(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(flock), + SCMP_CMP(1, SCMP_CMP_EQ, LOCK_EX|LOCK_NB)); + if (rc) + return rc; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(flock), + SCMP_CMP(1, SCMP_CMP_EQ, LOCK_UN)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the futex syscall for + * the seccomp filter sandbox. + */ +static int +sb_futex(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + // can remove + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), + SCMP_CMP(1, SCMP_CMP_EQ, + FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME)); + if (rc) + return rc; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), + SCMP_CMP(1, SCMP_CMP_EQ, FUTEX_WAKE_PRIVATE)); + if (rc) + return rc; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), + SCMP_CMP(1, SCMP_CMP_EQ, FUTEX_WAIT_PRIVATE)); + if (rc) + return rc; + + return 0; +} + +/** + * Function responsible for setting up the mremap syscall for + * the seccomp filter sandbox. + * + * NOTE: so far only occurs before filter is applied. + */ +static int +sb_mremap(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + (void) filter; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mremap), + SCMP_CMP(3, SCMP_CMP_EQ, MREMAP_MAYMOVE)); + if (rc) + return rc; + + return 0; +} + +#ifdef __NR_stat64 +/** + * Function responsible for setting up the stat64 syscall for + * the seccomp filter sandbox. + */ +static int +sb_stat64(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc = 0; + sandbox_cfg_t *elem = NULL; + + // for each dynamic parameter filters + for (elem = filter; elem != NULL; elem = elem->next) { + smp_param_t *param = elem->param; + + if (param != NULL && param->prot == 1 && (param->syscall == SCMP_SYS(open) + || param->syscall == SCMP_SYS(stat64))) { + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(stat64), + SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add stat64 syscall, received " + "libseccomp error %d", rc); + return rc; + } + } + } + + return 0; +} +#endif /* defined(__NR_stat64) */ + +static int +sb_kill(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + (void) filter; +#ifdef __NR_kill + /* Allow killing anything with signal 0 -- it isn't really a kill. */ + return seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(kill), + SCMP_CMP(1, SCMP_CMP_EQ, 0)); +#else + return 0; +#endif /* defined(__NR_kill) */ +} + +/** + * Array of function pointers responsible for filtering different syscalls at + * a parameter level. + */ +static sandbox_filter_func_t filter_func[] = { + sb_rt_sigaction, + sb_rt_sigprocmask, + sb_time, + sb_accept4, +#ifdef __NR_mmap2 + sb_mmap2, +#endif + sb_chown, + sb_chmod, + sb_open, + sb_openat, + sb__sysctl, + sb_rename, +#ifdef __NR_fcntl64 + sb_fcntl64, +#endif + sb_epoll_ctl, + sb_prctl, + sb_mprotect, + sb_flock, + sb_futex, + sb_mremap, +#ifdef __NR_stat64 + sb_stat64, +#endif + + sb_socket, + sb_setsockopt, + sb_getsockopt, + sb_socketpair, +#ifdef HAVE_KIST_SUPPORT + sb_ioctl, +#endif + sb_kill +}; + +const char * +sandbox_intern_string(const char *str) +{ + sandbox_cfg_t *elem; + + if (str == NULL) + return NULL; + + for (elem = filter_dynamic; elem != NULL; elem = elem->next) { + smp_param_t *param = elem->param; + + if (param->prot) { + if (!strcmp(str, (char*)(param->value))) { + return (char*)param->value; + } + if (param->value2 && !strcmp(str, (char*)param->value2)) { + return (char*)param->value2; + } + } + } + + if (sandbox_active) + log_warn(LD_BUG, "No interned sandbox parameter found for %s", str); + return str; +} + +/* DOCDOC */ +static int +prot_strings_helper(strmap_t *locations, + char **pr_mem_next_p, + size_t *pr_mem_left_p, + char **value_p) +{ + char *param_val; + size_t param_size; + void *location; + + if (*value_p == 0) + return 0; + + param_val = (char*) *value_p; + param_size = strlen(param_val) + 1; + location = strmap_get(locations, param_val); + + if (location) { + // We already interned this string. + tor_free(param_val); + *value_p = location; + return 0; + } else if (*pr_mem_left_p >= param_size) { + // copy to protected + location = *pr_mem_next_p; + memcpy(location, param_val, param_size); + + // re-point el parameter to protected + tor_free(param_val); + *value_p = location; + + strmap_set(locations, location, location); /* good real estate advice */ + + // move next available protected memory + *pr_mem_next_p += param_size; + *pr_mem_left_p -= param_size; + return 0; + } else { + log_err(LD_BUG,"(Sandbox) insufficient protected memory!"); + return -1; + } +} + +/** + * Protects all the strings in the sandbox's parameter list configuration. It + * works by calculating the total amount of memory required by the parameter + * list, allocating the memory using mmap, and protecting it from writes with + * mprotect(). + */ +static int +prot_strings(scmp_filter_ctx ctx, sandbox_cfg_t* cfg) +{ + int ret = 0; + size_t pr_mem_size = 0, pr_mem_left = 0; + char *pr_mem_next = NULL, *pr_mem_base; + sandbox_cfg_t *el = NULL; + strmap_t *locations = NULL; + + // get total number of bytes required to mmap. (Overestimate.) + for (el = cfg; el != NULL; el = el->next) { + pr_mem_size += strlen((char*) el->param->value) + 1; + if (el->param->value2) + pr_mem_size += strlen((char*) el->param->value2) + 1; + } + + // allocate protected memory with MALLOC_MP_LIM canary + pr_mem_base = (char*) mmap(NULL, MALLOC_MP_LIM + pr_mem_size, + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (pr_mem_base == MAP_FAILED) { + log_err(LD_BUG,"(Sandbox) failed allocate protected memory! mmap: %s", + strerror(errno)); + ret = -1; + goto out; + } + + pr_mem_next = pr_mem_base + MALLOC_MP_LIM; + pr_mem_left = pr_mem_size; + + locations = strmap_new(); + + // change el value pointer to protected + for (el = cfg; el != NULL; el = el->next) { + if (prot_strings_helper(locations, &pr_mem_next, &pr_mem_left, + &el->param->value) < 0) { + ret = -2; + goto out; + } + if (prot_strings_helper(locations, &pr_mem_next, &pr_mem_left, + &el->param->value2) < 0) { + ret = -2; + goto out; + } + el->param->prot = 1; + } + + // protecting from writes + if (mprotect(pr_mem_base, MALLOC_MP_LIM + pr_mem_size, PROT_READ)) { + log_err(LD_BUG,"(Sandbox) failed to protect memory! mprotect: %s", + strerror(errno)); + ret = -3; + goto out; + } + + /* + * Setting sandbox restrictions so the string memory cannot be tampered with + */ + // no mremap of the protected base address + ret = seccomp_rule_add_1(ctx, SCMP_ACT_KILL, SCMP_SYS(mremap), + SCMP_CMP(0, SCMP_CMP_EQ, (intptr_t) pr_mem_base)); + if (ret) { + log_err(LD_BUG,"(Sandbox) mremap protected memory filter fail!"); + goto out; + } + + // no munmap of the protected base address + ret = seccomp_rule_add_1(ctx, SCMP_ACT_KILL, SCMP_SYS(munmap), + SCMP_CMP(0, SCMP_CMP_EQ, (intptr_t) pr_mem_base)); + if (ret) { + log_err(LD_BUG,"(Sandbox) munmap protected memory filter fail!"); + goto out; + } + + /* + * Allow mprotect with PROT_READ|PROT_WRITE because openssl uses it, but + * never over the memory region used by the protected strings. + * + * PROT_READ|PROT_WRITE was originally fully allowed in sb_mprotect(), but + * had to be removed due to limitation of libseccomp regarding intervals. + * + * There is a restriction on how much you can mprotect with R|W up to the + * size of the canary. + */ + ret = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), + SCMP_CMP(0, SCMP_CMP_LT, (intptr_t) pr_mem_base), + SCMP_CMP(1, SCMP_CMP_LE, MALLOC_MP_LIM), + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE)); + if (ret) { + log_err(LD_BUG,"(Sandbox) mprotect protected memory filter fail (LT)!"); + goto out; + } + + ret = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), + SCMP_CMP(0, SCMP_CMP_GT, (intptr_t) pr_mem_base + pr_mem_size + + MALLOC_MP_LIM), + SCMP_CMP(1, SCMP_CMP_LE, MALLOC_MP_LIM), + SCMP_CMP(2, SCMP_CMP_EQ, PROT_READ|PROT_WRITE)); + if (ret) { + log_err(LD_BUG,"(Sandbox) mprotect protected memory filter fail (GT)!"); + goto out; + } + + out: + strmap_free(locations, NULL); + return ret; +} + +/** + * Auxiliary function used in order to allocate a sandbox_cfg_t element and set + * its values according the parameter list. All elements are initialised + * with the 'prot' field set to false, as the pointer is not protected at this + * point. + */ +static sandbox_cfg_t* +new_element2(int syscall, char *value, char *value2) +{ + smp_param_t *param = NULL; + + sandbox_cfg_t *elem = tor_malloc_zero(sizeof(sandbox_cfg_t)); + param = elem->param = tor_malloc_zero(sizeof(smp_param_t)); + + param->syscall = syscall; + param->value = value; + param->value2 = value2; + param->prot = 0; + + return elem; +} + +static sandbox_cfg_t* +new_element(int syscall, char *value) +{ + return new_element2(syscall, value, NULL); +} + +#ifdef __NR_stat64 +#define SCMP_stat SCMP_SYS(stat64) +#else +#define SCMP_stat SCMP_SYS(stat) +#endif + +int +sandbox_cfg_allow_stat_filename(sandbox_cfg_t **cfg, char *file) +{ + sandbox_cfg_t *elem = NULL; + + elem = new_element(SCMP_stat, file); + + elem->next = *cfg; + *cfg = elem; + + return 0; +} + +int +sandbox_cfg_allow_open_filename(sandbox_cfg_t **cfg, char *file) +{ + sandbox_cfg_t *elem = NULL; + + elem = new_element(SCMP_SYS(open), file); + + elem->next = *cfg; + *cfg = elem; + + return 0; +} + +int +sandbox_cfg_allow_chmod_filename(sandbox_cfg_t **cfg, char *file) +{ + sandbox_cfg_t *elem = NULL; + + elem = new_element(SCMP_SYS(chmod), file); + + elem->next = *cfg; + *cfg = elem; + + return 0; +} + +int +sandbox_cfg_allow_chown_filename(sandbox_cfg_t **cfg, char *file) +{ + sandbox_cfg_t *elem = NULL; + + elem = new_element(SCMP_SYS(chown), file); + + elem->next = *cfg; + *cfg = elem; + + return 0; +} + +int +sandbox_cfg_allow_rename(sandbox_cfg_t **cfg, char *file1, char *file2) +{ + sandbox_cfg_t *elem = NULL; + + elem = new_element2(SCMP_SYS(rename), file1, file2); + + elem->next = *cfg; + *cfg = elem; + + return 0; +} + +int +sandbox_cfg_allow_openat_filename(sandbox_cfg_t **cfg, char *file) +{ + sandbox_cfg_t *elem = NULL; + + elem = new_element(SCMP_SYS(openat), file); + + elem->next = *cfg; + *cfg = elem; + + return 0; +} + +/** + * Function responsible for going through the parameter syscall filters and + * call each function pointer in the list. + */ +static int +add_param_filter(scmp_filter_ctx ctx, sandbox_cfg_t* cfg) +{ + unsigned i; + int rc = 0; + + // function pointer + for (i = 0; i < ARRAY_LENGTH(filter_func); i++) { + rc = filter_func[i](ctx, cfg); + if (rc) { + log_err(LD_BUG,"(Sandbox) failed to add syscall %d, received libseccomp " + "error %d", i, rc); + return rc; + } + } + + return 0; +} + +/** + * Function responsible of loading the libseccomp syscall filters which do not + * have parameter filtering. + */ +static int +add_noparam_filter(scmp_filter_ctx ctx) +{ + unsigned i; + int rc = 0; + + // add general filters + for (i = 0; i < ARRAY_LENGTH(filter_nopar_gen); i++) { + rc = seccomp_rule_add_0(ctx, SCMP_ACT_ALLOW, filter_nopar_gen[i]); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add syscall index %d (NR=%d), " + "received libseccomp error %d", i, filter_nopar_gen[i], rc); + return rc; + } + } + + return 0; +} + +/** + * Function responsible for setting up and enabling a global syscall filter. + * The function is a prototype developed for stage 1 of sandboxing Tor. + * Returns 0 on success. + */ +static int +install_syscall_filter(sandbox_cfg_t* cfg) +{ + int rc = 0; + scmp_filter_ctx ctx; + + ctx = seccomp_init(SCMP_ACT_TRAP); + if (ctx == NULL) { + log_err(LD_BUG,"(Sandbox) failed to initialise libseccomp context"); + rc = -1; + goto end; + } + + // protectign sandbox parameter strings + if ((rc = prot_strings(ctx, cfg))) { + goto end; + } + + // add parameter filters + if ((rc = add_param_filter(ctx, cfg))) { + log_err(LD_BUG, "(Sandbox) failed to add param filters!"); + goto end; + } + + // adding filters with no parameters + if ((rc = add_noparam_filter(ctx))) { + log_err(LD_BUG, "(Sandbox) failed to add param filters!"); + goto end; + } + + // loading the seccomp2 filter + if ((rc = seccomp_load(ctx))) { + log_err(LD_BUG, "(Sandbox) failed to load: %d (%s)! " + "Are you sure that your kernel has seccomp2 support? The " + "sandbox won't work without it.", rc, + strerror(-rc)); + goto end; + } + + // marking the sandbox as active + sandbox_active = 1; + tor_make_getaddrinfo_cache_active(); + + end: + seccomp_release(ctx); + return (rc < 0 ? -rc : rc); +} + +#include "lib/sandbox/linux_syscalls.inc" + +static const char * +get_syscall_name(int syscall_num) +{ + int i; + for (i = 0; SYSCALLS_BY_NUMBER[i].syscall_name; ++i) { + if (SYSCALLS_BY_NUMBER[i].syscall_num == syscall_num) + return SYSCALLS_BY_NUMBER[i].syscall_name; + } + + { + static char syscall_name_buf[64]; + format_dec_number_sigsafe(syscall_num, + syscall_name_buf, sizeof(syscall_name_buf)); + return syscall_name_buf; + } +} + +#ifdef USE_BACKTRACE +#define MAX_DEPTH 256 +static void *syscall_cb_buf[MAX_DEPTH]; +#endif + +/** + * Function called when a SIGSYS is caught by the application. It notifies the + * user that an error has occurred and either terminates or allows the + * application to continue execution, based on the DEBUGGING_CLOSE symbol. + */ +static void +sigsys_debugging(int nr, siginfo_t *info, void *void_context) +{ + ucontext_t *ctx = (ucontext_t *) (void_context); + const char *syscall_name; + int syscall; +#ifdef USE_BACKTRACE + size_t depth; + int n_fds, i; + const int *fds = NULL; +#endif + + (void) nr; + + if (info->si_code != SYS_SECCOMP) + return; + + if (!ctx) + return; + + syscall = (int) ctx->uc_mcontext.M_SYSCALL; + +#ifdef USE_BACKTRACE + depth = backtrace(syscall_cb_buf, MAX_DEPTH); + /* Clean up the top stack frame so we get the real function + * name for the most recently failing function. */ + clean_backtrace(syscall_cb_buf, depth, ctx); +#endif /* defined(USE_BACKTRACE) */ + + syscall_name = get_syscall_name(syscall); + + tor_log_err_sigsafe("(Sandbox) Caught a bad syscall attempt (syscall ", + syscall_name, + ")\n", + NULL); + +#ifdef USE_BACKTRACE + n_fds = tor_log_get_sigsafe_err_fds(&fds); + for (i=0; i < n_fds; ++i) + backtrace_symbols_fd(syscall_cb_buf, (int)depth, fds[i]); +#endif + +#if defined(DEBUGGING_CLOSE) + _exit(1); // exit ok: programming error has led to sandbox failure. +#endif // DEBUGGING_CLOSE +} + +/** + * Function that adds a handler for SIGSYS, which is the signal thrown + * when the application is issuing a syscall which is not allowed. The + * main purpose of this function is to help with debugging by identifying + * filtered syscalls. + */ +static int +install_sigsys_debugging(void) +{ + struct sigaction act; + sigset_t mask; + + memset(&act, 0, sizeof(act)); + sigemptyset(&mask); + sigaddset(&mask, SIGSYS); + + act.sa_sigaction = &sigsys_debugging; + act.sa_flags = SA_SIGINFO; + if (sigaction(SIGSYS, &act, NULL) < 0) { + log_err(LD_BUG,"(Sandbox) Failed to register SIGSYS signal handler"); + return -1; + } + + if (sigprocmask(SIG_UNBLOCK, &mask, NULL)) { + log_err(LD_BUG,"(Sandbox) Failed call to sigprocmask()"); + return -2; + } + + return 0; +} + +/** + * Function responsible of registering the sandbox_cfg_t list of parameter + * syscall filters to the existing parameter list. This is used for incipient + * multiple-sandbox support. + */ +static int +register_cfg(sandbox_cfg_t* cfg) +{ + sandbox_cfg_t *elem = NULL; + + if (filter_dynamic == NULL) { + filter_dynamic = cfg; + return 0; + } + + for (elem = filter_dynamic; elem->next != NULL; elem = elem->next) + ; + + elem->next = cfg; + + return 0; +} + +#endif /* defined(USE_LIBSECCOMP) */ + +#ifdef USE_LIBSECCOMP +/** + * Initialises the syscall sandbox filter for any linux architecture, taking + * into account various available features for different linux flavours. + */ +static int +initialise_libseccomp_sandbox(sandbox_cfg_t* cfg) +{ + /* Prevent glibc from trying to open /dev/tty on fatal error */ + setenv("LIBC_FATAL_STDERR_", "1", 1); + + if (install_sigsys_debugging()) + return -1; + + if (install_syscall_filter(cfg)) + return -2; + + if (register_cfg(cfg)) + return -3; + + return 0; +} + +int +sandbox_is_active(void) +{ + return sandbox_active != 0; +} +#endif /* defined(USE_LIBSECCOMP) */ + +sandbox_cfg_t* +sandbox_cfg_new(void) +{ + return NULL; +} + +int +sandbox_init(sandbox_cfg_t *cfg) +{ +#if defined(USE_LIBSECCOMP) + return initialise_libseccomp_sandbox(cfg); + +#elif defined(__linux__) + (void)cfg; + log_warn(LD_GENERAL, + "This version of Tor was built without support for sandboxing. To " + "build with support for sandboxing on Linux, you must have " + "libseccomp and its necessary header files (e.g. seccomp.h)."); + return 0; + +#else + (void)cfg; + log_warn(LD_GENERAL, + "Currently, sandboxing is only implemented on Linux. The feature " + "is disabled on your platform."); + return 0; +#endif /* defined(USE_LIBSECCOMP) || ... */ +} + +#ifndef USE_LIBSECCOMP +int +sandbox_cfg_allow_open_filename(sandbox_cfg_t **cfg, char *file) +{ + (void)cfg; (void)file; + return 0; +} + +int +sandbox_cfg_allow_openat_filename(sandbox_cfg_t **cfg, char *file) +{ + (void)cfg; (void)file; + return 0; +} + +int +sandbox_cfg_allow_stat_filename(sandbox_cfg_t **cfg, char *file) +{ + (void)cfg; (void)file; + return 0; +} + +int +sandbox_cfg_allow_chown_filename(sandbox_cfg_t **cfg, char *file) +{ + (void)cfg; (void)file; + return 0; +} + +int +sandbox_cfg_allow_chmod_filename(sandbox_cfg_t **cfg, char *file) +{ + (void)cfg; (void)file; + return 0; +} + +int +sandbox_cfg_allow_rename(sandbox_cfg_t **cfg, char *file1, char *file2) +{ + (void)cfg; (void)file1; (void)file2; + return 0; +} + +int +sandbox_is_active(void) +{ + return 0; +} + +void +sandbox_disable_getaddrinfo_cache(void) +{ +} + +#endif /* !defined(USE_LIBSECCOMP) */ diff --git a/src/lib/sandbox/sandbox.h b/src/lib/sandbox/sandbox.h new file mode 100644 index 0000000000..5bec09a36a --- /dev/null +++ b/src/lib/sandbox/sandbox.h @@ -0,0 +1,150 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file sandbox.h + * \brief Header file for sandbox.c. + **/ + +#ifndef SANDBOX_H_ +#define SANDBOX_H_ + +#include "orconfig.h" +#include "lib/cc/torint.h" + +#ifndef SYS_SECCOMP + +/** + * Used by SIGSYS signal handler to check if the signal was issued due to a + * seccomp2 filter violation. + */ +#define SYS_SECCOMP 1 + +#endif /* !defined(SYS_SECCOMP) */ + +#if defined(HAVE_SECCOMP_H) && defined(__linux__) +#define USE_LIBSECCOMP +#endif + +struct sandbox_cfg_elem; + +/** Typedef to structure used to manage a sandbox configuration. */ +typedef struct sandbox_cfg_elem sandbox_cfg_t; + +/** + * Linux definitions + */ +#ifdef USE_LIBSECCOMP + +#include <sys/ucontext.h> +#include <seccomp.h> +#include <netdb.h> + +#define PARAM_PTR 0 +#define PARAM_NUM 1 + +/** + * Enum used to manage the type of the implementation for general purpose. + */ +typedef enum { + /** Libseccomp implementation based on seccomp2*/ + LIBSECCOMP2 = 0 +} SB_IMPL; + +/** + * Configuration parameter structure associated with the LIBSECCOMP2 + * implementation. + */ +typedef struct smp_param { + /** syscall associated with parameter. */ + int syscall; + + /** parameter value. */ + char *value; + /** parameter value, second argument. */ + char *value2; + + /** parameter flag (0 = not protected, 1 = protected). */ + int prot; +} smp_param_t; + +/** + * Structure used to manage a sandbox configuration. + * + * It is implemented as a linked list of parameters. Currently only controls + * parameters for open, openat, execve, stat64. + */ +struct sandbox_cfg_elem { + /** Sandbox implementation which dictates the parameter type. */ + SB_IMPL implem; + + /** Configuration parameter. */ + smp_param_t *param; + + /** Next element of the configuration*/ + struct sandbox_cfg_elem *next; +}; + +/** Function pointer defining the prototype of a filter function.*/ +typedef int (*sandbox_filter_func_t)(scmp_filter_ctx ctx, + sandbox_cfg_t *filter); + +/** Type that will be used in step 3 in order to manage multiple sandboxes.*/ +typedef struct { + /** function pointers associated with the filter */ + sandbox_filter_func_t *filter_func; + + /** filter function pointer parameters */ + sandbox_cfg_t *filter_dynamic; +} sandbox_t; + +#endif /* defined(USE_LIBSECCOMP) */ + +#ifdef USE_LIBSECCOMP +/** Returns a registered protected string used with the sandbox, given that + * it matches the parameter. + */ +const char* sandbox_intern_string(const char *param); +#else /* !(defined(USE_LIBSECCOMP)) */ +#define sandbox_intern_string(s) (s) +#endif /* defined(USE_LIBSECCOMP) */ + +/** Creates an empty sandbox configuration file.*/ +sandbox_cfg_t * sandbox_cfg_new(void); + +/** + * Function used to add a open allowed filename to a supplied configuration. + * The (char*) specifies the path to the allowed file; we take ownership + * of the pointer. + */ +int sandbox_cfg_allow_open_filename(sandbox_cfg_t **cfg, char *file); + +int sandbox_cfg_allow_chmod_filename(sandbox_cfg_t **cfg, char *file); +int sandbox_cfg_allow_chown_filename(sandbox_cfg_t **cfg, char *file); + +/* DOCDOC */ +int sandbox_cfg_allow_rename(sandbox_cfg_t **cfg, char *file1, char *file2); + +/** + * Function used to add a openat allowed filename to a supplied configuration. + * The (char*) specifies the path to the allowed file; we steal the pointer to + * that file. + */ +int sandbox_cfg_allow_openat_filename(sandbox_cfg_t **cfg, char *file); + +/** + * Function used to add a stat/stat64 allowed filename to a configuration. + * The (char*) specifies the path to the allowed file; that pointer is stolen. + */ +int sandbox_cfg_allow_stat_filename(sandbox_cfg_t **cfg, char *file); + +/** Function used to initialise a sandbox configuration.*/ +int sandbox_init(sandbox_cfg_t* cfg); + +/** Return true iff the sandbox is turned on. */ +int sandbox_is_active(void); + +#endif /* !defined(SANDBOX_H_) */ diff --git a/src/lib/smartlist_core/.may_include b/src/lib/smartlist_core/.may_include new file mode 100644 index 0000000000..a8507761a4 --- /dev/null +++ b/src/lib/smartlist_core/.may_include @@ -0,0 +1,7 @@ +orconfig.h +lib/cc/*.h +lib/malloc/*.h +lib/err/*.h +lib/string/*.h +lib/smartlist_core/*.h +lib/testsupport/testsupport.h diff --git a/src/lib/smartlist_core/include.am b/src/lib/smartlist_core/include.am new file mode 100644 index 0000000000..99d65f0b23 --- /dev/null +++ b/src/lib/smartlist_core/include.am @@ -0,0 +1,21 @@ + +noinst_LIBRARIES += src/lib/libtor-smartlist-core.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-smartlist-core-testing.a +endif + +src_lib_libtor_smartlist_core_a_SOURCES = \ + src/lib/smartlist_core/smartlist_core.c \ + src/lib/smartlist_core/smartlist_split.c + +src_lib_libtor_smartlist_core_testing_a_SOURCES = \ + $(src_lib_libtor_smartlist_core_a_SOURCES) +src_lib_libtor_smartlist_core_testing_a_CPPFLAGS = \ + $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_smartlist_core_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/smartlist_core/smartlist_core.h \ + src/lib/smartlist_core/smartlist_foreach.h \ + src/lib/smartlist_core/smartlist_split.h diff --git a/src/lib/smartlist_core/smartlist_core.c b/src/lib/smartlist_core/smartlist_core.c new file mode 100644 index 0000000000..ac85a6cc84 --- /dev/null +++ b/src/lib/smartlist_core/smartlist_core.c @@ -0,0 +1,234 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file smartlist_core.c + * \brief Implements the core functionality of a smartlist (a resizeable + * dynamic array). For more functionality and helper functions, see the + * container library. + **/ + +#include "lib/err/torerr.h" +#include "lib/malloc/malloc.h" +#include "lib/smartlist_core/smartlist_core.h" + +#include <stdlib.h> +#include <string.h> + +/** All newly allocated smartlists have this capacity. */ +#define SMARTLIST_DEFAULT_CAPACITY 16 + +/** Allocate and return an empty smartlist. + */ +MOCK_IMPL(smartlist_t *, +smartlist_new,(void)) +{ + smartlist_t *sl = tor_malloc(sizeof(smartlist_t)); + sl->num_used = 0; + sl->capacity = SMARTLIST_DEFAULT_CAPACITY; + sl->list = tor_calloc(sizeof(void *), sl->capacity); + return sl; +} + +/** Deallocate a smartlist. Does not release storage associated with the + * list's elements. + */ +MOCK_IMPL(void, +smartlist_free_,(smartlist_t *sl)) +{ + if (!sl) + return; + tor_free(sl->list); + tor_free(sl); +} + +/** Remove all elements from the list. + */ +void +smartlist_clear(smartlist_t *sl) +{ + memset(sl->list, 0, sizeof(void *) * sl->num_used); + sl->num_used = 0; +} + +#if SIZE_MAX < INT_MAX +#error "We don't support systems where size_t is smaller than int." +#endif + +/** Make sure that <b>sl</b> can hold at least <b>size</b> entries. */ +static inline void +smartlist_ensure_capacity(smartlist_t *sl, size_t size) +{ + /* Set MAX_CAPACITY to MIN(INT_MAX, SIZE_MAX / sizeof(void*)) */ +#if (SIZE_MAX/SIZEOF_VOID_P) > INT_MAX +#define MAX_CAPACITY (INT_MAX) +#else +#define MAX_CAPACITY (int)((SIZE_MAX / (sizeof(void*)))) +#endif + + raw_assert(size <= MAX_CAPACITY); + + if (size > (size_t) sl->capacity) { + size_t higher = (size_t) sl->capacity; + if (PREDICT_UNLIKELY(size > MAX_CAPACITY/2)) { + higher = MAX_CAPACITY; + } else { + while (size > higher) + higher *= 2; + } + sl->list = tor_reallocarray(sl->list, sizeof(void *), + ((size_t)higher)); + memset(sl->list + sl->capacity, 0, + sizeof(void *) * (higher - sl->capacity)); + sl->capacity = (int) higher; + } +#undef ASSERT_CAPACITY +#undef MAX_CAPACITY +} + +/** Append element to the end of the list. */ +void +smartlist_add(smartlist_t *sl, void *element) +{ + smartlist_ensure_capacity(sl, ((size_t) sl->num_used)+1); + sl->list[sl->num_used++] = element; +} + +/** Append each element from S2 to the end of S1. */ +void +smartlist_add_all(smartlist_t *s1, const smartlist_t *s2) +{ + size_t new_size = (size_t)s1->num_used + (size_t)s2->num_used; + raw_assert(new_size >= (size_t) s1->num_used); /* check for overflow. */ + smartlist_ensure_capacity(s1, new_size); + memcpy(s1->list + s1->num_used, s2->list, s2->num_used*sizeof(void*)); + raw_assert(new_size <= INT_MAX); /* redundant. */ + s1->num_used = (int) new_size; +} + +/** Append a copy of string to sl */ +void +smartlist_add_strdup(struct smartlist_t *sl, const char *string) +{ + char *copy; + + copy = tor_strdup(string); + + smartlist_add(sl, copy); +} + +/** Remove all elements E from sl such that E==element. Preserve + * the order of any elements before E, but elements after E can be + * rearranged. + */ +void +smartlist_remove(smartlist_t *sl, const void *element) +{ + int i; + if (element == NULL) + return; + for (i=0; i < sl->num_used; i++) + if (sl->list[i] == element) { + sl->list[i] = sl->list[--sl->num_used]; /* swap with the end */ + i--; /* so we process the new i'th element */ + sl->list[sl->num_used] = NULL; + } +} + +/** As <b>smartlist_remove</b>, but do not change the order of + * any elements not removed */ +void +smartlist_remove_keeporder(smartlist_t *sl, const void *element) +{ + int i, j, num_used_orig = sl->num_used; + if (element == NULL) + return; + + for (i=j=0; j < num_used_orig; ++j) { + if (sl->list[j] == element) { + --sl->num_used; + } else { + sl->list[i++] = sl->list[j]; + } + } +} + +/** If <b>sl</b> is nonempty, remove and return the final element. Otherwise, + * return NULL. */ +void * +smartlist_pop_last(smartlist_t *sl) +{ + raw_assert(sl); + if (sl->num_used) { + void *tmp = sl->list[--sl->num_used]; + sl->list[sl->num_used] = NULL; + return tmp; + } else + return NULL; +} + +/** Return true iff some element E of sl has E==element. + */ +int +smartlist_contains(const smartlist_t *sl, const void *element) +{ + int i; + for (i=0; i < sl->num_used; i++) + if (sl->list[i] == element) + return 1; + return 0; +} + +/** Remove the <b>idx</b>th element of sl; if idx is not the last + * element, swap the last element of sl into the <b>idx</b>th space. + */ +void +smartlist_del(smartlist_t *sl, int idx) +{ + raw_assert(sl); + raw_assert(idx>=0); + raw_assert(idx < sl->num_used); + sl->list[idx] = sl->list[--sl->num_used]; + sl->list[sl->num_used] = NULL; +} + +/** Remove the <b>idx</b>th element of sl; if idx is not the last element, + * moving all subsequent elements back one space. Return the old value + * of the <b>idx</b>th element. + */ +void +smartlist_del_keeporder(smartlist_t *sl, int idx) +{ + raw_assert(sl); + raw_assert(idx>=0); + raw_assert(idx < sl->num_used); + --sl->num_used; + if (idx < sl->num_used) + memmove(sl->list+idx, sl->list+idx+1, sizeof(void*)*(sl->num_used-idx)); + sl->list[sl->num_used] = NULL; +} + +/** Insert the value <b>val</b> as the new <b>idx</b>th element of + * <b>sl</b>, moving all items previously at <b>idx</b> or later + * forward one space. + */ +void +smartlist_insert(smartlist_t *sl, int idx, void *val) +{ + raw_assert(sl); + raw_assert(idx>=0); + raw_assert(idx <= sl->num_used); + if (idx == sl->num_used) { + smartlist_add(sl, val); + } else { + smartlist_ensure_capacity(sl, ((size_t) sl->num_used)+1); + /* Move other elements away */ + if (idx < sl->num_used) + memmove(sl->list + idx + 1, sl->list + idx, + sizeof(void*)*(sl->num_used-idx)); + sl->num_used++; + sl->list[idx] = val; + } +} diff --git a/src/lib/smartlist_core/smartlist_core.h b/src/lib/smartlist_core/smartlist_core.h new file mode 100644 index 0000000000..a7fbaa099b --- /dev/null +++ b/src/lib/smartlist_core/smartlist_core.h @@ -0,0 +1,100 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file smartlist_core.h + * \brief Top-level declarations for the smartlist_t dynamic array type. + **/ + +#ifndef TOR_SMARTLIST_CORE_H +#define TOR_SMARTLIST_CORE_H + +#include <stddef.h> + +#include "lib/cc/compat_compiler.h" +#include "lib/cc/torint.h" +#include "lib/testsupport/testsupport.h" + +/** A resizeable list of pointers, with associated helpful functionality. + * + * The members of this struct are exposed only so that macros and inlines can + * use them; all access to smartlist internals should go through the functions + * and macros defined here. + **/ +typedef struct smartlist_t { + /** @{ */ + /** <b>list</b> has enough capacity to store exactly <b>capacity</b> elements + * before it needs to be resized. Only the first <b>num_used</b> (\<= + * capacity) elements point to valid data. + */ + void **list; + int num_used; + int capacity; + /** @} */ +} smartlist_t; + +MOCK_DECL(smartlist_t *, smartlist_new, (void)); +MOCK_DECL(void, smartlist_free_, (smartlist_t *sl)); +#define smartlist_free(sl) FREE_AND_NULL(smartlist_t, smartlist_free_, (sl)) + +void smartlist_clear(smartlist_t *sl); +void smartlist_add(smartlist_t *sl, void *element); +void smartlist_add_all(smartlist_t *sl, const smartlist_t *s2); +void smartlist_add_strdup(struct smartlist_t *sl, const char *string); + +void smartlist_remove(smartlist_t *sl, const void *element); +void smartlist_remove_keeporder(smartlist_t *sl, const void *element); +void *smartlist_pop_last(smartlist_t *sl); + +int smartlist_contains(const smartlist_t *sl, const void *element); + +/* smartlist_choose() is defined in crypto.[ch] */ +#ifdef DEBUG_SMARTLIST +#include "lib/err/torerr.h" +#include <stdlib.h> +/** Return the number of items in sl. + */ +static inline int smartlist_len(const smartlist_t *sl); +static inline int smartlist_len(const smartlist_t *sl) { + raw_assert(sl); + return (sl)->num_used; +} +/** Return the <b>idx</b>th element of sl. + */ +static inline void *smartlist_get(const smartlist_t *sl, int idx); +static inline void *smartlist_get(const smartlist_t *sl, int idx) { + raw_assert(sl); + raw_assert(idx>=0); + raw_assert(sl->num_used > idx); + return sl->list[idx]; +} +static inline void smartlist_set(smartlist_t *sl, int idx, void *val) { + raw_assert(sl); + raw_assert(idx>=0); + raw_assert(sl->num_used > idx); + sl->list[idx] = val; +} +#else /* !(defined(DEBUG_SMARTLIST)) */ +#define smartlist_len(sl) ((sl)->num_used) +#define smartlist_get(sl, idx) ((sl)->list[idx]) +#define smartlist_set(sl, idx, val) ((sl)->list[idx] = (val)) +#endif /* defined(DEBUG_SMARTLIST) */ + +/** Exchange the elements at indices <b>idx1</b> and <b>idx2</b> of the + * smartlist <b>sl</b>. */ +static inline void smartlist_swap(smartlist_t *sl, int idx1, int idx2) +{ + if (idx1 != idx2) { + void *elt = smartlist_get(sl, idx1); + smartlist_set(sl, idx1, smartlist_get(sl, idx2)); + smartlist_set(sl, idx2, elt); + } +} + +void smartlist_del(smartlist_t *sl, int idx); +void smartlist_del_keeporder(smartlist_t *sl, int idx); +void smartlist_insert(smartlist_t *sl, int idx, void *val); + +#endif /* !defined(TOR_CONTAINER_H) */ diff --git a/src/lib/smartlist_core/smartlist_foreach.h b/src/lib/smartlist_core/smartlist_foreach.h new file mode 100644 index 0000000000..0f6fe30074 --- /dev/null +++ b/src/lib/smartlist_core/smartlist_foreach.h @@ -0,0 +1,133 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file smartlist_foreach.h + * \brief Macros for iterating over the elements of a smartlist_t. + **/ + +#ifndef TOR_SMARTLIST_FOREACH_H +#define TOR_SMARTLIST_FOREACH_H + +/** Iterate over the items in a smartlist <b>sl</b>, in order. For each item, + * assign it to a new local variable of type <b>type</b> named <b>var</b>, and + * execute the statements inside the loop body. Inside the loop, the loop + * index can be accessed as <b>var</b>_sl_idx and the length of the list can + * be accessed as <b>var</b>_sl_len. + * + * NOTE: Do not change the length of the list while the loop is in progress, + * unless you adjust the _sl_len variable correspondingly. See second example + * below. + * + * Example use: + * <pre> + * smartlist_t *list = smartlist_split("A:B:C", ":", 0, 0); + * SMARTLIST_FOREACH_BEGIN(list, char *, cp) { + * printf("%d: %s\n", cp_sl_idx, cp); + * tor_free(cp); + * } SMARTLIST_FOREACH_END(cp); + * smartlist_free(list); + * </pre> + * + * Example use (advanced): + * <pre> + * SMARTLIST_FOREACH_BEGIN(list, char *, cp) { + * if (!strcmp(cp, "junk")) { + * tor_free(cp); + * SMARTLIST_DEL_CURRENT(list, cp); + * } + * } SMARTLIST_FOREACH_END(cp); + * </pre> + */ +/* Note: these macros use token pasting, and reach into smartlist internals. + * This can make them a little daunting. Here's the approximate unpacking of + * the above examples, for entertainment value: + * + * <pre> + * smartlist_t *list = smartlist_split("A:B:C", ":", 0, 0); + * { + * int cp_sl_idx, cp_sl_len = smartlist_len(list); + * char *cp; + * for (cp_sl_idx = 0; cp_sl_idx < cp_sl_len; ++cp_sl_idx) { + * cp = smartlist_get(list, cp_sl_idx); + * printf("%d: %s\n", cp_sl_idx, cp); + * tor_free(cp); + * } + * } + * smartlist_free(list); + * </pre> + * + * <pre> + * { + * int cp_sl_idx, cp_sl_len = smartlist_len(list); + * char *cp; + * for (cp_sl_idx = 0; cp_sl_idx < cp_sl_len; ++cp_sl_idx) { + * cp = smartlist_get(list, cp_sl_idx); + * if (!strcmp(cp, "junk")) { + * tor_free(cp); + * smartlist_del(list, cp_sl_idx); + * --cp_sl_idx; + * --cp_sl_len; + * } + * } + * } + * </pre> + */ +#define SMARTLIST_FOREACH_BEGIN(sl, type, var) \ + STMT_BEGIN \ + int var ## _sl_idx, var ## _sl_len=(sl)->num_used; \ + type var; \ + for (var ## _sl_idx = 0; var ## _sl_idx < var ## _sl_len; \ + ++var ## _sl_idx) { \ + var = (sl)->list[var ## _sl_idx]; + +#define SMARTLIST_FOREACH_END(var) \ + var = NULL; \ + (void) var ## _sl_idx; \ + } STMT_END + +/** + * An alias for SMARTLIST_FOREACH_BEGIN and SMARTLIST_FOREACH_END, using + * <b>cmd</b> as the loop body. This wrapper is here for convenience with + * very short loops. + * + * By convention, we do not use this for loops which nest, or for loops over + * 10 lines or so. Use SMARTLIST_FOREACH_{BEGIN,END} for those. + */ +#define SMARTLIST_FOREACH(sl, type, var, cmd) \ + SMARTLIST_FOREACH_BEGIN(sl,type,var) { \ + cmd; \ + } SMARTLIST_FOREACH_END(var) + +/** Helper: While in a SMARTLIST_FOREACH loop over the list <b>sl</b> indexed + * with the variable <b>var</b>, remove the current element in a way that + * won't confuse the loop. */ +#define SMARTLIST_DEL_CURRENT(sl, var) \ + STMT_BEGIN \ + smartlist_del(sl, var ## _sl_idx); \ + --var ## _sl_idx; \ + --var ## _sl_len; \ + STMT_END + +/** Helper: While in a SMARTLIST_FOREACH loop over the list <b>sl</b> indexed + * with the variable <b>var</b>, remove the current element in a way that + * won't confuse the loop. */ +#define SMARTLIST_DEL_CURRENT_KEEPORDER(sl, var) \ + STMT_BEGIN \ + smartlist_del_keeporder(sl, var ## _sl_idx); \ + --var ## _sl_idx; \ + --var ## _sl_len; \ + STMT_END + +/** Helper: While in a SMARTLIST_FOREACH loop over the list <b>sl</b> indexed + * with the variable <b>var</b>, replace the current element with <b>val</b>. + * Does not deallocate the current value of <b>var</b>. + */ +#define SMARTLIST_REPLACE_CURRENT(sl, var, val) \ + STMT_BEGIN \ + smartlist_set(sl, var ## _sl_idx, val); \ + STMT_END + +#endif /* !defined(TOR_SMARTLIST_FOREACH_H) */ diff --git a/src/lib/smartlist_core/smartlist_split.c b/src/lib/smartlist_core/smartlist_split.c new file mode 100644 index 0000000000..c9cf59851f --- /dev/null +++ b/src/lib/smartlist_core/smartlist_split.c @@ -0,0 +1,92 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file smartlist_split.c + * \brief Split a string into a smartlist_t of substrings. + **/ + +#include "lib/smartlist_core/smartlist_core.h" +#include "lib/smartlist_core/smartlist_split.h" + +#include "lib/err/torerr.h" +#include "lib/string/util_string.h" +#include "lib/string/compat_ctype.h" +#include "lib/malloc/malloc.h" + +#include <string.h> + +/** + * Split a string <b>str</b> along all occurrences of <b>sep</b>, + * appending the (newly allocated) split strings, in order, to + * <b>sl</b>. Return the number of strings added to <b>sl</b>. + * + * If <b>flags</b>&SPLIT_SKIP_SPACE is true, remove initial and + * trailing space from each entry. + * If <b>flags</b>&SPLIT_IGNORE_BLANK is true, remove any entries + * of length 0. + * If <b>flags</b>&SPLIT_STRIP_SPACE is true, strip spaces from each + * split string. + * + * If <b>max</b>\>0, divide the string into no more than <b>max</b> pieces. If + * <b>sep</b> is NULL, split on any sequence of horizontal space. + */ +int +smartlist_split_string(smartlist_t *sl, const char *str, const char *sep, + int flags, int max) +{ + const char *cp, *end, *next; + int n = 0; + + raw_assert(sl); + raw_assert(str); + + cp = str; + while (1) { + if (flags&SPLIT_SKIP_SPACE) { + while (TOR_ISSPACE(*cp)) ++cp; + } + + if (max>0 && n == max-1) { + end = strchr(cp,'\0'); + } else if (sep) { + end = strstr(cp,sep); + if (!end) + end = strchr(cp,'\0'); + } else { + for (end = cp; *end && *end != '\t' && *end != ' '; ++end) + ; + } + + raw_assert(end); + + if (!*end) { + next = NULL; + } else if (sep) { + next = end+strlen(sep); + } else { + next = end+1; + while (*next == '\t' || *next == ' ') + ++next; + } + + if (flags&SPLIT_SKIP_SPACE) { + while (end > cp && TOR_ISSPACE(*(end-1))) + --end; + } + if (end != cp || !(flags&SPLIT_IGNORE_BLANK)) { + char *string = tor_strndup(cp, end-cp); + if (flags&SPLIT_STRIP_SPACE) + tor_strstrip(string, " "); + smartlist_add(sl, string); + ++n; + } + if (!next) + break; + cp = next; + } + + return n; +} diff --git a/src/lib/smartlist_core/smartlist_split.h b/src/lib/smartlist_core/smartlist_split.h new file mode 100644 index 0000000000..4f72376125 --- /dev/null +++ b/src/lib/smartlist_core/smartlist_split.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file smartlist_split.h + * \brief Header for smartlist_split.c + **/ + +#ifndef TOR_SMARTLIST_SPLIT_H +#define TOR_SMARTLIST_SPLIT_H + +#define SPLIT_SKIP_SPACE 0x01 +#define SPLIT_IGNORE_BLANK 0x02 +#define SPLIT_STRIP_SPACE 0x04 +int smartlist_split_string(smartlist_t *sl, const char *str, const char *sep, + int flags, int max); + +#endif diff --git a/src/lib/string/.may_include b/src/lib/string/.may_include new file mode 100644 index 0000000000..ec5c769831 --- /dev/null +++ b/src/lib/string/.may_include @@ -0,0 +1,10 @@ +orconfig.h +lib/cc/*.h +lib/defs/*.h +lib/err/*.h +lib/malloc/*.h +lib/ctime/*.h +lib/string/*.h + +strlcat.c +strlcpy.c diff --git a/src/lib/string/compat_ctype.c b/src/lib/string/compat_ctype.c new file mode 100644 index 0000000000..f5d82be3ae --- /dev/null +++ b/src/lib/string/compat_ctype.c @@ -0,0 +1,72 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_ctype.c + * \brief Locale-independent character-type inspection (backend) + **/ + +#include "lib/string/compat_ctype.h" + +/** + * Tables to implement ctypes-replacement TOR_IS*() functions. Each table + * has 256 bits to look up whether a character is in some set or not. This + * fails on non-ASCII platforms, but it is hard to find a platform whose + * character set is not a superset of ASCII nowadays. */ + +/**@{*/ +const uint32_t TOR_ISALPHA_TABLE[8] = + { 0, 0, 0x7fffffe, 0x7fffffe, 0, 0, 0, 0 }; +const uint32_t TOR_ISALNUM_TABLE[8] = + { 0, 0x3ff0000, 0x7fffffe, 0x7fffffe, 0, 0, 0, 0 }; +const uint32_t TOR_ISSPACE_TABLE[8] = { 0x3e00, 0x1, 0, 0, 0, 0, 0, 0 }; +const uint32_t TOR_ISXDIGIT_TABLE[8] = + { 0, 0x3ff0000, 0x7e, 0x7e, 0, 0, 0, 0 }; +const uint32_t TOR_ISDIGIT_TABLE[8] = { 0, 0x3ff0000, 0, 0, 0, 0, 0, 0 }; +const uint32_t TOR_ISPRINT_TABLE[8] = + { 0, 0xffffffff, 0xffffffff, 0x7fffffff, 0, 0, 0, 0x0 }; +const uint32_t TOR_ISUPPER_TABLE[8] = { 0, 0, 0x7fffffe, 0, 0, 0, 0, 0 }; +const uint32_t TOR_ISLOWER_TABLE[8] = { 0, 0, 0, 0x7fffffe, 0, 0, 0, 0 }; + +/** Upper-casing and lowercasing tables to map characters to upper/lowercase + * equivalents. Used by tor_toupper() and tor_tolower(). */ +/**@{*/ +const uint8_t TOR_TOUPPER_TABLE[256] = { + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, + 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, + 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, + 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, + 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95, + 96,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, + 80,81,82,83,84,85,86,87,88,89,90,123,124,125,126,127, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255, +}; +const uint8_t TOR_TOLOWER_TABLE[256] = { + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, + 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, + 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, + 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, + 64,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122,91,92,93,94,95, + 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255, +}; +/**@}*/ diff --git a/src/lib/string/compat_ctype.h b/src/lib/string/compat_ctype.h new file mode 100644 index 0000000000..dbddd356c1 --- /dev/null +++ b/src/lib/string/compat_ctype.h @@ -0,0 +1,67 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_ctype.h + * \brief Locale-independent character-type inspection (header) + **/ + +#ifndef TOR_COMPAT_CTYPE_H +#define TOR_COMPAT_CTYPE_H + +#include "orconfig.h" +#include "lib/cc/torint.h" + +/* Much of the time when we're checking ctypes, we're doing spec compliance, + * which all assumes we're doing ASCII. */ +#define DECLARE_CTYPE_FN(name) \ + static int TOR_##name(char c); \ + extern const uint32_t TOR_##name##_TABLE[]; \ + static inline int TOR_##name(char c) { \ + uint8_t u = c; \ + return !!(TOR_##name##_TABLE[(u >> 5) & 7] & (1u << (u & 31))); \ + } +DECLARE_CTYPE_FN(ISALPHA) +DECLARE_CTYPE_FN(ISALNUM) +DECLARE_CTYPE_FN(ISSPACE) +DECLARE_CTYPE_FN(ISDIGIT) +DECLARE_CTYPE_FN(ISXDIGIT) +DECLARE_CTYPE_FN(ISPRINT) +DECLARE_CTYPE_FN(ISLOWER) +DECLARE_CTYPE_FN(ISUPPER) +extern const uint8_t TOR_TOUPPER_TABLE[]; +extern const uint8_t TOR_TOLOWER_TABLE[]; +#define TOR_TOLOWER(c) (TOR_TOLOWER_TABLE[(uint8_t)c]) +#define TOR_TOUPPER(c) (TOR_TOUPPER_TABLE[(uint8_t)c]) + +static inline int hex_decode_digit(char c); + +/** Helper: given a hex digit, return its value, or -1 if it isn't hex. */ +static inline int +hex_decode_digit(char c) +{ + switch (c) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'A': case 'a': return 10; + case 'B': case 'b': return 11; + case 'C': case 'c': return 12; + case 'D': case 'd': return 13; + case 'E': case 'e': return 14; + case 'F': case 'f': return 15; + default: + return -1; + } +} + +#endif /* !defined(TOR_COMPAT_CTYPE_H) */ diff --git a/src/lib/string/compat_string.c b/src/lib/string/compat_string.c new file mode 100644 index 0000000000..e125c921a4 --- /dev/null +++ b/src/lib/string/compat_string.c @@ -0,0 +1,74 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_string.c + * \brief Useful string-processing functions that some platforms don't + * provide. + **/ + +#include "lib/string/compat_string.h" +#include "lib/err/torerr.h" + +/* Inline the strl functions if the platform doesn't have them. */ +#ifndef HAVE_STRLCPY +#include "strlcpy.c" +#endif +#ifndef HAVE_STRLCAT +#include "strlcat.c" +#endif + +#include <stdlib.h> +#include <string.h> + +/** Helper for tor_strtok_r_impl: Advances cp past all characters in + * <b>sep</b>, and returns its new value. */ +static char * +strtok_helper(char *cp, const char *sep) +{ + if (sep[1]) { + while (*cp && strchr(sep, *cp)) + ++cp; + } else { + while (*cp && *cp == *sep) + ++cp; + } + return cp; +} + +/** Implementation of strtok_r for platforms whose coders haven't figured out + * how to write one. Hey, retrograde libc developers! You can use this code + * here for free! */ +char * +tor_strtok_r_impl(char *str, const char *sep, char **lasts) +{ + char *cp, *start; + raw_assert(*sep); + if (str) { + str = strtok_helper(str, sep); + if (!*str) + return NULL; + start = cp = *lasts = str; + } else if (!*lasts || !**lasts) { + return NULL; + } else { + start = cp = *lasts; + } + + if (sep[1]) { + while (*cp && !strchr(sep, *cp)) + ++cp; + } else { + cp = strchr(cp, *sep); + } + + if (!cp || !*cp) { + *lasts = NULL; + } else { + *cp++ = '\0'; + *lasts = strtok_helper(cp, sep); + } + return start; +} diff --git a/src/lib/string/compat_string.h b/src/lib/string/compat_string.h new file mode 100644 index 0000000000..a0e37bb6dc --- /dev/null +++ b/src/lib/string/compat_string.h @@ -0,0 +1,62 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_string.h + * \brief Header for compat_string.c + **/ + +#ifndef TOR_COMPAT_STRING_H +#define TOR_COMPAT_STRING_H + +#include "orconfig.h" +#include "lib/cc/compat_compiler.h" + +#include <stddef.h> + +/* ===== String compatibility */ +#ifdef _WIN32 +/* Windows doesn't have str(n)casecmp, but mingw defines it: only define it + * ourselves if it's missing. */ +#ifndef HAVE_STRNCASECMP +static inline int strncasecmp(const char *a, const char *b, size_t n); +static inline int strncasecmp(const char *a, const char *b, size_t n) { + return _strnicmp(a,b,n); +} +#endif +#ifndef HAVE_STRCASECMP +static inline int strcasecmp(const char *a, const char *b); +static inline int strcasecmp(const char *a, const char *b) { + return _stricmp(a,b); +} +#endif +#endif + +#if defined __APPLE__ +/* On OSX 10.9 and later, the overlap-checking code for strlcat would + * appear to have a severe bug that can sometimes cause aborts in Tor. + * Instead, use the non-checking variants. This is sad. + * + * See https://trac.torproject.org/projects/tor/ticket/15205 + */ +#undef strlcat +#undef strlcpy +#endif /* defined __APPLE__ */ + +#ifndef HAVE_STRLCAT +size_t strlcat(char *dst, const char *src, size_t siz); +#endif +#ifndef HAVE_STRLCPY +size_t strlcpy(char *dst, const char *src, size_t siz); +#endif + +char *tor_strtok_r_impl(char *str, const char *sep, char **lasts); +#ifdef HAVE_STRTOK_R +#define tor_strtok_r(str, sep, lasts) strtok_r(str, sep, lasts) +#else +#define tor_strtok_r(str, sep, lasts) tor_strtok_r_impl(str, sep, lasts) +#endif + +#endif diff --git a/src/lib/string/include.am b/src/lib/string/include.am new file mode 100644 index 0000000000..edd74b8a3e --- /dev/null +++ b/src/lib/string/include.am @@ -0,0 +1,27 @@ + +noinst_LIBRARIES += src/lib/libtor-string.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-string-testing.a +endif + +src_lib_libtor_string_a_SOURCES = \ + src/lib/string/compat_ctype.c \ + src/lib/string/compat_string.c \ + src/lib/string/util_string.c \ + src/lib/string/parse_int.c \ + src/lib/string/printf.c \ + src/lib/string/scanf.c + +src_lib_libtor_string_testing_a_SOURCES = \ + $(src_lib_libtor_string_a_SOURCES) +src_lib_libtor_string_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_string_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/string/compat_ctype.h \ + src/lib/string/compat_string.h \ + src/lib/string/util_string.h \ + src/lib/string/parse_int.h \ + src/lib/string/printf.h \ + src/lib/string/scanf.h diff --git a/src/lib/string/parse_int.c b/src/lib/string/parse_int.c new file mode 100644 index 0000000000..fbdd554a47 --- /dev/null +++ b/src/lib/string/parse_int.c @@ -0,0 +1,131 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file parse_int.c + * \brief Convert strings into the integers they encode, with bounds checking. + **/ + +#include "lib/string/parse_int.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +/* Helper: common code to check whether the result of a strtol or strtoul or + * strtoll is correct. */ +#define CHECK_STRTOX_RESULT() \ + /* Did an overflow occur? */ \ + if (errno == ERANGE) \ + goto err; \ + /* Was at least one character converted? */ \ + if (endptr == s) \ + goto err; \ + /* Were there unexpected unconverted characters? */ \ + if (!next && *endptr) \ + goto err; \ + /* Illogical (max, min) inputs? */ \ + if (max < min) \ + goto err; \ + /* Is r within limits? */ \ + if (r < min || r > max) \ + goto err; \ + if (ok) *ok = 1; \ + if (next) *next = endptr; \ + return r; \ + err: \ + if (ok) *ok = 0; \ + if (next) *next = endptr; \ + return 0 + +/** Extract a long from the start of <b>s</b>, in the given numeric + * <b>base</b>. If <b>base</b> is 0, <b>s</b> is parsed as a decimal, + * octal, or hex number in the syntax of a C integer literal. If + * there is unconverted data and <b>next</b> is provided, set + * *<b>next</b> to the first unconverted character. An error has + * occurred if no characters are converted; or if there are + * unconverted characters and <b>next</b> is NULL; or if the parsed + * value is not between <b>min</b> and <b>max</b>. When no error + * occurs, return the parsed value and set *<b>ok</b> (if provided) to + * 1. When an error occurs, return 0 and set *<b>ok</b> (if provided) + * to 0. + */ +long +tor_parse_long(const char *s, int base, long min, long max, + int *ok, char **next) +{ + char *endptr; + long r; + + if (base < 0) { + if (ok) + *ok = 0; + return 0; + } + + errno = 0; + r = strtol(s, &endptr, base); + CHECK_STRTOX_RESULT(); +} + +/** As tor_parse_long(), but return an unsigned long. */ +unsigned long +tor_parse_ulong(const char *s, int base, unsigned long min, + unsigned long max, int *ok, char **next) +{ + char *endptr; + unsigned long r; + + if (base < 0) { + if (ok) + *ok = 0; + return 0; + } + + errno = 0; + r = strtoul(s, &endptr, base); + CHECK_STRTOX_RESULT(); +} + +/** As tor_parse_long(), but return a double. */ +double +tor_parse_double(const char *s, double min, double max, int *ok, char **next) +{ + char *endptr; + double r; + + errno = 0; + r = strtod(s, &endptr); + CHECK_STRTOX_RESULT(); +} + +/** As tor_parse_long, but return a uint64_t. Only base 10 is guaranteed to + * work for now. */ +uint64_t +tor_parse_uint64(const char *s, int base, uint64_t min, + uint64_t max, int *ok, char **next) +{ + char *endptr; + uint64_t r; + + if (base < 0) { + if (ok) + *ok = 0; + return 0; + } + + errno = 0; +#ifdef HAVE_STRTOULL + r = (uint64_t)strtoull(s, &endptr, base); +#elif defined(_WIN32) + r = (uint64_t)_strtoui64(s, &endptr, base); +#elif SIZEOF_LONG == 8 + r = (uint64_t)strtoul(s, &endptr, base); +#else +#error "I don't know how to parse 64-bit numbers." +#endif /* defined(HAVE_STRTOULL) || ... */ + + CHECK_STRTOX_RESULT(); +} diff --git a/src/lib/string/parse_int.h b/src/lib/string/parse_int.h new file mode 100644 index 0000000000..925547942e --- /dev/null +++ b/src/lib/string/parse_int.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file parse_int.h + * \brief Header for parse_int.c + **/ + +#ifndef TOR_PARSE_INT_H +#define TOR_PARSE_INT_H + +#include "lib/cc/torint.h" + +long tor_parse_long(const char *s, int base, long min, + long max, int *ok, char **next); +unsigned long tor_parse_ulong(const char *s, int base, unsigned long min, + unsigned long max, int *ok, char **next); +double tor_parse_double(const char *s, double min, double max, int *ok, + char **next); +uint64_t tor_parse_uint64(const char *s, int base, uint64_t min, + uint64_t max, int *ok, char **next); + +#endif diff --git a/src/lib/string/printf.c b/src/lib/string/printf.c new file mode 100644 index 0000000000..415d4ac4a7 --- /dev/null +++ b/src/lib/string/printf.c @@ -0,0 +1,157 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file printf.c + * \brief Compatibility wrappers around snprintf and its friends + **/ + +#include "lib/string/printf.h" +#include "lib/err/torerr.h" +#include "lib/cc/torint.h" +#include "lib/malloc/malloc.h" + +#include <stdlib.h> +#include <stdio.h> + +/** Replacement for snprintf. Differs from platform snprintf in two + * ways: First, always NUL-terminates its output. Second, always + * returns -1 if the result is truncated. (Note that this return + * behavior does <i>not</i> conform to C99; it just happens to be + * easier to emulate "return -1" with conformant implementations than + * it is to emulate "return number that would be written" with + * non-conformant implementations.) */ +int +tor_snprintf(char *str, size_t size, const char *format, ...) +{ + va_list ap; + int r; + va_start(ap,format); + r = tor_vsnprintf(str,size,format,ap); + va_end(ap); + return r; +} + +/** Replacement for vsnprintf; behavior differs as tor_snprintf differs from + * snprintf. + */ +int +tor_vsnprintf(char *str, size_t size, const char *format, va_list args) +{ + int r; + if (size == 0) + return -1; /* no place for the NUL */ + if (size > SIZE_T_CEILING) + return -1; +#ifdef _WIN32 + r = _vsnprintf(str, size, format, args); +#else + r = vsnprintf(str, size, format, args); +#endif + str[size-1] = '\0'; + if (r < 0 || r >= (ssize_t)size) + return -1; + return r; +} + +/** + * Portable asprintf implementation. Does a printf() into a newly malloc'd + * string. Sets *<b>strp</b> to this string, and returns its length (not + * including the terminating NUL character). + * + * You can treat this function as if its implementation were something like + <pre> + char buf[_INFINITY_]; + tor_snprintf(buf, sizeof(buf), fmt, args); + *strp = tor_strdup(buf); + return strlen(*strp): + </pre> + * Where _INFINITY_ is an imaginary constant so big that any string can fit + * into it. + */ +int +tor_asprintf(char **strp, const char *fmt, ...) +{ + int r; + va_list args; + va_start(args, fmt); + r = tor_vasprintf(strp, fmt, args); + va_end(args); + if (!*strp || r < 0) { + /* LCOV_EXCL_START */ + raw_assert_unreached_msg("Internal error in asprintf"); + /* LCOV_EXCL_STOP */ + } + return r; +} + +/** + * Portable vasprintf implementation. Does a printf() into a newly malloc'd + * string. Differs from regular vasprintf in the same ways that + * tor_asprintf() differs from regular asprintf. + */ +int +tor_vasprintf(char **strp, const char *fmt, va_list args) +{ + /* use a temporary variable in case *strp is in args. */ + char *strp_tmp=NULL; +#ifdef HAVE_VASPRINTF + /* If the platform gives us one, use it. */ + int r = vasprintf(&strp_tmp, fmt, args); + if (r < 0) + *strp = NULL; // LCOV_EXCL_LINE -- no cross-platform way to force this + else + *strp = strp_tmp; + return r; +#elif defined(HAVE__VSCPRINTF) + /* On Windows, _vsnprintf won't tell us the length of the string if it + * overflows, so we need to use _vcsprintf to tell how much to allocate */ + int len, r; + va_list tmp_args; + va_copy(tmp_args, args); + len = _vscprintf(fmt, tmp_args); + va_end(tmp_args); + if (len < 0) { + *strp = NULL; + return -1; + } + strp_tmp = tor_malloc(len + 1); + r = _vsnprintf(strp_tmp, len+1, fmt, args); + if (r != len) { + tor_free(strp_tmp); + *strp = NULL; + return -1; + } + *strp = strp_tmp; + return len; +#else + /* Everywhere else, we have a decent vsnprintf that tells us how many + * characters we need. We give it a try on a short buffer first, since + * it might be nice to avoid the second vsnprintf call. + */ + char buf[128]; + int len, r; + va_list tmp_args; + va_copy(tmp_args, args); + /* vsnprintf() was properly checked but tor_vsnprintf() available so + * why not use it? */ + len = tor_vsnprintf(buf, sizeof(buf), fmt, tmp_args); + va_end(tmp_args); + if (len < (int)sizeof(buf)) { + *strp = tor_strdup(buf); + return len; + } + strp_tmp = tor_malloc(len+1); + /* use of tor_vsnprintf() will ensure string is null terminated */ + r = tor_vsnprintf(strp_tmp, len+1, fmt, args); + if (r != len) { + tor_free(strp_tmp); + *strp = NULL; + return -1; + } + *strp = strp_tmp; + return len; +#endif /* defined(HAVE_VASPRINTF) || ... */ +} diff --git a/src/lib/string/printf.h b/src/lib/string/printf.h new file mode 100644 index 0000000000..2cc13d6bee --- /dev/null +++ b/src/lib/string/printf.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file printf.h + * \brief Header for printf.c + **/ + +#ifndef TOR_UTIL_PRINTF_H +#define TOR_UTIL_PRINTF_H + +#include "orconfig.h" +#include "lib/cc/compat_compiler.h" + +#include <stdarg.h> +#include <stddef.h> + +int tor_snprintf(char *str, size_t size, const char *format, ...) + CHECK_PRINTF(3,4); +int tor_vsnprintf(char *str, size_t size, const char *format, va_list args) + CHECK_PRINTF(3,0); + +int tor_asprintf(char **strp, const char *fmt, ...) + CHECK_PRINTF(2,3); +int tor_vasprintf(char **strp, const char *fmt, va_list args) + CHECK_PRINTF(2,0); + +#endif /* !defined(TOR_UTIL_STRING_H) */ diff --git a/src/lib/string/scanf.c b/src/lib/string/scanf.c new file mode 100644 index 0000000000..1bc39b5182 --- /dev/null +++ b/src/lib/string/scanf.c @@ -0,0 +1,317 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file scanf.c + * \brief Locale-independent minimal implementation of sscanf(). + **/ + +#include "lib/string/scanf.h" +#include "lib/string/compat_ctype.h" +#include "lib/cc/torint.h" +#include "lib/err/torerr.h" + +#include <stdlib.h> + +#define MAX_SCANF_WIDTH 9999 + +/** Helper: given an ASCII-encoded decimal digit, return its numeric value. + * NOTE: requires that its input be in-bounds. */ +static int +digit_to_num(char d) +{ + int num = ((int)d) - (int)'0'; + raw_assert(num <= 9 && num >= 0); + return num; +} + +/** Helper: Read an unsigned int from *<b>bufp</b> of up to <b>width</b> + * characters. (Handle arbitrary width if <b>width</b> is less than 0.) On + * success, store the result in <b>out</b>, advance bufp to the next + * character, and return 0. On failure, return -1. */ +static int +scan_unsigned(const char **bufp, unsigned long *out, int width, unsigned base) +{ + unsigned long result = 0; + int scanned_so_far = 0; + const int hex = base==16; + raw_assert(base == 10 || base == 16); + if (!bufp || !*bufp || !out) + return -1; + if (width<0) + width=MAX_SCANF_WIDTH; + + while (**bufp && (hex?TOR_ISXDIGIT(**bufp):TOR_ISDIGIT(**bufp)) + && scanned_so_far < width) { + unsigned digit = hex?hex_decode_digit(*(*bufp)++):digit_to_num(*(*bufp)++); + // Check for overflow beforehand, without actually causing any overflow + // This preserves functionality on compilers that don't wrap overflow + // (i.e. that trap or optimise away overflow) + // result * base + digit > ULONG_MAX + // result * base > ULONG_MAX - digit + if (result > (ULONG_MAX - digit)/base) + return -1; /* Processing this digit would overflow */ + result = result * base + digit; + ++scanned_so_far; + } + + if (!scanned_so_far) /* No actual digits scanned */ + return -1; + + *out = result; + return 0; +} + +/** Helper: Read an signed int from *<b>bufp</b> of up to <b>width</b> + * characters. (Handle arbitrary width if <b>width</b> is less than 0.) On + * success, store the result in <b>out</b>, advance bufp to the next + * character, and return 0. On failure, return -1. */ +static int +scan_signed(const char **bufp, long *out, int width) +{ + int neg = 0; + unsigned long result = 0; + + if (!bufp || !*bufp || !out) + return -1; + if (width<0) + width=MAX_SCANF_WIDTH; + + if (**bufp == '-') { + neg = 1; + ++*bufp; + --width; + } + + if (scan_unsigned(bufp, &result, width, 10) < 0) + return -1; + + if (neg && result > 0) { + if (result > ((unsigned long)LONG_MAX) + 1) + return -1; /* Underflow */ + else if (result == ((unsigned long)LONG_MAX) + 1) + *out = LONG_MIN; + else { + /* We once had a far more clever no-overflow conversion here, but + * some versions of GCC apparently ran it into the ground. Now + * we just check for LONG_MIN explicitly. + */ + *out = -(long)result; + } + } else { + if (result > LONG_MAX) + return -1; /* Overflow */ + *out = (long)result; + } + + return 0; +} + +/** Helper: Read a decimal-formatted double from *<b>bufp</b> of up to + * <b>width</b> characters. (Handle arbitrary width if <b>width</b> is less + * than 0.) On success, store the result in <b>out</b>, advance bufp to the + * next character, and return 0. On failure, return -1. */ +static int +scan_double(const char **bufp, double *out, int width) +{ + int neg = 0; + double result = 0; + int scanned_so_far = 0; + + if (!bufp || !*bufp || !out) + return -1; + if (width<0) + width=MAX_SCANF_WIDTH; + + if (**bufp == '-') { + neg = 1; + ++*bufp; + } + + while (**bufp && TOR_ISDIGIT(**bufp) && scanned_so_far < width) { + const int digit = digit_to_num(*(*bufp)++); + result = result * 10 + digit; + ++scanned_so_far; + } + if (**bufp == '.') { + double fracval = 0, denominator = 1; + ++*bufp; + ++scanned_so_far; + while (**bufp && TOR_ISDIGIT(**bufp) && scanned_so_far < width) { + const int digit = digit_to_num(*(*bufp)++); + fracval = fracval * 10 + digit; + denominator *= 10; + ++scanned_so_far; + } + result += fracval / denominator; + } + + if (!scanned_so_far) /* No actual digits scanned */ + return -1; + + *out = neg ? -result : result; + return 0; +} + +/** Helper: copy up to <b>width</b> non-space characters from <b>bufp</b> to + * <b>out</b>. Make sure <b>out</b> is nul-terminated. Advance <b>bufp</b> + * to the next non-space character or the EOS. */ +static int +scan_string(const char **bufp, char *out, int width) +{ + int scanned_so_far = 0; + if (!bufp || !out || width < 0) + return -1; + while (**bufp && ! TOR_ISSPACE(**bufp) && scanned_so_far < width) { + *out++ = *(*bufp)++; + ++scanned_so_far; + } + *out = '\0'; + return 0; +} + +/** Locale-independent, minimal, no-surprises scanf variant, accepting only a + * restricted pattern format. For more info on what it supports, see + * tor_sscanf() documentation. */ +int +tor_vsscanf(const char *buf, const char *pattern, va_list ap) +{ + int n_matched = 0; + + while (*pattern) { + if (*pattern != '%') { + if (*buf == *pattern) { + ++buf; + ++pattern; + continue; + } else { + return n_matched; + } + } else { + int width = -1; + int longmod = 0; + ++pattern; + if (TOR_ISDIGIT(*pattern)) { + width = digit_to_num(*pattern++); + while (TOR_ISDIGIT(*pattern)) { + width *= 10; + width += digit_to_num(*pattern++); + if (width > MAX_SCANF_WIDTH) + return -1; + } + if (!width) /* No zero-width things. */ + return -1; + } + if (*pattern == 'l') { + longmod = 1; + ++pattern; + } + if (*pattern == 'u' || *pattern == 'x') { + unsigned long u; + const int base = (*pattern == 'u') ? 10 : 16; + if (!*buf) + return n_matched; + if (scan_unsigned(&buf, &u, width, base)<0) + return n_matched; + if (longmod) { + unsigned long *out = va_arg(ap, unsigned long *); + *out = u; + } else { + unsigned *out = va_arg(ap, unsigned *); + if (u > UINT_MAX) + return n_matched; + *out = (unsigned) u; + } + ++pattern; + ++n_matched; + } else if (*pattern == 'f') { + double *d = va_arg(ap, double *); + if (!longmod) + return -1; /* float not supported */ + if (!*buf) + return n_matched; + if (scan_double(&buf, d, width)<0) + return n_matched; + ++pattern; + ++n_matched; + } else if (*pattern == 'd') { + long lng=0; + if (scan_signed(&buf, &lng, width)<0) + return n_matched; + if (longmod) { + long *out = va_arg(ap, long *); + *out = lng; + } else { + int *out = va_arg(ap, int *); +#if LONG_MAX > INT_MAX + if (lng < INT_MIN || lng > INT_MAX) + return n_matched; +#endif + *out = (int)lng; + } + ++pattern; + ++n_matched; + } else if (*pattern == 's') { + char *s = va_arg(ap, char *); + if (longmod) + return -1; + if (width < 0) + return -1; + if (scan_string(&buf, s, width)<0) + return n_matched; + ++pattern; + ++n_matched; + } else if (*pattern == 'c') { + char *ch = va_arg(ap, char *); + if (longmod) + return -1; + if (width != -1) + return -1; + if (!*buf) + return n_matched; + *ch = *buf++; + ++pattern; + ++n_matched; + } else if (*pattern == '%') { + if (*buf != '%') + return n_matched; + if (longmod) + return -1; + ++buf; + ++pattern; + } else { + return -1; /* Unrecognized pattern component. */ + } + } + } + + return n_matched; +} + +/** Minimal sscanf replacement: parse <b>buf</b> according to <b>pattern</b> + * and store the results in the corresponding argument fields. Differs from + * sscanf in that: + * <ul><li>It only handles %u, %lu, %x, %lx, %[NUM]s, %d, %ld, %lf, and %c. + * <li>It only handles decimal inputs for %lf. (12.3, not 1.23e1) + * <li>It does not handle arbitrarily long widths. + * <li>Numbers do not consume any space characters. + * <li>It is locale-independent. + * <li>%u and %x do not consume any space. + * <li>It returns -1 on malformed patterns.</ul> + * + * (As with other locale-independent functions, we need this to parse data that + * is in ASCII without worrying that the C library's locale-handling will make + * miscellaneous characters look like numbers, spaces, and so on.) + */ +int +tor_sscanf(const char *buf, const char *pattern, ...) +{ + int r; + va_list ap; + va_start(ap, pattern); + r = tor_vsscanf(buf, pattern, ap); + va_end(ap); + return r; +} diff --git a/src/lib/string/scanf.h b/src/lib/string/scanf.h new file mode 100644 index 0000000000..6673173de5 --- /dev/null +++ b/src/lib/string/scanf.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file scanf.h + * \brief Header for scanf.c + **/ + +#ifndef TOR_UTIL_SCANF_H +#define TOR_UTIL_SCANF_H + +#include "orconfig.h" +#include "lib/cc/compat_compiler.h" + +#include <stdarg.h> + +int tor_vsscanf(const char *buf, const char *pattern, va_list ap) \ + CHECK_SCANF(2, 0); +int tor_sscanf(const char *buf, const char *pattern, ...) + CHECK_SCANF(2, 3); + +#endif /* !defined(TOR_UTIL_STRING_H) */ diff --git a/src/lib/string/util_string.c b/src/lib/string/util_string.c new file mode 100644 index 0000000000..f934f66f02 --- /dev/null +++ b/src/lib/string/util_string.c @@ -0,0 +1,543 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file util_string.c + * \brief Non-standard string functions used throughout Tor. + **/ + +#include "lib/string/util_string.h" +#include "lib/string/compat_ctype.h" +#include "lib/err/torerr.h" +#include "lib/ctime/di_ops.h" +#include "lib/defs/digest_sizes.h" + +#include <string.h> +#include <stdlib.h> + +/** Given <b>hlen</b> bytes at <b>haystack</b> and <b>nlen</b> bytes at + * <b>needle</b>, return a pointer to the first occurrence of the needle + * within the haystack, or NULL if there is no such occurrence. + * + * This function is <em>not</em> timing-safe. + * + * Requires that <b>nlen</b> be greater than zero. + */ +const void * +tor_memmem(const void *_haystack, size_t hlen, + const void *_needle, size_t nlen) +{ +#if defined(HAVE_MEMMEM) && (!defined(__GNUC__) || __GNUC__ >= 2) + raw_assert(nlen); + return memmem(_haystack, hlen, _needle, nlen); +#else + /* This isn't as fast as the GLIBC implementation, but it doesn't need to + * be. */ + const char *p, *last_possible_start; + const char *haystack = (const char*)_haystack; + const char *needle = (const char*)_needle; + char first; + raw_assert(nlen); + + if (nlen > hlen) + return NULL; + + p = haystack; + /* Last position at which the needle could start. */ + last_possible_start = haystack + hlen - nlen; + first = *(const char*)needle; + while ((p = memchr(p, first, last_possible_start + 1 - p))) { + if (fast_memeq(p, needle, nlen)) + return p; + if (++p > last_possible_start) { + /* This comparison shouldn't be necessary, since if p was previously + * equal to last_possible_start, the next memchr call would be + * "memchr(p, first, 0)", which will return NULL. But it clarifies the + * logic. */ + return NULL; + } + } + return NULL; +#endif /* defined(HAVE_MEMMEM) && (!defined(__GNUC__) || __GNUC__ >= 2) */ +} + +const void * +tor_memstr(const void *haystack, size_t hlen, const char *needle) +{ + return tor_memmem(haystack, hlen, needle, strlen(needle)); +} + +/** Return true iff the 'len' bytes at 'mem' are all zero. */ +int +tor_mem_is_zero(const char *mem, size_t len) +{ + static const char ZERO[] = { + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + }; + while (len >= sizeof(ZERO)) { + /* It's safe to use fast_memcmp here, since the very worst thing an + * attacker could learn is how many initial bytes of a secret were zero */ + if (fast_memcmp(mem, ZERO, sizeof(ZERO))) + return 0; + len -= sizeof(ZERO); + mem += sizeof(ZERO); + } + /* Deal with leftover bytes. */ + if (len) + return fast_memeq(mem, ZERO, len); + + return 1; +} + +/** Return true iff the DIGEST_LEN bytes in digest are all zero. */ +int +tor_digest_is_zero(const char *digest) +{ + static const uint8_t ZERO_DIGEST[] = { + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 + }; + return tor_memeq(digest, ZERO_DIGEST, DIGEST_LEN); +} + +/** Return true iff the DIGEST256_LEN bytes in digest are all zero. */ +int +tor_digest256_is_zero(const char *digest) +{ + return tor_mem_is_zero(digest, DIGEST256_LEN); +} + +/** Remove from the string <b>s</b> every character which appears in + * <b>strip</b>. */ +void +tor_strstrip(char *s, const char *strip) +{ + char *readp = s; + while (*readp) { + if (strchr(strip, *readp)) { + ++readp; + } else { + *s++ = *readp++; + } + } + *s = '\0'; +} + +/** Convert all alphabetic characters in the nul-terminated string <b>s</b> to + * lowercase. */ +void +tor_strlower(char *s) +{ + while (*s) { + *s = TOR_TOLOWER(*s); + ++s; + } +} + +/** Convert all alphabetic characters in the nul-terminated string <b>s</b> to + * lowercase. */ +void +tor_strupper(char *s) +{ + while (*s) { + *s = TOR_TOUPPER(*s); + ++s; + } +} + +/** Return 1 if every character in <b>s</b> is printable, else return 0. + */ +int +tor_strisprint(const char *s) +{ + while (*s) { + if (!TOR_ISPRINT(*s)) + return 0; + s++; + } + return 1; +} + +/** Return 1 if no character in <b>s</b> is uppercase, else return 0. + */ +int +tor_strisnonupper(const char *s) +{ + while (*s) { + if (TOR_ISUPPER(*s)) + return 0; + s++; + } + return 1; +} + +/** Return true iff every character in <b>s</b> is whitespace space; else + * return false. */ +int +tor_strisspace(const char *s) +{ + while (*s) { + if (!TOR_ISSPACE(*s)) + return 0; + s++; + } + return 1; +} + +/** As strcmp, except that either string may be NULL. The NULL string is + * considered to be before any non-NULL string. */ +int +strcmp_opt(const char *s1, const char *s2) +{ + if (!s1) { + if (!s2) + return 0; + else + return -1; + } else if (!s2) { + return 1; + } else { + return strcmp(s1, s2); + } +} + +/** Compares the first strlen(s2) characters of s1 with s2. Returns as for + * strcmp. + */ +int +strcmpstart(const char *s1, const char *s2) +{ + size_t n = strlen(s2); + return strncmp(s1, s2, n); +} + +/** Compare the s1_len-byte string <b>s1</b> with <b>s2</b>, + * without depending on a terminating nul in s1. Sorting order is first by + * length, then lexically; return values are as for strcmp. + */ +int +strcmp_len(const char *s1, const char *s2, size_t s1_len) +{ + size_t s2_len = strlen(s2); + if (s1_len < s2_len) + return -1; + if (s1_len > s2_len) + return 1; + return fast_memcmp(s1, s2, s2_len); +} + +/** Compares the first strlen(s2) characters of s1 with s2. Returns as for + * strcasecmp. + */ +int +strcasecmpstart(const char *s1, const char *s2) +{ + size_t n = strlen(s2); + return strncasecmp(s1, s2, n); +} + +/** Compare the value of the string <b>prefix</b> with the start of the + * <b>memlen</b>-byte memory chunk at <b>mem</b>. Return as for strcmp. + * + * [As fast_memcmp(mem, prefix, strlen(prefix)) but returns -1 if memlen is + * less than strlen(prefix).] + */ +int +fast_memcmpstart(const void *mem, size_t memlen, + const char *prefix) +{ + size_t plen = strlen(prefix); + if (memlen < plen) + return -1; + return fast_memcmp(mem, prefix, plen); +} + +/** Compares the last strlen(s2) characters of s1 with s2. Returns as for + * strcmp. + */ +int +strcmpend(const char *s1, const char *s2) +{ + size_t n1 = strlen(s1), n2 = strlen(s2); + if (n2>n1) + return strcmp(s1,s2); + else + return strncmp(s1+(n1-n2), s2, n2); +} + +/** Compares the last strlen(s2) characters of s1 with s2. Returns as for + * strcasecmp. + */ +int +strcasecmpend(const char *s1, const char *s2) +{ + size_t n1 = strlen(s1), n2 = strlen(s2); + if (n2>n1) /* then they can't be the same; figure out which is bigger */ + return strcasecmp(s1,s2); + else + return strncasecmp(s1+(n1-n2), s2, n2); +} + +/** Return a pointer to the first char of s that is not whitespace and + * not a comment, or to the terminating NUL if no such character exists. + */ +const char * +eat_whitespace(const char *s) +{ + raw_assert(s); + + while (1) { + switch (*s) { + case '\0': + default: + return s; + case ' ': + case '\t': + case '\n': + case '\r': + ++s; + break; + case '#': + ++s; + while (*s && *s != '\n') + ++s; + } + } +} + +/** Return a pointer to the first char of s that is not whitespace and + * not a comment, or to the terminating NUL if no such character exists. + */ +const char * +eat_whitespace_eos(const char *s, const char *eos) +{ + raw_assert(s); + raw_assert(eos && s <= eos); + + while (s < eos) { + switch (*s) { + case '\0': + default: + return s; + case ' ': + case '\t': + case '\n': + case '\r': + ++s; + break; + case '#': + ++s; + while (s < eos && *s && *s != '\n') + ++s; + } + } + return s; +} + +/** Return a pointer to the first char of s that is not a space or a tab + * or a \\r, or to the terminating NUL if no such character exists. */ +const char * +eat_whitespace_no_nl(const char *s) +{ + while (*s == ' ' || *s == '\t' || *s == '\r') + ++s; + return s; +} + +/** As eat_whitespace_no_nl, but stop at <b>eos</b> whether we have + * found a non-whitespace character or not. */ +const char * +eat_whitespace_eos_no_nl(const char *s, const char *eos) +{ + while (s < eos && (*s == ' ' || *s == '\t' || *s == '\r')) + ++s; + return s; +} + +/** Return a pointer to the first char of s that is whitespace or <b>#</b>, + * or to the terminating NUL if no such character exists. + */ +const char * +find_whitespace(const char *s) +{ + /* tor_assert(s); */ + while (1) { + switch (*s) + { + case '\0': + case '#': + case ' ': + case '\r': + case '\n': + case '\t': + return s; + default: + ++s; + } + } +} + +/** As find_whitespace, but stop at <b>eos</b> whether we have found a + * whitespace or not. */ +const char * +find_whitespace_eos(const char *s, const char *eos) +{ + /* tor_assert(s); */ + while (s < eos) { + switch (*s) + { + case '\0': + case '#': + case ' ': + case '\r': + case '\n': + case '\t': + return s; + default: + ++s; + } + } + return s; +} + +/** Return the first occurrence of <b>needle</b> in <b>haystack</b> that + * occurs at the start of a line (that is, at the beginning of <b>haystack</b> + * or immediately after a newline). Return NULL if no such string is found. + */ +const char * +find_str_at_start_of_line(const char *haystack, const char *needle) +{ + size_t needle_len = strlen(needle); + + do { + if (!strncmp(haystack, needle, needle_len)) + return haystack; + + haystack = strchr(haystack, '\n'); + if (!haystack) + return NULL; + else + ++haystack; + } while (*haystack); + + return NULL; +} + +/** Returns true if <b>string</b> could be a C identifier. + A C identifier must begin with a letter or an underscore and the + rest of its characters can be letters, numbers or underscores. No + length limit is imposed. */ +int +string_is_C_identifier(const char *string) +{ + size_t iter; + size_t length = strlen(string); + if (!length) + return 0; + + for (iter = 0; iter < length ; iter++) { + if (iter == 0) { + if (!(TOR_ISALPHA(string[iter]) || + string[iter] == '_')) + return 0; + } else { + if (!(TOR_ISALPHA(string[iter]) || + TOR_ISDIGIT(string[iter]) || + string[iter] == '_')) + return 0; + } + } + + return 1; +} + +/** A byte with the top <b>x</b> bits set. */ +#define TOP_BITS(x) ((uint8_t)(0xFF << (8 - (x)))) +/** A byte with the lowest <b>x</b> bits set. */ +#define LOW_BITS(x) ((uint8_t)(0xFF >> (8 - (x)))) + +/** Given the leading byte <b>b</b>, return the total number of bytes in the + * UTF-8 character. Returns 0 if it's an invalid leading byte. + */ +static uint8_t +bytes_in_char(uint8_t b) +{ + if ((TOP_BITS(1) & b) == 0x00) + return 1; // a 1-byte UTF-8 char, aka ASCII + if ((TOP_BITS(3) & b) == TOP_BITS(2)) + return 2; // a 2-byte UTF-8 char + if ((TOP_BITS(4) & b) == TOP_BITS(3)) + return 3; // a 3-byte UTF-8 char + if ((TOP_BITS(5) & b) == TOP_BITS(4)) + return 4; // a 4-byte UTF-8 char + + // Invalid: either the top 2 bits are 10, or the top 5 bits are 11111. + return 0; +} + +/** Returns true iff <b>b</b> is a UTF-8 continuation byte. */ +static bool +is_continuation_byte(uint8_t b) +{ + uint8_t top2bits = b & TOP_BITS(2); + return top2bits == TOP_BITS(1); +} + +/** Returns true iff the <b>len</b> bytes in <b>c</b> are a valid UTF-8 + * character. + */ +static bool +validate_char(const uint8_t *c, uint8_t len) +{ + if (len == 1) + return true; // already validated this is an ASCII char earlier. + + uint8_t mask = LOW_BITS(7 - len); // bitmask for the leading byte. + uint32_t codepoint = c[0] & mask; + + mask = LOW_BITS(6); // bitmask for continuation bytes. + for (uint8_t i = 1; i < len; i++) { + if (!is_continuation_byte(c[i])) + return false; + codepoint <<= 6; + codepoint |= (c[i] & mask); + } + + if (len == 2 && codepoint <= 0x7f) + return false; // Invalid, overly long encoding, should have fit in 1 byte. + + if (len == 3 && codepoint <= 0x7ff) + return false; // Invalid, overly long encoding, should have fit in 2 bytes. + + if (len == 4 && codepoint <= 0xffff) + return false; // Invalid, overly long encoding, should have fit in 3 bytes. + + if (codepoint >= 0xd800 && codepoint <= 0xdfff) + return false; // Invalid, reserved for UTF-16 surrogate pairs. + + return codepoint <= 0x10ffff; // Check if within maximum. +} + +/** Returns true iff the first <b>len</b> bytes in <b>str</b> are a + valid UTF-8 string. */ +int +string_is_utf8(const char *str, size_t len) +{ + for (size_t i = 0; i < len;) { + uint8_t num_bytes = bytes_in_char(str[i]); + if (num_bytes == 0) // Invalid leading byte found. + return false; + + size_t next_char = i + num_bytes; + if (next_char > len) + return false; + + // Validate the continuation bytes in this multi-byte character, + // and advance to the next character in the string. + if (!validate_char((const uint8_t*)&str[i], num_bytes)) + return false; + i = next_char; + } + return true; +} diff --git a/src/lib/string/util_string.h b/src/lib/string/util_string.h new file mode 100644 index 0000000000..d9fbf8c61e --- /dev/null +++ b/src/lib/string/util_string.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file util_string.h + * \brief Header for util_string.c + **/ + +#ifndef TOR_UTIL_STRING_H +#define TOR_UTIL_STRING_H + +#include "orconfig.h" +#include "lib/cc/compat_compiler.h" + +#include <stddef.h> + +const void *tor_memmem(const void *haystack, size_t hlen, const void *needle, + size_t nlen); +const void *tor_memstr(const void *haystack, size_t hlen, + const char *needle); +int tor_mem_is_zero(const char *mem, size_t len); +int tor_digest_is_zero(const char *digest); +int tor_digest256_is_zero(const char *digest); + +/** Allowable characters in a hexadecimal string. */ +#define HEX_CHARACTERS "0123456789ABCDEFabcdef" +void tor_strlower(char *s); +void tor_strupper(char *s); +int tor_strisprint(const char *s); +int tor_strisnonupper(const char *s); +int tor_strisspace(const char *s); +int strcmp_opt(const char *s1, const char *s2); +int strcmpstart(const char *s1, const char *s2); +int strcmp_len(const char *s1, const char *s2, size_t len); +int strcasecmpstart(const char *s1, const char *s2); +int strcmpend(const char *s1, const char *s2); +int strcasecmpend(const char *s1, const char *s2); +int fast_memcmpstart(const void *mem, size_t memlen, const char *prefix); + +void tor_strstrip(char *s, const char *strip); + +const char *eat_whitespace(const char *s); +const char *eat_whitespace_eos(const char *s, const char *eos); +const char *eat_whitespace_no_nl(const char *s); +const char *eat_whitespace_eos_no_nl(const char *s, const char *eos); +const char *find_whitespace(const char *s); +const char *find_whitespace_eos(const char *s, const char *eos); +const char *find_str_at_start_of_line(const char *haystack, + const char *needle); + +int string_is_C_identifier(const char *string); + +int string_is_utf8(const char *str, size_t len); + +#endif /* !defined(TOR_UTIL_STRING_H) */ diff --git a/src/lib/term/.may_include b/src/lib/term/.may_include new file mode 100644 index 0000000000..c93a06e59e --- /dev/null +++ b/src/lib/term/.may_include @@ -0,0 +1,9 @@ +orconfig.h + +lib/cc/*.h +lib/log/*.h +lib/term/*.h +lib/malloc/*.h + +# From src/ext +tor_readpassphrase.h diff --git a/src/lib/term/getpass.c b/src/lib/term/getpass.c new file mode 100644 index 0000000000..c6459f250f --- /dev/null +++ b/src/lib/term/getpass.c @@ -0,0 +1,120 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file getpass.c + * \brief Cross-platform wrapper to read passphrases from the terminal. + **/ + +#include "lib/term/getpass.h" + +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" + +#ifdef _WIN32 +#include <windows.h> +#include <conio.h> +#include <wchar.h> +/* Some mingw headers lack these. :p */ +#if defined(HAVE_DECL__GETWCH) && !HAVE_DECL__GETWCH +wint_t _getwch(void); +#endif +#ifndef WEOF +#define WEOF (wchar_t)(0xFFFF) +#endif +#if defined(HAVE_DECL_SECUREZEROMEMORY) && !HAVE_DECL_SECUREZEROMEMORY +static inline void +SecureZeroMemory(PVOID ptr, SIZE_T cnt) +{ + volatile char *vcptr = (volatile char*)ptr; + while (cnt--) + *vcptr++ = 0; +} +#endif /* defined(HAVE_DECL_SECUREZEROMEMORY) && !HAVE_DECL_SECUREZEROMEMORY */ +#elif defined(HAVE_READPASSPHRASE_H) +#include <readpassphrase.h> +#else +#include "tor_readpassphrase.h" +#endif /* defined(_WIN32) || ... */ + +#include <stdlib.h> +#include <string.h> + +/** Emit the password prompt <b>prompt</b>, then read up to <b>buflen</b> + * bytes of passphrase into <b>output</b>. Return the number of bytes in + * the passphrase, excluding terminating NUL. + */ +ssize_t +tor_getpass(const char *prompt, char *output, size_t buflen) +{ + tor_assert(buflen <= SSIZE_MAX); + tor_assert(buflen >= 1); +#if defined(HAVE_READPASSPHRASE) + char *pwd = readpassphrase(prompt, output, buflen, RPP_ECHO_OFF); + if (pwd == NULL) + return -1; + return strlen(pwd); +#elif defined(_WIN32) + int r = -1; + while (*prompt) { + _putch(*prompt++); + } + + tor_assert(buflen <= INT_MAX); + wchar_t *buf = tor_calloc(buflen, sizeof(wchar_t)); + + wchar_t *ptr = buf, *lastch = buf + buflen - 1; + while (ptr < lastch) { + wint_t ch = _getwch(); + switch (ch) { + case '\r': + case '\n': + case WEOF: + goto done_reading; + case 3: + goto done; /* Can't actually read ctrl-c this way. */ + case '\b': + if (ptr > buf) + --ptr; + continue; + case 0: + case 0xe0: + ch = _getwch(); /* Ignore; this is a function or arrow key */ + break; + default: + *ptr++ = ch; + break; + } + } + done_reading: + ; + +#ifndef WC_ERR_INVALID_CHARS +#define WC_ERR_INVALID_CHARS 0x80 +#endif + + /* Now convert it to UTF-8 */ + r = WideCharToMultiByte(CP_UTF8, + WC_NO_BEST_FIT_CHARS|WC_ERR_INVALID_CHARS, + buf, (int)(ptr-buf), + output, (int)(buflen-1), + NULL, NULL); + if (r <= 0) { + r = -1; + goto done; + } + + tor_assert(r < (int)buflen); + + output[r] = 0; + + done: + SecureZeroMemory(buf, sizeof(wchar_t)*buflen); + tor_free(buf); + return r; +#else +#error "No implementation for tor_getpass found!" +#endif /* defined(HAVE_READPASSPHRASE) || ... */ +} diff --git a/src/lib/term/getpass.h b/src/lib/term/getpass.h new file mode 100644 index 0000000000..a9c146ea8f --- /dev/null +++ b/src/lib/term/getpass.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file getpass.h + * \brief Header for getpass.c + **/ + +#ifndef TOR_GETPASS_H +#define TOR_GETPASS_H + +#include "lib/cc/torint.h" + +ssize_t tor_getpass(const char *prompt, char *output, size_t buflen); + +#endif diff --git a/src/lib/term/include.am b/src/lib/term/include.am new file mode 100644 index 0000000000..55fe548ebc --- /dev/null +++ b/src/lib/term/include.am @@ -0,0 +1,24 @@ + +noinst_LIBRARIES += src/lib/libtor-term.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-term-testing.a +endif + +if BUILD_READPASSPHRASE_C +readpassphrase_source=src/ext/readpassphrase.c +else +readpassphrase_source= +endif + +src_lib_libtor_term_a_SOURCES = \ + src/lib/term/getpass.c \ + $(readpassphrase_source) + +src_lib_libtor_term_testing_a_SOURCES = \ + $(src_lib_libtor_term_a_SOURCES) +src_lib_libtor_term_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_term_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/term/getpass.h diff --git a/src/lib/testsupport/.may_include b/src/lib/testsupport/.may_include new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/src/lib/testsupport/.may_include diff --git a/src/lib/testsupport/include.am b/src/lib/testsupport/include.am new file mode 100644 index 0000000000..b2aa620985 --- /dev/null +++ b/src/lib/testsupport/include.am @@ -0,0 +1,3 @@ + +noinst_HEADERS += \ + src/lib/testsupport/testsupport.h diff --git a/src/lib/testsupport/testsupport.h b/src/lib/testsupport/testsupport.h new file mode 100644 index 0000000000..9363a9ba66 --- /dev/null +++ b/src/lib/testsupport/testsupport.h @@ -0,0 +1,103 @@ +/* Copyright (c) 2013-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file testsupport.h + * + * \brief Macros to implement mocking and selective exposure for the test code. + * + * Each Tor source file is built twice: once with TOR_UNIT_TESTS defined, and + * once with it undefined. The only difference between these configurations + * should be that when building for the tests, more functions are exposed as + * non-static, and a number of functions are declared as mockable. + **/ + +#ifndef TOR_TESTSUPPORT_H +#define TOR_TESTSUPPORT_H + +#ifdef TOR_UNIT_TESTS +/** The "STATIC" macro marks a function or variable that is static when + * building Tor for production, but non-static when building the unit + * tests. */ +#define STATIC +#define EXTERN(type, name) extern type name; +#else +#define STATIC static +#define EXTERN(type, name) +#endif /* defined(TOR_UNIT_TESTS) */ + +/** Quick and dirty macros to implement test mocking. + * + * To use them, suppose that you have a function you'd like to mock + * with the signature "void writebuf(size_t n, char *buf)". You can then + * declare the function as: + * + * MOCK_DECL(void, writebuf, (size_t n, char *buf)); + * + * and implement it as: + * + * MOCK_IMPL(void, + * writebuf,(size_t n, char *buf)) + * { + * ... + * } + * + * For the non-testing build, this will expand simply into: + * + * void writebuf(size_t n, char *buf); + * void + * writebuf(size_t n, char *buf) + * { + * ... + * } + * + * But for the testing case, it will expand into: + * + * void writebuf__real(size_t n, char *buf); + * extern void (*writebuf)(size_t n, char *buf); + * + * void (*writebuf)(size_t n, char *buf) = writebuf__real; + * void + * writebuf__real(size_t n, char *buf) + * { + * ... + * } + * + * This is not a great mocking system! It is deliberately "the simplest + * thing that could work", and pays for its simplicity in its lack of + * features, and in its uglification of the Tor code. Replacing it with + * something clever would be a fine thing. + * + * @{ */ +#ifdef TOR_UNIT_TESTS +#define MOCK_DECL(rv, funcname, arglist) \ + rv funcname ##__real arglist; \ + extern rv(*funcname) arglist +#define MOCK_IMPL(rv, funcname, arglist) \ + rv(*funcname) arglist = funcname ##__real; \ + rv funcname ##__real arglist +#define MOCK_DECL_ATTR(rv, funcname, arglist, attr) \ + rv funcname ##__real arglist attr; \ + extern rv(*funcname) arglist +#define MOCK_IMPL(rv, funcname, arglist) \ + rv(*funcname) arglist = funcname ##__real; \ + rv funcname ##__real arglist +#define MOCK(func, replacement) \ + do { \ + (func) = (replacement); \ + } while (0) +#define UNMOCK(func) \ + do { \ + func = func ##__real; \ + } while (0) +#else /* !(defined(TOR_UNIT_TESTS)) */ +#define MOCK_DECL(rv, funcname, arglist) \ + rv funcname arglist +#define MOCK_DECL_ATTR(rv, funcname, arglist, attr) \ + rv funcname arglist attr +#define MOCK_IMPL(rv, funcname, arglist) \ + rv funcname arglist +#endif /* defined(TOR_UNIT_TESTS) */ +/** @} */ + +#endif /* !defined(TOR_TESTSUPPORT_H) */ diff --git a/src/lib/thread/.may_include b/src/lib/thread/.may_include new file mode 100644 index 0000000000..fc56f46836 --- /dev/null +++ b/src/lib/thread/.may_include @@ -0,0 +1,7 @@ +orconfig.h +lib/cc/*.h +lib/lock/*.h +lib/log/*.h +lib/testsupport/*.h +lib/thread/*.h +lib/wallclock/*.h diff --git a/src/lib/thread/compat_pthreads.c b/src/lib/thread/compat_pthreads.c new file mode 100644 index 0000000000..05efe9cfd0 --- /dev/null +++ b/src/lib/thread/compat_pthreads.c @@ -0,0 +1,270 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_pthreads.c + * + * \brief Implementation for the pthreads-based multithreading backend + * functions. + */ + +#include "orconfig.h" +#include "lib/thread/threads.h" +#include "lib/wallclock/timeval.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +#include <sys/time.h> +#include <pthread.h> +#include <signal.h> +#include <time.h> +#include <errno.h> +#include <string.h> + +/** Wraps a void (*)(void*) function and its argument so we can + * invoke them in a way pthreads would expect. + */ +typedef struct tor_pthread_data_t { + void (*func)(void *); + void *data; +} tor_pthread_data_t; +/** Given a tor_pthread_data_t <b>_data</b>, call _data->func(d->data) + * and free _data. Used to make sure we can call functions the way pthread + * expects. */ +static void * +tor_pthread_helper_fn(void *_data) +{ + tor_pthread_data_t *data = _data; + void (*func)(void*); + void *arg; + /* mask signals to worker threads to avoid SIGPIPE, etc */ + sigset_t sigs; + /* We're in a subthread; don't handle any signals here. */ + sigfillset(&sigs); + pthread_sigmask(SIG_SETMASK, &sigs, NULL); + + func = data->func; + arg = data->data; + tor_free(_data); + func(arg); + return NULL; +} +/** + * A pthread attribute to make threads start detached. + */ +static pthread_attr_t attr_detached; +/** True iff we've called tor_threads_init() */ +static int threads_initialized = 0; + +/** Minimalist interface to run a void function in the background. On + * Unix calls pthread_create, on win32 calls beginthread. Returns -1 on + * failure. + * func should not return, but rather should call spawn_exit. + * + * NOTE: if <b>data</b> is used, it should not be allocated on the stack, + * since in a multithreaded environment, there is no way to be sure that + * the caller's stack will still be around when the called function is + * running. + */ +int +spawn_func(void (*func)(void *), void *data) +{ + pthread_t thread; + tor_pthread_data_t *d; + if (PREDICT_UNLIKELY(!threads_initialized)) { + tor_threads_init(); + } + d = tor_malloc(sizeof(tor_pthread_data_t)); + d->data = data; + d->func = func; + if (pthread_create(&thread, &attr_detached, tor_pthread_helper_fn, d)) { + tor_free(d); + return -1; + } + + return 0; +} + +/** End the current thread/process. + */ +void +spawn_exit(void) +{ + pthread_exit(NULL); +} + +/** Return an integer representing this thread. */ +unsigned long +tor_get_thread_id(void) +{ + union { + pthread_t thr; + unsigned long id; + } r; + r.thr = pthread_self(); + return r.id; +} + +/* Conditions. */ + +/** Initialize an already-allocated condition variable. */ +int +tor_cond_init(tor_cond_t *cond) +{ + pthread_condattr_t condattr; + + memset(cond, 0, sizeof(tor_cond_t)); + /* Default condition attribute. Might be used if clock monotonic is + * available else this won't affect anything. */ + if (pthread_condattr_init(&condattr)) { + return -1; + } + +#if defined(HAVE_CLOCK_GETTIME) +#if defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && \ + defined(CLOCK_MONOTONIC) + /* Use monotonic time so when we timedwait() on it, any clock adjustment + * won't affect the timeout value. */ + if (pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC)) { + return -1; + } +#define USE_COND_CLOCK CLOCK_MONOTONIC +#else /* !(defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && ...) */ + /* On OSX Sierra, there is no pthread_condattr_setclock, so we are stuck + * with the realtime clock. + */ +#define USE_COND_CLOCK CLOCK_REALTIME +#endif /* defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && ... */ +#endif /* defined(HAVE_CLOCK_GETTIME) */ + if (pthread_cond_init(&cond->cond, &condattr)) { + return -1; + } + return 0; +} + +/** Release all resources held by <b>cond</b>, but do not free <b>cond</b> + * itself. */ +void +tor_cond_uninit(tor_cond_t *cond) +{ + if (pthread_cond_destroy(&cond->cond)) { + // LCOV_EXCL_START + log_warn(LD_GENERAL,"Error freeing condition: %s", strerror(errno)); + return; + // LCOV_EXCL_STOP + } +} +/** Wait until one of the tor_cond_signal functions is called on <b>cond</b>. + * (If <b>tv</b> is set, and that amount of time passes with no signal to + * <b>cond</b>, return anyway. All waiters on the condition must wait holding + * the same <b>mutex</b>. All signallers should hold that mutex. The mutex + * needs to have been allocated with tor_mutex_init_for_cond(). + * + * Returns 0 on success, -1 on failure, 1 on timeout. */ +int +tor_cond_wait(tor_cond_t *cond, tor_mutex_t *mutex, const struct timeval *tv) +{ + int r; + if (tv == NULL) { + while (1) { + r = pthread_cond_wait(&cond->cond, &mutex->mutex); + if (r == EINTR) { + /* EINTR should be impossible according to POSIX, but POSIX, like the + * Pirate's Code, is apparently treated "more like what you'd call + * guidelines than actual rules." */ + continue; // LCOV_EXCL_LINE + } + return r ? -1 : 0; + } + } else { + struct timeval tvnow, tvsum; + struct timespec ts; + while (1) { +#if defined(HAVE_CLOCK_GETTIME) && defined(USE_COND_CLOCK) + if (clock_gettime(USE_COND_CLOCK, &ts) < 0) { + return -1; + } + tvnow.tv_sec = ts.tv_sec; + tvnow.tv_usec = (int)(ts.tv_nsec / 1000); + timeradd(tv, &tvnow, &tvsum); +#else /* !(defined(HAVE_CLOCK_GETTIME) && defined(USE_COND_CLOCK)) */ + if (gettimeofday(&tvnow, NULL) < 0) + return -1; + timeradd(tv, &tvnow, &tvsum); +#endif /* defined(HAVE_CLOCK_GETTIME) && defined(USE_COND_CLOCK) */ + + ts.tv_sec = tvsum.tv_sec; + ts.tv_nsec = tvsum.tv_usec * 1000; + + r = pthread_cond_timedwait(&cond->cond, &mutex->mutex, &ts); + if (r == 0) + return 0; + else if (r == ETIMEDOUT) + return 1; + else if (r == EINTR) + continue; + else + return -1; + } + } +} +/** Wake up one of the waiters on <b>cond</b>. */ +void +tor_cond_signal_one(tor_cond_t *cond) +{ + pthread_cond_signal(&cond->cond); +} +/** Wake up all of the waiters on <b>cond</b>. */ +void +tor_cond_signal_all(tor_cond_t *cond) +{ + pthread_cond_broadcast(&cond->cond); +} + +int +tor_threadlocal_init(tor_threadlocal_t *threadlocal) +{ + int err = pthread_key_create(&threadlocal->key, NULL); + return err ? -1 : 0; +} + +void +tor_threadlocal_destroy(tor_threadlocal_t *threadlocal) +{ + pthread_key_delete(threadlocal->key); + memset(threadlocal, 0, sizeof(tor_threadlocal_t)); +} + +void * +tor_threadlocal_get(tor_threadlocal_t *threadlocal) +{ + return pthread_getspecific(threadlocal->key); +} + +void +tor_threadlocal_set(tor_threadlocal_t *threadlocal, void *value) +{ + int err = pthread_setspecific(threadlocal->key, value); + tor_assert(err == 0); +} + +/** Set up common structures for use by threading. */ +void +tor_threads_init(void) +{ + if (!threads_initialized) { + tor_locking_init(); + const int ret1 = pthread_attr_init(&attr_detached); + tor_assert(ret1 == 0); +#ifndef PTHREAD_CREATE_DETACHED +#define PTHREAD_CREATE_DETACHED 1 +#endif + const int ret2 = + pthread_attr_setdetachstate(&attr_detached, PTHREAD_CREATE_DETACHED); + tor_assert(ret2 == 0); + threads_initialized = 1; + set_main_thread(); + } +} diff --git a/src/lib/thread/compat_threads.c b/src/lib/thread/compat_threads.c new file mode 100644 index 0000000000..94ab021c52 --- /dev/null +++ b/src/lib/thread/compat_threads.c @@ -0,0 +1,111 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_threads.c + * + * \brief Cross-platform threading and inter-thread communication logic. + * (Platform-specific parts are written in the other compat_*threads + * modules.) + */ + +#include "orconfig.h" +#include <stdlib.h> +#include "lib/thread/threads.h" + +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +#include <string.h> + +/** Allocate and return a new condition variable. */ +tor_cond_t * +tor_cond_new(void) +{ + tor_cond_t *cond = tor_malloc(sizeof(tor_cond_t)); + if (BUG(tor_cond_init(cond)<0)) + tor_free(cond); // LCOV_EXCL_LINE + return cond; +} + +/** Free all storage held in <b>c</b>. */ +void +tor_cond_free_(tor_cond_t *c) +{ + if (!c) + return; + tor_cond_uninit(c); + tor_free(c); +} + +/** Identity of the "main" thread */ +static unsigned long main_thread_id = -1; + +/** Start considering the current thread to be the 'main thread'. This has + * no effect on anything besides in_main_thread(). */ +void +set_main_thread(void) +{ + main_thread_id = tor_get_thread_id(); +} +/** Return true iff called from the main thread. */ +int +in_main_thread(void) +{ + return main_thread_id == tor_get_thread_id(); +} + +#ifndef HAVE_WORKING_STDATOMIC +/** Initialize a new atomic counter with the value 0 */ +void +atomic_counter_init(atomic_counter_t *counter) +{ + memset(counter, 0, sizeof(*counter)); + tor_mutex_init_nonrecursive(&counter->mutex); +} +/** Clean up all resources held by an atomic counter. */ +void +atomic_counter_destroy(atomic_counter_t *counter) +{ + tor_mutex_uninit(&counter->mutex); + memset(counter, 0, sizeof(*counter)); +} +/** Add a value to an atomic counter. */ +void +atomic_counter_add(atomic_counter_t *counter, size_t add) +{ + tor_mutex_acquire(&counter->mutex); + counter->val += add; + tor_mutex_release(&counter->mutex); +} +/** Subtract a value from an atomic counter. */ +void +atomic_counter_sub(atomic_counter_t *counter, size_t sub) +{ + // this relies on unsigned overflow, but that's fine. + atomic_counter_add(counter, -sub); +} +/** Return the current value of an atomic counter */ +size_t +atomic_counter_get(atomic_counter_t *counter) +{ + size_t val; + tor_mutex_acquire(&counter->mutex); + val = counter->val; + tor_mutex_release(&counter->mutex); + return val; +} +/** Replace the value of an atomic counter; return the old one. */ +size_t +atomic_counter_exchange(atomic_counter_t *counter, size_t newval) +{ + size_t oldval; + tor_mutex_acquire(&counter->mutex); + oldval = counter->val; + counter->val = newval; + tor_mutex_release(&counter->mutex); + return oldval; +} +#endif /* !defined(HAVE_WORKING_STDATOMIC) */ diff --git a/src/lib/thread/compat_winthreads.c b/src/lib/thread/compat_winthreads.c new file mode 100644 index 0000000000..f0b1430e84 --- /dev/null +++ b/src/lib/thread/compat_winthreads.c @@ -0,0 +1,223 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_winthreads.c + * + * \brief Implementation for the windows-based multithreading backend + * functions. + */ + +#ifdef _WIN32 + +#include <windows.h> +#include <process.h> +#include "lib/thread/threads.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/log/win32err.h" + +/* This value is more or less total cargo-cult */ +#define SPIN_COUNT 2000 + +/** Minimalist interface to run a void function in the background. On + * Unix calls fork, on win32 calls beginthread. Returns -1 on failure. + * func should not return, but rather should call spawn_exit. + * + * NOTE: if <b>data</b> is used, it should not be allocated on the stack, + * since in a multithreaded environment, there is no way to be sure that + * the caller's stack will still be around when the called function is + * running. + */ +int +spawn_func(void (*func)(void *), void *data) +{ + int rv; + rv = (int)_beginthread(func, 0, data); + if (rv == (int)-1) + return -1; + return 0; +} + +/** End the current thread/process. + */ +void +spawn_exit(void) +{ + _endthread(); + // LCOV_EXCL_START + //we should never get here. my compiler thinks that _endthread returns, this + //is an attempt to fool it. + tor_assert(0); + _exit(0); // exit ok: unreachable. + // LCOV_EXCL_STOP +} + +unsigned long +tor_get_thread_id(void) +{ + return (unsigned long)GetCurrentThreadId(); +} + +int +tor_cond_init(tor_cond_t *cond) +{ + memset(cond, 0, sizeof(tor_cond_t)); + if (InitializeCriticalSectionAndSpinCount(&cond->lock, SPIN_COUNT)==0) { + return -1; + } + if ((cond->event = CreateEvent(NULL,TRUE,FALSE,NULL)) == NULL) { + DeleteCriticalSection(&cond->lock); + return -1; + } + cond->n_waiting = cond->n_to_wake = cond->generation = 0; + return 0; +} +void +tor_cond_uninit(tor_cond_t *cond) +{ + DeleteCriticalSection(&cond->lock); + CloseHandle(cond->event); +} + +static void +tor_cond_signal_impl(tor_cond_t *cond, int broadcast) +{ + EnterCriticalSection(&cond->lock); + if (broadcast) + cond->n_to_wake = cond->n_waiting; + else + ++cond->n_to_wake; + cond->generation++; + SetEvent(cond->event); + LeaveCriticalSection(&cond->lock); +} +void +tor_cond_signal_one(tor_cond_t *cond) +{ + tor_cond_signal_impl(cond, 0); +} +void +tor_cond_signal_all(tor_cond_t *cond) +{ + tor_cond_signal_impl(cond, 1); +} + +int +tor_threadlocal_init(tor_threadlocal_t *threadlocal) +{ + threadlocal->index = TlsAlloc(); + return (threadlocal->index == TLS_OUT_OF_INDEXES) ? -1 : 0; +} + +void +tor_threadlocal_destroy(tor_threadlocal_t *threadlocal) +{ + TlsFree(threadlocal->index); + memset(threadlocal, 0, sizeof(tor_threadlocal_t)); +} + +void * +tor_threadlocal_get(tor_threadlocal_t *threadlocal) +{ + void *value = TlsGetValue(threadlocal->index); + if (value == NULL) { + DWORD err = GetLastError(); + if (err != ERROR_SUCCESS) { + char *msg = format_win32_error(err); + log_err(LD_GENERAL, "Error retrieving thread-local value: %s", msg); + tor_free(msg); + tor_assert(err == ERROR_SUCCESS); + } + } + return value; +} + +void +tor_threadlocal_set(tor_threadlocal_t *threadlocal, void *value) +{ + BOOL ok = TlsSetValue(threadlocal->index, value); + if (!ok) { + DWORD err = GetLastError(); + char *msg = format_win32_error(err); + log_err(LD_GENERAL, "Error adjusting thread-local value: %s", msg); + tor_free(msg); + tor_assert(ok); + } +} + +int +tor_cond_wait(tor_cond_t *cond, tor_mutex_t *lock_, const struct timeval *tv) +{ + CRITICAL_SECTION *lock = &lock_->mutex; + int generation_at_start; + int waiting = 1; + int result = -1; + DWORD ms = INFINITE, ms_orig = INFINITE, startTime, endTime; + if (tv) + ms_orig = ms = tv->tv_sec*1000 + (tv->tv_usec+999)/1000; + + EnterCriticalSection(&cond->lock); + ++cond->n_waiting; + generation_at_start = cond->generation; + LeaveCriticalSection(&cond->lock); + + LeaveCriticalSection(lock); + + startTime = GetTickCount(); + do { + DWORD res; + res = WaitForSingleObject(cond->event, ms); + EnterCriticalSection(&cond->lock); + if (cond->n_to_wake && + cond->generation != generation_at_start) { + --cond->n_to_wake; + --cond->n_waiting; + result = 0; + waiting = 0; + goto out; + } else if (res != WAIT_OBJECT_0) { + result = (res==WAIT_TIMEOUT) ? 1 : -1; + --cond->n_waiting; + waiting = 0; + goto out; + } else if (ms != INFINITE) { + endTime = GetTickCount(); + if (startTime + ms_orig <= endTime) { + result = 1; /* Timeout */ + --cond->n_waiting; + waiting = 0; + goto out; + } else { + ms = startTime + ms_orig - endTime; + } + } + /* If we make it here, we are still waiting. */ + if (cond->n_to_wake == 0) { + /* There is nobody else who should wake up; reset + * the event. */ + ResetEvent(cond->event); + } + out: + LeaveCriticalSection(&cond->lock); + } while (waiting); + + EnterCriticalSection(lock); + + EnterCriticalSection(&cond->lock); + if (!cond->n_waiting) + ResetEvent(cond->event); + LeaveCriticalSection(&cond->lock); + + return result; +} + +void +tor_threads_init(void) +{ + set_main_thread(); +} + +#endif /* defined(_WIN32) */ diff --git a/src/lib/thread/include.am b/src/lib/thread/include.am new file mode 100644 index 0000000000..9ec23d166e --- /dev/null +++ b/src/lib/thread/include.am @@ -0,0 +1,27 @@ + +noinst_LIBRARIES += src/lib/libtor-thread.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-thread-testing.a +endif + +if THREADS_PTHREADS +threads_impl_source=src/lib/thread/compat_pthreads.c +endif +if THREADS_WIN32 +threads_impl_source=src/lib/thread/compat_winthreads.c +endif + +src_lib_libtor_thread_a_SOURCES = \ + src/lib/thread/compat_threads.c \ + src/lib/thread/numcpus.c \ + $(threads_impl_source) + +src_lib_libtor_thread_testing_a_SOURCES = \ + $(src_lib_libtor_thread_a_SOURCES) +src_lib_libtor_thread_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_thread_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/thread/threads.h \ + src/lib/thread/numcpus.h diff --git a/src/lib/thread/numcpus.c b/src/lib/thread/numcpus.c new file mode 100644 index 0000000000..b293d965d2 --- /dev/null +++ b/src/lib/thread/numcpus.c @@ -0,0 +1,98 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file numcpus.c + * \brief Compute the number of CPUs configured on this system. + **/ + +#include "orconfig.h" +#include "lib/thread/numcpus.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef _WIN32 +#include <windows.h> +#endif + +#include <stdlib.h> + +/** Implementation logic for compute_num_cpus(). */ +static int +compute_num_cpus_impl(void) +{ +#ifdef _WIN32 + SYSTEM_INFO info; + memset(&info, 0, sizeof(info)); + GetSystemInfo(&info); + if (info.dwNumberOfProcessors >= 1 && info.dwNumberOfProcessors < INT_MAX) + return (int)info.dwNumberOfProcessors; + else + return -1; +#elif defined(HAVE_SYSCONF) +#ifdef _SC_NPROCESSORS_CONF + long cpus_conf = sysconf(_SC_NPROCESSORS_CONF); +#else + long cpus_conf = -1; +#endif +#ifdef _SC_NPROCESSORS_ONLN + long cpus_onln = sysconf(_SC_NPROCESSORS_ONLN); +#else + long cpus_onln = -1; +#endif + long cpus = -1; + + if (cpus_conf > 0 && cpus_onln < 0) { + cpus = cpus_conf; + } else if (cpus_onln > 0 && cpus_conf < 0) { + cpus = cpus_onln; + } else if (cpus_onln > 0 && cpus_conf > 0) { + if (cpus_onln < cpus_conf) { + log_notice(LD_GENERAL, "I think we have %ld CPUS, but only %ld of them " + "are available. Telling Tor to only use %ld. You can over" + "ride this with the NumCPUs option", + cpus_conf, cpus_onln, cpus_onln); + } + cpus = cpus_onln; + } + + if (cpus >= 1 && cpus < INT_MAX) + return (int)cpus; + else + return -1; +#else + return -1; +#endif /* defined(_WIN32) || ... */ +} + +#define MAX_DETECTABLE_CPUS 16 + +/** Return how many CPUs we are running with. We assume that nobody is + * using hot-swappable CPUs, so we don't recompute this after the first + * time. Return -1 if we don't know how to tell the number of CPUs on this + * system. + */ +int +compute_num_cpus(void) +{ + static int num_cpus = -2; + if (num_cpus == -2) { + num_cpus = compute_num_cpus_impl(); + tor_assert(num_cpus != -2); + if (num_cpus > MAX_DETECTABLE_CPUS) { + /* LCOV_EXCL_START */ + log_notice(LD_GENERAL, "Wow! I detected that you have %d CPUs. I " + "will not autodetect any more than %d, though. If you " + "want to configure more, set NumCPUs in your torrc", + num_cpus, MAX_DETECTABLE_CPUS); + num_cpus = MAX_DETECTABLE_CPUS; + /* LCOV_EXCL_STOP */ + } + } + return num_cpus; +} diff --git a/src/lib/thread/numcpus.h b/src/lib/thread/numcpus.h new file mode 100644 index 0000000000..3f0a29ce7c --- /dev/null +++ b/src/lib/thread/numcpus.h @@ -0,0 +1,16 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file numcpus.h + * \brief Header for numcpus.c + **/ + +#ifndef TOR_NUMCPUS_H +#define TOR_NUMCPUS_H + +int compute_num_cpus(void); + +#endif diff --git a/src/lib/thread/threads.h b/src/lib/thread/threads.h new file mode 100644 index 0000000000..ecf60641b5 --- /dev/null +++ b/src/lib/thread/threads.h @@ -0,0 +1,168 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file threads.h + * \brief Header for threads.c + **/ + +#ifndef TOR_COMPAT_THREADS_H +#define TOR_COMPAT_THREADS_H + +#include "orconfig.h" +#include "lib/cc/torint.h" +#include "lib/testsupport/testsupport.h" +#include "lib/lock/compat_mutex.h" + +#if defined(HAVE_STDATOMIC_H) && defined(STDATOMIC_WORKS) +#define HAVE_WORKING_STDATOMIC +#endif + +#ifdef HAVE_WORKING_STDATOMIC +#include <stdatomic.h> +#endif + +struct timeval; + +int spawn_func(void (*func)(void *), void *data); +void spawn_exit(void) ATTR_NORETURN; + +unsigned long tor_get_thread_id(void); +void tor_threads_init(void); + +/** Conditions need nonrecursive mutexes with pthreads. */ +#define tor_mutex_init_for_cond(m) tor_mutex_init_nonrecursive(m) + +void set_main_thread(void); +int in_main_thread(void); + +typedef struct tor_cond_t { +#ifdef USE_PTHREADS + pthread_cond_t cond; +#elif defined(USE_WIN32_THREADS) + HANDLE event; + + CRITICAL_SECTION lock; + int n_waiting; + int n_to_wake; + int generation; +#else +#error no known condition implementation. +#endif /* defined(USE_PTHREADS) || ... */ +} tor_cond_t; + +tor_cond_t *tor_cond_new(void); +void tor_cond_free_(tor_cond_t *cond); +#define tor_cond_free(c) FREE_AND_NULL(tor_cond_t, tor_cond_free_, (c)) +int tor_cond_init(tor_cond_t *cond); +void tor_cond_uninit(tor_cond_t *cond); +int tor_cond_wait(tor_cond_t *cond, tor_mutex_t *mutex, + const struct timeval *tv); +void tor_cond_signal_one(tor_cond_t *cond); +void tor_cond_signal_all(tor_cond_t *cond); + +typedef struct tor_threadlocal_s { +#ifdef _WIN32 + DWORD index; +#else + pthread_key_t key; +#endif +} tor_threadlocal_t; + +/** Initialize a thread-local variable. + * + * After you call this function on a tor_threadlocal_t, you can call + * tor_threadlocal_set to change the current value of this variable for the + * current thread, and tor_threadlocal_get to retrieve the current value for + * the current thread. Each thread has its own value. + **/ +int tor_threadlocal_init(tor_threadlocal_t *threadlocal); +/** + * Release all resource associated with a thread-local variable. + */ +void tor_threadlocal_destroy(tor_threadlocal_t *threadlocal); +/** + * Return the current value of a thread-local variable for this thread. + * + * It's undefined behavior to use this function if the threadlocal hasn't + * been initialized, or has been destroyed. + */ +void *tor_threadlocal_get(tor_threadlocal_t *threadlocal); +/** + * Change the current value of a thread-local variable for this thread to + * <b>value</b>. + * + * It's undefined behavior to use this function if the threadlocal hasn't + * been initialized, or has been destroyed. + */ +void tor_threadlocal_set(tor_threadlocal_t *threadlocal, void *value); + +/** + * Atomic counter type; holds a size_t value. + */ +#ifdef HAVE_WORKING_STDATOMIC +typedef struct atomic_counter_t { + atomic_size_t val; +} atomic_counter_t; +#define ATOMIC_LINKAGE static +#else /* !(defined(HAVE_WORKING_STDATOMIC)) */ +typedef struct atomic_counter_t { + tor_mutex_t mutex; + size_t val; +} atomic_counter_t; +#define ATOMIC_LINKAGE +#endif /* defined(HAVE_WORKING_STDATOMIC) */ + +ATOMIC_LINKAGE void atomic_counter_init(atomic_counter_t *counter); +ATOMIC_LINKAGE void atomic_counter_destroy(atomic_counter_t *counter); +ATOMIC_LINKAGE void atomic_counter_add(atomic_counter_t *counter, size_t add); +ATOMIC_LINKAGE void atomic_counter_sub(atomic_counter_t *counter, size_t sub); +ATOMIC_LINKAGE size_t atomic_counter_get(atomic_counter_t *counter); +ATOMIC_LINKAGE size_t atomic_counter_exchange(atomic_counter_t *counter, + size_t newval); +#undef ATOMIC_LINKAGE + +#ifdef HAVE_WORKING_STDATOMIC +/** Initialize a new atomic counter with the value 0 */ +static inline void +atomic_counter_init(atomic_counter_t *counter) +{ + atomic_init(&counter->val, 0); +} +/** Clean up all resources held by an atomic counter. */ +static inline void +atomic_counter_destroy(atomic_counter_t *counter) +{ + (void)counter; +} +/** Add a value to an atomic counter. */ +static inline void +atomic_counter_add(atomic_counter_t *counter, size_t add) +{ + (void) atomic_fetch_add(&counter->val, add); +} +/** Subtract a value from an atomic counter. */ +static inline void +atomic_counter_sub(atomic_counter_t *counter, size_t sub) +{ + (void) atomic_fetch_sub(&counter->val, sub); +} +/** Return the current value of an atomic counter */ +static inline size_t +atomic_counter_get(atomic_counter_t *counter) +{ + return atomic_load(&counter->val); +} +/** Replace the value of an atomic counter; return the old one. */ +static inline size_t +atomic_counter_exchange(atomic_counter_t *counter, size_t newval) +{ + return atomic_exchange(&counter->val, newval); +} + +#else /* !(defined(HAVE_WORKING_STDATOMIC)) */ +#endif /* defined(HAVE_WORKING_STDATOMIC) */ + +#endif /* !defined(TOR_COMPAT_THREADS_H) */ diff --git a/src/lib/time/.may_include b/src/lib/time/.may_include new file mode 100644 index 0000000000..2c7e37a836 --- /dev/null +++ b/src/lib/time/.may_include @@ -0,0 +1,11 @@ +orconfig.h + +lib/cc/*.h +lib/err/*.h +lib/intmath/*.h +lib/log/*.h +lib/time/*.h +lib/wallclock/*.h + +# For load_windows_system_lib. +lib/fs/winlib.h
\ No newline at end of file diff --git a/src/lib/time/compat_time.c b/src/lib/time/compat_time.c new file mode 100644 index 0000000000..3d1ffa7af4 --- /dev/null +++ b/src/lib/time/compat_time.c @@ -0,0 +1,869 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_time.c + * \brief Portable wrappers for finding out the current time, running + * timers, etc. + **/ + +#define COMPAT_TIME_PRIVATE +#include "lib/time/compat_time.h" + +#include "lib/err/torerr.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/intmath/muldiv.h" +#include "lib/intmath/bits.h" +#include "lib/fs/winlib.h" +#include "lib/wallclock/timeval.h" + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#endif + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef TOR_UNIT_TESTS +#if !defined(HAVE_USLEEP) && defined(HAVE_SYS_SELECT_H) +/* as fallback implementation for tor_sleep_msec */ +#include <sys/select.h> +#endif +#endif /* defined(TOR_UNIT_TESTS) */ + +#ifdef __APPLE__ +#include <mach/mach_time.h> +#endif + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#ifdef _WIN32 +#undef HAVE_CLOCK_GETTIME +#endif + +#ifdef TOR_UNIT_TESTS +/** Delay for <b>msec</b> milliseconds. Only used in tests. */ +void +tor_sleep_msec(int msec) +{ +#ifdef _WIN32 + Sleep(msec); +#elif defined(HAVE_USLEEP) + sleep(msec / 1000); + /* Some usleep()s hate sleeping more than 1 sec */ + usleep((msec % 1000) * 1000); +#elif defined(HAVE_SYS_SELECT_H) + struct timeval tv = { msec / 1000, (msec % 1000) * 1000}; + select(0, NULL, NULL, NULL, &tv); +#else + sleep(CEIL_DIV(msec, 1000)); +#endif /* defined(_WIN32) || ... */ +} +#endif /* defined(TOR_UNIT_TESTS) */ + +#define ONE_MILLION ((int64_t) (1000 * 1000)) +#define ONE_BILLION ((int64_t) (1000 * 1000 * 1000)) + +/** True iff monotime_init has been called. */ +static int monotime_initialized = 0; + +static monotime_t initialized_at; +#ifdef MONOTIME_COARSE_FN_IS_DIFFERENT +static monotime_coarse_t initialized_at_coarse; +#endif + +#ifdef TOR_UNIT_TESTS +/** True if we are running unit tests and overriding the current monotonic + * time. Note that mocked monotonic time might not be monotonic. + */ +static int monotime_mocking_enabled = 0; +static monotime_t initialized_at_saved; + +static int64_t mock_time_nsec = 0; +#ifdef MONOTIME_COARSE_FN_IS_DIFFERENT +static int64_t mock_time_nsec_coarse = 0; +static monotime_coarse_t initialized_at_coarse_saved; +#endif + +void +monotime_enable_test_mocking(void) +{ + if (BUG(monotime_initialized == 0)) { + monotime_init(); + } + + tor_assert_nonfatal(monotime_mocking_enabled == 0); + monotime_mocking_enabled = 1; + memcpy(&initialized_at_saved, + &initialized_at, sizeof(monotime_t)); + memset(&initialized_at, 0, sizeof(monotime_t)); +#ifdef MONOTIME_COARSE_FN_IS_DIFFERENT + memcpy(&initialized_at_coarse_saved, + &initialized_at_coarse, sizeof(monotime_coarse_t)); + memset(&initialized_at_coarse, 0, sizeof(monotime_coarse_t)); +#endif +} + +void +monotime_disable_test_mocking(void) +{ + tor_assert_nonfatal(monotime_mocking_enabled == 1); + monotime_mocking_enabled = 0; + + memcpy(&initialized_at, + &initialized_at_saved, sizeof(monotime_t)); +#ifdef MONOTIME_COARSE_FN_IS_DIFFERENT + memcpy(&initialized_at_coarse, + &initialized_at_coarse_saved, sizeof(monotime_coarse_t)); +#endif +} + +void +monotime_set_mock_time_nsec(int64_t nsec) +{ + tor_assert_nonfatal(monotime_mocking_enabled == 1); + mock_time_nsec = nsec; +} + +#ifdef MONOTIME_COARSE_FN_IS_DIFFERENT +void +monotime_coarse_set_mock_time_nsec(int64_t nsec) +{ + tor_assert_nonfatal(monotime_mocking_enabled == 1); + mock_time_nsec_coarse = nsec; +} +#endif /* defined(MONOTIME_COARSE_FN_IS_DIFFERENT) */ +#endif /* defined(TOR_UNIT_TESTS) */ + +/* "ratchet" functions for monotonic time. */ + +#if defined(_WIN32) || defined(TOR_UNIT_TESTS) + +/** Protected by lock: last value returned by monotime_get(). */ +static int64_t last_pctr = 0; +/** Protected by lock: offset we must add to monotonic time values. */ +static int64_t pctr_offset = 0; +/* If we are using GetTickCount(), how many times has it rolled over? */ +static uint32_t rollover_count = 0; +/* If we are using GetTickCount(), what's the last value it returned? */ +static int64_t last_tick_count = 0; + +/** Helper for windows: Called with a sequence of times that are supposed + * to be monotonic; increments them as appropriate so that they actually + * _are_ monotonic. + * + * Caller must hold lock. */ +STATIC int64_t +ratchet_performance_counter(int64_t count_raw) +{ + /* must hold lock */ + const int64_t count_adjusted = count_raw + pctr_offset; + + if (PREDICT_UNLIKELY(count_adjusted < last_pctr)) { + /* Monotonicity failed! Pretend no time elapsed. */ + pctr_offset = last_pctr - count_raw; + return last_pctr; + } else { + last_pctr = count_adjusted; + return count_adjusted; + } +} + +STATIC int64_t +ratchet_coarse_performance_counter(const int64_t count_raw) +{ + int64_t count = count_raw + (((int64_t)rollover_count) << 32); + while (PREDICT_UNLIKELY(count < last_tick_count)) { + ++rollover_count; + count = count_raw + (((int64_t)rollover_count) << 32); + } + last_tick_count = count; + return count; +} +#endif /* defined(_WIN32) || defined(TOR_UNIT_TESTS) */ + +#if defined(MONOTIME_USING_GETTIMEOFDAY) || defined(TOR_UNIT_TESTS) +static struct timeval last_timeofday = { 0, 0 }; +static struct timeval timeofday_offset = { 0, 0 }; + +/** Helper for gettimeofday(): Called with a sequence of times that are + * supposed to be monotonic; increments them as appropriate so that they + * actually _are_ monotonic. + * + * Caller must hold lock. */ +STATIC void +ratchet_timeval(const struct timeval *timeval_raw, struct timeval *out) +{ + /* must hold lock */ + timeradd(timeval_raw, &timeofday_offset, out); + if (PREDICT_UNLIKELY(timercmp(out, &last_timeofday, OP_LT))) { + /* time ran backwards. Instead, declare that no time occurred. */ + timersub(&last_timeofday, timeval_raw, &timeofday_offset); + memcpy(out, &last_timeofday, sizeof(struct timeval)); + } else { + memcpy(&last_timeofday, out, sizeof(struct timeval)); + } +} +#endif /* defined(MONOTIME_USING_GETTIMEOFDAY) || defined(TOR_UNIT_TESTS) */ + +#ifdef TOR_UNIT_TESTS +/** For testing: reset all the ratchets */ +void +monotime_reset_ratchets_for_testing(void) +{ + last_pctr = pctr_offset = last_tick_count = 0; + rollover_count = 0; + memset(&last_timeofday, 0, sizeof(struct timeval)); + memset(&timeofday_offset, 0, sizeof(struct timeval)); +} +#endif /* defined(TOR_UNIT_TESTS) */ + +#ifdef __APPLE__ + +/** Initialized on startup: tells is how to convert from ticks to + * nanoseconds. + */ +static struct mach_timebase_info mach_time_info; +static struct mach_timebase_info mach_time_info_msec_cvt; +static int32_t mach_time_msec_cvt_threshold; +static int monotime_shift = 0; + +static void +monotime_init_internal(void) +{ + tor_assert(!monotime_initialized); + int r = mach_timebase_info(&mach_time_info); + tor_assert(r == 0); + tor_assert(mach_time_info.denom != 0); + + { + // approximate only. + uint64_t ns_per_tick = mach_time_info.numer / mach_time_info.denom; + uint64_t ms_per_tick = ns_per_tick * ONE_MILLION; + // requires that tor_log2(0) == 0. + monotime_shift = tor_log2(ms_per_tick); + } + { + // For converting ticks to milliseconds in a 32-bit-friendly way, we + // will first right-shift by 20, and then multiply by 2048/1953, since + // (1<<20) * 1953/2048 is about 1e6. We precompute a new numerator and + // denominator here to avoid multiple multiplies. + mach_time_info_msec_cvt.numer = mach_time_info.numer * 2048; + mach_time_info_msec_cvt.denom = mach_time_info.denom * 1953; + // For any value above this amount, we should divide before multiplying, + // to avoid overflow. For a value below this, we should multiply + // before dividing, to improve accuracy. + mach_time_msec_cvt_threshold = INT32_MAX / mach_time_info_msec_cvt.numer; + } +} + +/** + * Set "out" to the most recent monotonic time value + */ +void +monotime_get(monotime_t *out) +{ +#ifdef TOR_UNIT_TESTS + if (monotime_mocking_enabled) { + out->abstime_ = (mock_time_nsec * mach_time_info.denom) + / mach_time_info.numer; + return; + } +#endif /* defined(TOR_UNIT_TESTS) */ + out->abstime_ = mach_absolute_time(); +} + +#if defined(HAVE_MACH_APPROXIMATE_TIME) +void +monotime_coarse_get(monotime_coarse_t *out) +{ +#ifdef TOR_UNIT_TESTS + if (monotime_mocking_enabled) { + out->abstime_ = (mock_time_nsec_coarse * mach_time_info.denom) + / mach_time_info.numer; + return; + } +#endif /* defined(TOR_UNIT_TESTS) */ + out->abstime_ = mach_approximate_time(); +} +#endif + +/** + * Return the number of nanoseconds between <b>start</b> and <b>end</b>. + */ +int64_t +monotime_diff_nsec(const monotime_t *start, + const monotime_t *end) +{ + if (BUG(mach_time_info.denom == 0)) { + monotime_init(); + } + const int64_t diff_ticks = end->abstime_ - start->abstime_; + const int64_t diff_nsec = + (diff_ticks * mach_time_info.numer) / mach_time_info.denom; + return diff_nsec; +} + +int32_t +monotime_coarse_diff_msec32_(const monotime_coarse_t *start, + const monotime_coarse_t *end) +{ + if (BUG(mach_time_info.denom == 0)) { + monotime_init(); + } + const int64_t diff_ticks = end->abstime_ - start->abstime_; + + /* We already require in di_ops.c that right-shift performs a sign-extend. */ + const int32_t diff_microticks = (int32_t)(diff_ticks >> 20); + + if (diff_microticks >= mach_time_msec_cvt_threshold) { + return (diff_microticks / mach_time_info_msec_cvt.denom) * + mach_time_info_msec_cvt.numer; + } else { + return (diff_microticks * mach_time_info_msec_cvt.numer) / + mach_time_info_msec_cvt.denom; + } +} + +uint32_t +monotime_coarse_to_stamp(const monotime_coarse_t *t) +{ + return (uint32_t)(t->abstime_ >> monotime_shift); +} + +int +monotime_is_zero(const monotime_t *val) +{ + return val->abstime_ == 0; +} + +void +monotime_add_msec(monotime_t *out, const monotime_t *val, uint32_t msec) +{ + const uint64_t nsec = msec * ONE_MILLION; + const uint64_t ticks = (nsec * mach_time_info.denom) / mach_time_info.numer; + out->abstime_ = val->abstime_ + ticks; +} + +/* end of "__APPLE__" */ +#elif defined(HAVE_CLOCK_GETTIME) + +#ifdef CLOCK_MONOTONIC_COARSE +/** + * Which clock should we use for coarse-grained monotonic time? By default + * this is CLOCK_MONOTONIC_COARSE, but it might not work -- for example, + * if we're compiled with newer Linux headers and then we try to run on + * an old Linux kernel. In that case, we will fall back to CLOCK_MONOTONIC. + */ +static int clock_monotonic_coarse = CLOCK_MONOTONIC_COARSE; +#endif /* defined(CLOCK_MONOTONIC_COARSE) */ + +static void +monotime_init_internal(void) +{ +#ifdef CLOCK_MONOTONIC_COARSE + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC_COARSE, &ts) < 0) { + log_info(LD_GENERAL, "CLOCK_MONOTONIC_COARSE isn't working (%s); " + "falling back to CLOCK_MONOTONIC.", strerror(errno)); + clock_monotonic_coarse = CLOCK_MONOTONIC; + } +#endif /* defined(CLOCK_MONOTONIC_COARSE) */ +} + +void +monotime_get(monotime_t *out) +{ +#ifdef TOR_UNIT_TESTS + if (monotime_mocking_enabled) { + out->ts_.tv_sec = (time_t) (mock_time_nsec / ONE_BILLION); + out->ts_.tv_nsec = (int) (mock_time_nsec % ONE_BILLION); + return; + } +#endif /* defined(TOR_UNIT_TESTS) */ + int r = clock_gettime(CLOCK_MONOTONIC, &out->ts_); + tor_assert(r == 0); +} + +#ifdef CLOCK_MONOTONIC_COARSE +void +monotime_coarse_get(monotime_coarse_t *out) +{ +#ifdef TOR_UNIT_TESTS + if (monotime_mocking_enabled) { + out->ts_.tv_sec = (time_t) (mock_time_nsec_coarse / ONE_BILLION); + out->ts_.tv_nsec = (int) (mock_time_nsec_coarse % ONE_BILLION); + return; + } +#endif /* defined(TOR_UNIT_TESTS) */ + int r = clock_gettime(clock_monotonic_coarse, &out->ts_); + if (PREDICT_UNLIKELY(r < 0) && + errno == EINVAL && + clock_monotonic_coarse == CLOCK_MONOTONIC_COARSE) { + /* We should have caught this at startup in monotime_init_internal! + */ + log_warn(LD_BUG, "Falling back to non-coarse monotonic time %s initial " + "system start?", monotime_initialized?"after":"without"); + clock_monotonic_coarse = CLOCK_MONOTONIC; + r = clock_gettime(clock_monotonic_coarse, &out->ts_); + } + + tor_assert(r == 0); +} +#endif /* defined(CLOCK_MONOTONIC_COARSE) */ + +int64_t +monotime_diff_nsec(const monotime_t *start, + const monotime_t *end) +{ + const int64_t diff_sec = end->ts_.tv_sec - start->ts_.tv_sec; + const int64_t diff_nsec = diff_sec * ONE_BILLION + + (end->ts_.tv_nsec - start->ts_.tv_nsec); + + return diff_nsec; +} + +int32_t +monotime_coarse_diff_msec32_(const monotime_coarse_t *start, + const monotime_coarse_t *end) +{ + const int32_t diff_sec = (int32_t)(end->ts_.tv_sec - start->ts_.tv_sec); + const int32_t diff_nsec = (int32_t)(end->ts_.tv_nsec - start->ts_.tv_nsec); + return diff_sec * 1000 + diff_nsec / ONE_MILLION; +} + +/* This value is ONE_BILLION >> 20. */ +static const uint32_t STAMP_TICKS_PER_SECOND = 953; + +uint32_t +monotime_coarse_to_stamp(const monotime_coarse_t *t) +{ + uint32_t nsec = (uint32_t)t->ts_.tv_nsec; + uint32_t sec = (uint32_t)t->ts_.tv_sec; + + return (sec * STAMP_TICKS_PER_SECOND) + (nsec >> 20); +} + +int +monotime_is_zero(const monotime_t *val) +{ + return val->ts_.tv_sec == 0 && val->ts_.tv_nsec == 0; +} + +void +monotime_add_msec(monotime_t *out, const monotime_t *val, uint32_t msec) +{ + const uint32_t sec = msec / 1000; + const uint32_t msec_remainder = msec % 1000; + out->ts_.tv_sec = val->ts_.tv_sec + sec; + out->ts_.tv_nsec = val->ts_.tv_nsec + (msec_remainder * ONE_MILLION); + if (out->ts_.tv_nsec > ONE_BILLION) { + out->ts_.tv_nsec -= ONE_BILLION; + out->ts_.tv_sec += 1; + } +} + +/* end of "HAVE_CLOCK_GETTIME" */ +#elif defined (_WIN32) + +/** Result of QueryPerformanceFrequency, in terms needed to + * convert ticks to nanoseconds. */ +static int64_t nsec_per_tick_numer = 1; +static int64_t nsec_per_tick_denom = 1; + +/** Lock to protect last_pctr and pctr_offset */ +static CRITICAL_SECTION monotime_lock; +/** Lock to protect rollover_count and last_tick_count */ +static CRITICAL_SECTION monotime_coarse_lock; + +typedef ULONGLONG (WINAPI *GetTickCount64_fn_t)(void); +static GetTickCount64_fn_t GetTickCount64_fn = NULL; + +static void +monotime_init_internal(void) +{ + tor_assert(!monotime_initialized); + BOOL ok = InitializeCriticalSectionAndSpinCount(&monotime_lock, 200); + tor_assert(ok); + ok = InitializeCriticalSectionAndSpinCount(&monotime_coarse_lock, 200); + tor_assert(ok); + LARGE_INTEGER li; + ok = QueryPerformanceFrequency(&li); + tor_assert(ok); + tor_assert(li.QuadPart); + + uint64_t n = ONE_BILLION; + uint64_t d = li.QuadPart; + /* We need to simplify this or we'll probably overflow the int64. */ + simplify_fraction64(&n, &d); + tor_assert(n <= INT64_MAX); + tor_assert(d <= INT64_MAX); + + nsec_per_tick_numer = (int64_t) n; + nsec_per_tick_denom = (int64_t) d; + + last_pctr = 0; + pctr_offset = 0; + + HANDLE h = load_windows_system_library(TEXT("kernel32.dll")); + if (h) { + GetTickCount64_fn = (GetTickCount64_fn_t) + GetProcAddress(h, "GetTickCount64"); + } + // FreeLibrary(h) ? +} + +void +monotime_get(monotime_t *out) +{ + if (BUG(monotime_initialized == 0)) { + monotime_init(); + } + +#ifdef TOR_UNIT_TESTS + if (monotime_mocking_enabled) { + out->pcount_ = (mock_time_nsec * nsec_per_tick_denom) + / nsec_per_tick_numer; + return; + } +#endif /* defined(TOR_UNIT_TESTS) */ + + /* Alas, QueryPerformanceCounter is not always monotonic: see bug list at + + https://www.python.org/dev/peps/pep-0418/#windows-queryperformancecounter + */ + + EnterCriticalSection(&monotime_lock); + LARGE_INTEGER res; + BOOL ok = QueryPerformanceCounter(&res); + tor_assert(ok); + const int64_t count_raw = res.QuadPart; + out->pcount_ = ratchet_performance_counter(count_raw); + LeaveCriticalSection(&monotime_lock); +} + +void +monotime_coarse_get(monotime_coarse_t *out) +{ +#ifdef TOR_UNIT_TESTS + if (monotime_mocking_enabled) { + out->tick_count_ = mock_time_nsec_coarse / ONE_MILLION; + return; + } +#endif /* defined(TOR_UNIT_TESTS) */ + + if (GetTickCount64_fn) { + out->tick_count_ = (int64_t)GetTickCount64_fn(); + } else { + EnterCriticalSection(&monotime_coarse_lock); + DWORD tick = GetTickCount(); + out->tick_count_ = ratchet_coarse_performance_counter(tick); + LeaveCriticalSection(&monotime_coarse_lock); + } +} + +int64_t +monotime_diff_nsec(const monotime_t *start, + const monotime_t *end) +{ + if (BUG(monotime_initialized == 0)) { + monotime_init(); + } + const int64_t diff_ticks = end->pcount_ - start->pcount_; + return (diff_ticks * nsec_per_tick_numer) / nsec_per_tick_denom; +} + +int64_t +monotime_coarse_diff_msec(const monotime_coarse_t *start, + const monotime_coarse_t *end) +{ + const int64_t diff_ticks = end->tick_count_ - start->tick_count_; + return diff_ticks; +} + +int32_t +monotime_coarse_diff_msec32_(const monotime_coarse_t *start, + const monotime_coarse_t *end) +{ + return (int32_t)monotime_coarse_diff_msec(start, end); +} + +int64_t +monotime_coarse_diff_usec(const monotime_coarse_t *start, + const monotime_coarse_t *end) +{ + return monotime_coarse_diff_msec(start, end) * 1000; +} + +int64_t +monotime_coarse_diff_nsec(const monotime_coarse_t *start, + const monotime_coarse_t *end) +{ + return monotime_coarse_diff_msec(start, end) * ONE_MILLION; +} + +static const uint32_t STAMP_TICKS_PER_SECOND = 1000; + +uint32_t +monotime_coarse_to_stamp(const monotime_coarse_t *t) +{ + return (uint32_t) t->tick_count_; +} + +int +monotime_is_zero(const monotime_t *val) +{ + return val->pcount_ == 0; +} + +int +monotime_coarse_is_zero(const monotime_coarse_t *val) +{ + return val->tick_count_ == 0; +} + +void +monotime_add_msec(monotime_t *out, const monotime_t *val, uint32_t msec) +{ + const uint64_t nsec = msec * ONE_MILLION; + const uint64_t ticks = (nsec * nsec_per_tick_denom) / nsec_per_tick_numer; + out->pcount_ = val->pcount_ + ticks; +} + +void +monotime_coarse_add_msec(monotime_coarse_t *out, const monotime_coarse_t *val, + uint32_t msec) +{ + out->tick_count_ = val->tick_count_ + msec; +} + +/* end of "_WIN32" */ +#elif defined(MONOTIME_USING_GETTIMEOFDAY) + +static tor_mutex_t monotime_lock; + +/** Initialize the monotonic timer subsystem. */ +static void +monotime_init_internal(void) +{ + tor_assert(!monotime_initialized); + tor_mutex_init(&monotime_lock); +} + +void +monotime_get(monotime_t *out) +{ + if (BUG(monotime_initialized == 0)) { + monotime_init(); + } + + tor_mutex_acquire(&monotime_lock); + struct timeval timeval_raw; + tor_gettimeofday(&timeval_raw); + ratchet_timeval(&timeval_raw, &out->tv_); + tor_mutex_release(&monotime_lock); +} + +int64_t +monotime_diff_nsec(const monotime_t *start, + const monotime_t *end) +{ + struct timeval diff; + timersub(&end->tv_, &start->tv_, &diff); + return (diff.tv_sec * ONE_BILLION + diff.tv_usec * 1000); +} + +int32_t +monotime_coarse_diff_msec32_(const monotime_coarse_t *start, + const monotime_coarse_t *end) +{ + struct timeval diff; + timersub(&end->tv_, &start->tv_, &diff); + return diff.tv_sec * 1000 + diff.tv_usec / 1000; +} + +/* This value is ONE_MILLION >> 10. */ +static const uint32_t STAMP_TICKS_PER_SECOND = 976; + +uint32_t +monotime_coarse_to_stamp(const monotime_coarse_t *t) +{ + const uint32_t usec = (uint32_t)t->tv_.tv_usec; + const uint32_t sec = (uint32_t)t->tv_.tv_sec; + return (sec * STAMP_TICKS_PER_SECOND) | (nsec >> 10); +} + +int +monotime_is_zero(const monotime_t *val) +{ + return val->tv_.tv_sec == 0 && val->tv_.tv_usec == 0; +} + +void +monotime_add_msec(monotime_t *out, const monotime_t *val, uint32_t msec) +{ + const uint32_t sec = msec / 1000; + const uint32_t msec_remainder = msec % 1000; + out->tv_.tv_sec = val->tv_.tv_sec + sec; + out->tv_.tv_usec = val->tv_.tv_nsec + (msec_remainder * 1000); + if (out->tv_.tv_usec > ONE_MILLION) { + out->tv_.tv_usec -= ONE_MILLION; + out->tv_.tv_sec += 1; + } +} + +/* end of "MONOTIME_USING_GETTIMEOFDAY" */ +#else +#error "No way to implement monotonic timers." +#endif /* defined(__APPLE__) || ... */ + +/** + * Initialize the monotonic timer subsystem. Must be called before any + * monotonic timer functions. This function is idempotent. + */ +void +monotime_init(void) +{ + if (!monotime_initialized) { + monotime_init_internal(); + monotime_initialized = 1; + monotime_get(&initialized_at); +#ifdef MONOTIME_COARSE_FN_IS_DIFFERENT + monotime_coarse_get(&initialized_at_coarse); +#endif + } +} + +void +monotime_zero(monotime_t *out) +{ + memset(out, 0, sizeof(*out)); +} +#ifdef MONOTIME_COARSE_TYPE_IS_DIFFERENT +void +monotime_coarse_zero(monotime_coarse_t *out) +{ + memset(out, 0, sizeof(*out)); +} +#endif + +int64_t +monotime_diff_usec(const monotime_t *start, + const monotime_t *end) +{ + const int64_t nsec = monotime_diff_nsec(start, end); + return CEIL_DIV(nsec, 1000); +} + +int64_t +monotime_diff_msec(const monotime_t *start, + const monotime_t *end) +{ + const int64_t nsec = monotime_diff_nsec(start, end); + return CEIL_DIV(nsec, ONE_MILLION); +} + +uint64_t +monotime_absolute_nsec(void) +{ + monotime_t now; + if (BUG(monotime_initialized == 0)) { + monotime_init(); + } + + monotime_get(&now); + return monotime_diff_nsec(&initialized_at, &now); +} + +uint64_t +monotime_absolute_usec(void) +{ + return monotime_absolute_nsec() / 1000; +} + +uint64_t +monotime_absolute_msec(void) +{ + return monotime_absolute_nsec() / ONE_MILLION; +} + +#ifdef MONOTIME_COARSE_FN_IS_DIFFERENT +uint64_t +monotime_coarse_absolute_nsec(void) +{ + if (BUG(monotime_initialized == 0)) { + monotime_init(); + } + + monotime_coarse_t now; + monotime_coarse_get(&now); + return monotime_coarse_diff_nsec(&initialized_at_coarse, &now); +} + +uint64_t +monotime_coarse_absolute_usec(void) +{ + return monotime_coarse_absolute_nsec() / 1000; +} + +uint64_t +monotime_coarse_absolute_msec(void) +{ + return monotime_coarse_absolute_nsec() / ONE_MILLION; +} +#else +#define initialized_at_coarse initialized_at +#endif /* defined(MONOTIME_COARSE_FN_IS_DIFFERENT) */ + +/** + * Return the current time "stamp" as described by monotime_coarse_to_stamp. + */ +uint32_t +monotime_coarse_get_stamp(void) +{ + monotime_coarse_t now; + monotime_coarse_get(&now); + return monotime_coarse_to_stamp(&now); +} + +#ifdef __APPLE__ +uint64_t +monotime_coarse_stamp_units_to_approx_msec(uint64_t units) +{ + /* Recover as much precision as we can. */ + uint64_t abstime_diff = (units << monotime_shift); + return (abstime_diff * mach_time_info.numer) / + (mach_time_info.denom * ONE_MILLION); +} +uint64_t +monotime_msec_to_approx_coarse_stamp_units(uint64_t msec) +{ + uint64_t abstime_val = + (((uint64_t)msec) * ONE_MILLION * mach_time_info.denom) / + mach_time_info.numer; + return abstime_val >> monotime_shift; +} +#else +uint64_t +monotime_coarse_stamp_units_to_approx_msec(uint64_t units) +{ + return (units * 1000) / STAMP_TICKS_PER_SECOND; +} +uint64_t +monotime_msec_to_approx_coarse_stamp_units(uint64_t msec) +{ + return (msec * STAMP_TICKS_PER_SECOND) / 1000; +} +#endif diff --git a/src/lib/time/compat_time.h b/src/lib/time/compat_time.h new file mode 100644 index 0000000000..480d426ac7 --- /dev/null +++ b/src/lib/time/compat_time.h @@ -0,0 +1,235 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file compat_time.h + * + * \brief Functions and types for monotonic times. + * + * monotime_* functions try to provide a high-resolution monotonic timer with + * something the best resolution the system provides. monotime_coarse_* + * functions run faster (if the operating system gives us a way to do that) + * but produce a less accurate timer: accuracy will probably be on the order + * of tens of milliseconds. + */ + +#ifndef TOR_COMPAT_TIME_H +#define TOR_COMPAT_TIME_H + +#include "orconfig.h" +#include "lib/cc/torint.h" + +#include "lib/wallclock/tor_gettimeofday.h" + +#ifdef _WIN32 +#undef HAVE_CLOCK_GETTIME +#endif + +#if defined(HAVE_CLOCK_GETTIME) +/* to ensure definition of CLOCK_MONOTONIC_COARSE if it's there */ +#include <time.h> +#endif + +#if !defined(HAVE_STRUCT_TIMEVAL_TV_SEC) +/** Implementation of timeval for platforms that don't have it. */ +struct timeval { + time_t tv_sec; + unsigned int tv_usec; +}; +#endif /* !defined(HAVE_STRUCT_TIMEVAL_TV_SEC) */ + +/** Represents a monotonic timer in a platform-dependent way. */ +typedef struct monotime_t { +#ifdef __APPLE__ + /* On apple, there is a 64-bit counter whose precision we must look up. */ + uint64_t abstime_; +#elif defined(HAVE_CLOCK_GETTIME) + /* It sure would be nice to use clock_gettime(). Posix is a nice thing. */ + struct timespec ts_; +#elif defined (_WIN32) + /* On Windows, there is a 64-bit counter whose precision we must look up. */ + int64_t pcount_; +#else +#define MONOTIME_USING_GETTIMEOFDAY + /* Otherwise, we will be stuck using gettimeofday. */ + struct timeval tv_; +#endif /* defined(__APPLE__) || ... */ +} monotime_t; + +#if defined(CLOCK_MONOTONIC_COARSE) && \ + defined(HAVE_CLOCK_GETTIME) +#define MONOTIME_COARSE_FN_IS_DIFFERENT +#define monotime_coarse_t monotime_t +#elif defined(_WIN32) +#define MONOTIME_COARSE_FN_IS_DIFFERENT +#define MONOTIME_COARSE_TYPE_IS_DIFFERENT +/** Represents a coarse monotonic time in a platform-independent way. */ +typedef struct monotime_coarse_t { + uint64_t tick_count_; +} monotime_coarse_t; +#elif defined(__APPLE__) && defined(HAVE_MACH_APPROXIMATE_TIME) +#define MONOTIME_COARSE_FN_IS_DIFFERENT +#define monotime_coarse_t monotime_t +#else +#define monotime_coarse_t monotime_t +#endif /* defined(CLOCK_MONOTONIC_COARSE) && ... || ... */ + +/** + * Initialize the timing subsystem. This function is idempotent. + */ +void monotime_init(void); +/** + * Set <b>out</b> to the current time. + */ +void monotime_get(monotime_t *out); +/** + * Return the number of nanoseconds between <b>start</b> and <b>end</b>. + */ +int64_t monotime_diff_nsec(const monotime_t *start, const monotime_t *end); +/** + * Return the number of microseconds between <b>start</b> and <b>end</b>. + */ +int64_t monotime_diff_usec(const monotime_t *start, const monotime_t *end); +/** + * Return the number of milliseconds between <b>start</b> and <b>end</b>. + */ +int64_t monotime_diff_msec(const monotime_t *start, const monotime_t *end); +/** + * Return the number of nanoseconds since the timer system was initialized. + */ +uint64_t monotime_absolute_nsec(void); +/** + * Return the number of microseconds since the timer system was initialized. + */ +uint64_t monotime_absolute_usec(void); +/** + * Return the number of milliseconds since the timer system was initialized. + */ +uint64_t monotime_absolute_msec(void); + +/** + * Set <b>out</b> to zero. + */ +void monotime_zero(monotime_t *out); +/** + * Return true iff <b>out</b> is zero + */ +int monotime_is_zero(const monotime_t *out); + +/** + * Set <b>out</b> to N milliseconds after <b>val</b>. + */ +/* XXXX We should add a more generic function here if we ever need to */ +void monotime_add_msec(monotime_t *out, const monotime_t *val, uint32_t msec); + +#if defined(MONOTIME_COARSE_FN_IS_DIFFERENT) +/** + * Set <b>out</b> to the current coarse time. + */ +void monotime_coarse_get(monotime_coarse_t *out); +uint64_t monotime_coarse_absolute_nsec(void); +uint64_t monotime_coarse_absolute_usec(void); +uint64_t monotime_coarse_absolute_msec(void); +#else /* !(defined(MONOTIME_COARSE_FN_IS_DIFFERENT)) */ +#define monotime_coarse_get monotime_get +#define monotime_coarse_absolute_nsec monotime_absolute_nsec +#define monotime_coarse_absolute_usec monotime_absolute_usec +#define monotime_coarse_absolute_msec monotime_absolute_msec +#endif /* defined(MONOTIME_COARSE_FN_IS_DIFFERENT) */ + +/** + * Return a "timestamp" approximation for a coarse monotonic timer. + * This timestamp is meant to be fast to calculate and easy to + * compare, and have a unit of something roughly around 1 msec. + * + * It will wrap over from time to time. + * + * It has no defined zero point. + */ +uint32_t monotime_coarse_to_stamp(const monotime_coarse_t *t); +/** + * Convert a difference, expressed in the units of monotime_coarse_to_stamp, + * into an approximate number of milliseconds. + */ +uint64_t monotime_coarse_stamp_units_to_approx_msec(uint64_t units); +uint64_t monotime_msec_to_approx_coarse_stamp_units(uint64_t msec); +uint32_t monotime_coarse_get_stamp(void); + +#if defined(MONOTIME_COARSE_TYPE_IS_DIFFERENT) +int64_t monotime_coarse_diff_nsec(const monotime_coarse_t *start, + const monotime_coarse_t *end); +int64_t monotime_coarse_diff_usec(const monotime_coarse_t *start, + const monotime_coarse_t *end); +int64_t monotime_coarse_diff_msec(const monotime_coarse_t *start, + const monotime_coarse_t *end); +void monotime_coarse_zero(monotime_coarse_t *out); +int monotime_coarse_is_zero(const monotime_coarse_t *val); +void monotime_coarse_add_msec(monotime_coarse_t *out, + const monotime_coarse_t *val, uint32_t msec); +#else /* !(defined(MONOTIME_COARSE_TYPE_IS_DIFFERENT)) */ +#define monotime_coarse_diff_nsec monotime_diff_nsec +#define monotime_coarse_diff_usec monotime_diff_usec +#define monotime_coarse_diff_msec monotime_diff_msec +#define monotime_coarse_zero monotime_zero +#define monotime_coarse_is_zero monotime_is_zero +#define monotime_coarse_add_msec monotime_add_msec +#endif /* defined(MONOTIME_COARSE_TYPE_IS_DIFFERENT) */ + +/** + * As monotime_coarse_diff_msec, but avoid 64-bit division. + * + * Requires that the difference fit into an int32_t; not for use with + * large time differences. + */ +int32_t monotime_coarse_diff_msec32_(const monotime_coarse_t *start, + const monotime_coarse_t *end); + +/** + * As monotime_coarse_diff_msec, but avoid 64-bit division if it is expensive. + * + * Requires that the difference fit into an int32_t; not for use with + * large time differences. + */ +static inline int32_t +monotime_coarse_diff_msec32(const monotime_coarse_t *start, + const monotime_coarse_t *end) +{ +#if SIZEOF_VOID_P == 8 + // on a 64-bit platform, let's assume 64/64 division is cheap. + return (int32_t) monotime_coarse_diff_msec(start, end); +#else +#define USING_32BIT_MSEC_HACK + return monotime_coarse_diff_msec32_(start, end); +#endif +} + +#ifdef TOR_UNIT_TESTS +void tor_sleep_msec(int msec); + +void monotime_enable_test_mocking(void); +void monotime_disable_test_mocking(void); +void monotime_set_mock_time_nsec(int64_t); +#if defined(MONOTIME_COARSE_FN_IS_DIFFERENT) +void monotime_coarse_set_mock_time_nsec(int64_t); +#else +#define monotime_coarse_set_mock_time_nsec monotime_set_mock_time_nsec +#endif +#endif /* defined(TOR_UNIT_TESTS) */ + +#ifdef COMPAT_TIME_PRIVATE +#if defined(_WIN32) || defined(TOR_UNIT_TESTS) +STATIC int64_t ratchet_performance_counter(int64_t count_raw); +STATIC int64_t ratchet_coarse_performance_counter(int64_t count_raw); +#endif +#if defined(MONOTIME_USING_GETTIMEOFDAY) || defined(TOR_UNIT_TESTS) +STATIC void ratchet_timeval(const struct timeval *timeval_raw, + struct timeval *out); +#endif +#ifdef TOR_UNIT_TESTS +void monotime_reset_ratchets_for_testing(void); +#endif +#endif /* defined(COMPAT_TIME_PRIVATE) */ + +#endif /* !defined(TOR_COMPAT_TIME_H) */ diff --git a/src/lib/time/include.am b/src/lib/time/include.am new file mode 100644 index 0000000000..a3f93a3744 --- /dev/null +++ b/src/lib/time/include.am @@ -0,0 +1,19 @@ + +noinst_LIBRARIES += src/lib/libtor-time.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-time-testing.a +endif + +src_lib_libtor_time_a_SOURCES = \ + src/lib/time/compat_time.c \ + src/lib/time/tvdiff.c + +src_lib_libtor_time_testing_a_SOURCES = \ + $(src_lib_libtor_time_a_SOURCES) +src_lib_libtor_time_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_time_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/time/compat_time.h \ + src/lib/time/tvdiff.h diff --git a/src/lib/time/tvdiff.c b/src/lib/time/tvdiff.c new file mode 100644 index 0000000000..a87d0d96dc --- /dev/null +++ b/src/lib/time/tvdiff.c @@ -0,0 +1,189 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tvdiff.c + * \brief Compute the difference between timevals, in various units. + **/ + +#include "lib/time/tvdiff.h" + +#include "lib/cc/compat_compiler.h" +#include "lib/log/log.h" + +#ifdef _WIN32 +#include <winsock2.h> +#endif +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#define TOR_USEC_PER_SEC 1000000 + +/** Return the difference between start->tv_sec and end->tv_sec. + * Returns INT64_MAX on overflow and underflow. + */ +static int64_t +tv_secdiff_impl(const struct timeval *start, const struct timeval *end) +{ + const int64_t s = (int64_t)start->tv_sec; + const int64_t e = (int64_t)end->tv_sec; + + /* This may not be the most efficient way of implemeting this check, + * but it's easy to see that it's correct and doesn't overflow */ + + if (s > 0 && e < INT64_MIN + s) { + /* s is positive: equivalent to e - s < INT64_MIN, but without any + * overflow */ + return INT64_MAX; + } else if (s < 0 && e > INT64_MAX + s) { + /* s is negative: equivalent to e - s > INT64_MAX, but without any + * overflow */ + return INT64_MAX; + } + + return e - s; +} + +/** Return the number of microseconds elapsed between *start and *end. + * Returns LONG_MAX on overflow and underflow. + */ +long +tv_udiff(const struct timeval *start, const struct timeval *end) +{ + /* Sanity check tv_usec */ + if (start->tv_usec > TOR_USEC_PER_SEC || start->tv_usec < 0) { + log_warn(LD_GENERAL, "comparing times on microsecond detail with bad " + "start tv_usec: %"PRId64 " microseconds", + (int64_t)start->tv_usec); + return LONG_MAX; + } + + if (end->tv_usec > TOR_USEC_PER_SEC || end->tv_usec < 0) { + log_warn(LD_GENERAL, "comparing times on microsecond detail with bad " + "end tv_usec: %"PRId64 " microseconds", + (int64_t)end->tv_usec); + return LONG_MAX; + } + + /* Some BSDs have struct timeval.tv_sec 64-bit, but time_t (and long) 32-bit + */ + int64_t udiff; + const int64_t secdiff = tv_secdiff_impl(start, end); + + /* end->tv_usec - start->tv_usec can be up to 1 second either way */ + if (secdiff > (int64_t)(LONG_MAX/1000000 - 1) || + secdiff < (int64_t)(LONG_MIN/1000000 + 1)) { + log_warn(LD_GENERAL, "comparing times on microsecond detail too far " + "apart: %"PRId64 " seconds", (secdiff)); + return LONG_MAX; + } + + /* we'll never get an overflow here, because we check that both usecs are + * between 0 and TV_USEC_PER_SEC. */ + udiff = secdiff*1000000 + ((int64_t)end->tv_usec - (int64_t)start->tv_usec); + + /* Some compilers are smart enough to work out this is a no-op on L64 */ +#if SIZEOF_LONG < 8 + if (udiff > (int64_t)LONG_MAX || udiff < (int64_t)LONG_MIN) { + return LONG_MAX; + } +#endif + + return (long)udiff; +} + +/** Return the number of milliseconds elapsed between *start and *end. + * If the tv_usec difference is 500, rounds away from zero. + * Returns LONG_MAX on overflow and underflow. + */ +long +tv_mdiff(const struct timeval *start, const struct timeval *end) +{ + /* Sanity check tv_usec */ + if (start->tv_usec > TOR_USEC_PER_SEC || start->tv_usec < 0) { + log_warn(LD_GENERAL, "comparing times on millisecond detail with bad " + "start tv_usec: %"PRId64 " microseconds", + (int64_t)start->tv_usec); + return LONG_MAX; + } + + if (end->tv_usec > TOR_USEC_PER_SEC || end->tv_usec < 0) { + log_warn(LD_GENERAL, "comparing times on millisecond detail with bad " + "end tv_usec: %"PRId64 " microseconds", + (int64_t)end->tv_usec); + return LONG_MAX; + } + + /* Some BSDs have struct timeval.tv_sec 64-bit, but time_t (and long) 32-bit + */ + int64_t mdiff; + const int64_t secdiff = tv_secdiff_impl(start, end); + + /* end->tv_usec - start->tv_usec can be up to 1 second either way, but the + * mdiff calculation may add another temporary second for rounding. + * Whether this actually causes overflow depends on the compiler's constant + * folding and order of operations. */ + if (secdiff > (int64_t)(LONG_MAX/1000 - 2) || + secdiff < (int64_t)(LONG_MIN/1000 + 1)) { + log_warn(LD_GENERAL, "comparing times on millisecond detail too far " + "apart: %"PRId64 " seconds", (int64_t)secdiff); + return LONG_MAX; + } + + /* Subtract and round */ + mdiff = secdiff*1000 + + /* We add a million usec here to ensure that the result is positive, + * so that the round-towards-zero behavior of the division will give + * the right result for rounding to the nearest msec. Later we subtract + * 1000 in order to get the correct result. + * We'll never get an overflow here, because we check that both usecs are + * between 0 and TV_USEC_PER_SEC. */ + ((int64_t)end->tv_usec - (int64_t)start->tv_usec + 500 + 1000000) / 1000 + - 1000; + + /* Some compilers are smart enough to work out this is a no-op on L64 */ +#if SIZEOF_LONG < 8 + if (mdiff > (int64_t)LONG_MAX || mdiff < (int64_t)LONG_MIN) { + return LONG_MAX; + } +#endif + + return (long)mdiff; +} + +/** + * Converts timeval to milliseconds. + */ +int64_t +tv_to_msec(const struct timeval *tv) +{ + int64_t conv = ((int64_t)tv->tv_sec)*1000L; + /* Round ghetto-style */ + conv += ((int64_t)tv->tv_usec+500)/1000L; + return conv; +} + +/** + * Return duration in seconds between time_t values + * <b>t1</b> and <b>t2</b> iff <b>t1</b> is numerically + * less or equal than <b>t2</b>. Otherwise, return TIME_MAX. + * + * This provides a safe way to compute difference between + * two UNIX timestamps (<b>t2</b> can be assumed by calling + * code to be later than <b>t1</b>) or two durations measured + * in seconds (<b>t2</b> can be assumed to be longer than + * <b>t1</b>). Calling code is expected to check for TIME_MAX + * return value and interpret that as error condition. + */ +time_t +time_diff(const time_t t1, const time_t t2) +{ + if (t1 <= t2) + return t2 - t1; + + return TIME_MAX; +} + diff --git a/src/lib/time/tvdiff.h b/src/lib/time/tvdiff.h new file mode 100644 index 0000000000..724af1528a --- /dev/null +++ b/src/lib/time/tvdiff.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tvdiff.h + * \brief Header for tvdiff.c + **/ + +#ifndef TOR_TVDIFF_H +#define TOR_TVDIFF_H + +#include "lib/cc/torint.h" +struct timeval; + +long tv_udiff(const struct timeval *start, const struct timeval *end); +long tv_mdiff(const struct timeval *start, const struct timeval *end); +int64_t tv_to_msec(const struct timeval *tv); + +time_t time_diff(const time_t from, const time_t to); + +#endif diff --git a/src/lib/tls/.may_include b/src/lib/tls/.may_include new file mode 100644 index 0000000000..2840e590b8 --- /dev/null +++ b/src/lib/tls/.may_include @@ -0,0 +1,17 @@ +orconfig.h + +lib/arch/*.h +lib/cc/*.h +lib/container/*.h +lib/crypt_ops/*.h +lib/ctime/*.h +lib/encoding/*.h +lib/intmath/*.h +lib/log/*.h +lib/malloc/*.h +lib/net/*.h +lib/string/*.h +lib/testsupport/testsupport.h +lib/tls/*.h + +ciphers.inc diff --git a/src/lib/tls/buffers_tls.c b/src/lib/tls/buffers_tls.c new file mode 100644 index 0000000000..c176162c35 --- /dev/null +++ b/src/lib/tls/buffers_tls.c @@ -0,0 +1,182 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file buffers_tls.c + * \brief Read and write data on a tor_tls_t connection from a buf_t object. + **/ + +#define BUFFERS_PRIVATE +#include "orconfig.h" +#include <stddef.h> +#include "lib/container/buffers.h" +#include "lib/tls/buffers_tls.h" +#include "lib/cc/torint.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/tls/tortls.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +/** As read_to_chunk(), but return (negative) error code on error, blocking, + * or TLS, and the number of bytes read otherwise. */ +static inline int +read_to_chunk_tls(buf_t *buf, chunk_t *chunk, tor_tls_t *tls, + size_t at_most) +{ + int read_result; + + tor_assert(CHUNK_REMAINING_CAPACITY(chunk) >= at_most); + read_result = tor_tls_read(tls, CHUNK_WRITE_PTR(chunk), at_most); + if (read_result < 0) + return read_result; + buf->datalen += read_result; + chunk->datalen += read_result; + return read_result; +} + +/** As read_to_buf, but reads from a TLS connection, and returns a TLS + * status value rather than the number of bytes read. + * + * Using TLS on OR connections complicates matters in two ways. + * + * First, a TLS stream has its own read buffer independent of the + * connection's read buffer. (TLS needs to read an entire frame from + * the network before it can decrypt any data. Thus, trying to read 1 + * byte from TLS can require that several KB be read from the network + * and decrypted. The extra data is stored in TLS's decrypt buffer.) + * Because the data hasn't been read by Tor (it's still inside the TLS), + * this means that sometimes a connection "has stuff to read" even when + * poll() didn't return POLLIN. The tor_tls_get_pending_bytes function is + * used in connection.c to detect TLS objects with non-empty internal + * buffers and read from them again. + * + * Second, the TLS stream's events do not correspond directly to network + * events: sometimes, before a TLS stream can read, the network must be + * ready to write -- or vice versa. + */ +int +buf_read_from_tls(buf_t *buf, tor_tls_t *tls, size_t at_most) +{ + int r = 0; + size_t total_read = 0; + + check_no_tls_errors(); + + if (BUG(buf->datalen >= INT_MAX)) + return -1; + if (BUG(buf->datalen >= INT_MAX - at_most)) + return -1; + + while (at_most > total_read) { + size_t readlen = at_most - total_read; + chunk_t *chunk; + if (!buf->tail || CHUNK_REMAINING_CAPACITY(buf->tail) < MIN_READ_LEN) { + chunk = buf_add_chunk_with_capacity(buf, at_most, 1); + if (readlen > chunk->memlen) + readlen = chunk->memlen; + } else { + size_t cap = CHUNK_REMAINING_CAPACITY(buf->tail); + chunk = buf->tail; + if (cap < readlen) + readlen = cap; + } + + r = read_to_chunk_tls(buf, chunk, tls, readlen); + if (r < 0) + return r; /* Error */ + tor_assert(total_read+r < INT_MAX); + total_read += r; + if ((size_t)r < readlen) /* eof, block, or no more to read. */ + break; + } + return (int)total_read; +} + +/** Helper for buf_flush_to_tls(): try to write <b>sz</b> bytes from chunk + * <b>chunk</b> of buffer <b>buf</b> onto socket <b>s</b>. (Tries to write + * more if there is a forced pending write size.) On success, deduct the + * bytes written from *<b>buf_flushlen</b>. Return the number of bytes + * written on success, and a TOR_TLS error code on failure or blocking. + */ +static inline int +flush_chunk_tls(tor_tls_t *tls, buf_t *buf, chunk_t *chunk, + size_t sz, size_t *buf_flushlen) +{ + int r; + size_t forced; + char *data; + + forced = tor_tls_get_forced_write_size(tls); + if (forced > sz) + sz = forced; + if (chunk) { + data = chunk->data; + tor_assert(sz <= chunk->datalen); + } else { + data = NULL; + tor_assert(sz == 0); + } + r = tor_tls_write(tls, data, sz); + if (r < 0) + return r; + if (*buf_flushlen > (size_t)r) + *buf_flushlen -= r; + else + *buf_flushlen = 0; + buf_drain(buf, r); + log_debug(LD_NET,"flushed %d bytes, %d ready to flush, %d remain.", + r,(int)*buf_flushlen,(int)buf->datalen); + return r; +} + +/** As buf_flush_to_socket(), but writes data to a TLS connection. Can write + * more than <b>flushlen</b> bytes. + */ +int +buf_flush_to_tls(buf_t *buf, tor_tls_t *tls, size_t flushlen, + size_t *buf_flushlen) +{ + int r; + size_t flushed = 0; + ssize_t sz; + tor_assert(buf_flushlen); + if (BUG(*buf_flushlen > buf->datalen)) { + *buf_flushlen = buf->datalen; + } + if (BUG(flushlen > *buf_flushlen)) { + flushlen = *buf_flushlen; + } + sz = (ssize_t) flushlen; + + /* we want to let tls write even if flushlen is zero, because it might + * have a partial record pending */ + check_no_tls_errors(); + + do { + size_t flushlen0; + if (buf->head) { + if ((ssize_t)buf->head->datalen >= sz) + flushlen0 = sz; + else + flushlen0 = buf->head->datalen; + } else { + flushlen0 = 0; + } + + r = flush_chunk_tls(tls, buf, buf->head, flushlen0, buf_flushlen); + if (r < 0) + return r; + flushed += r; + sz -= r; + if (r == 0) /* Can't flush any more now. */ + break; + } while (sz > 0); + tor_assert(flushed < INT_MAX); + return (int)flushed; +} diff --git a/src/lib/tls/buffers_tls.h b/src/lib/tls/buffers_tls.h new file mode 100644 index 0000000000..65788c3f34 --- /dev/null +++ b/src/lib/tls/buffers_tls.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file buffers_tls.h + * \brief Header for buffers_tls.c + **/ + +#ifndef TOR_BUFFERS_TLS_H +#define TOR_BUFFERS_TLS_H + +struct buf_t; +struct tor_tls_t; + +int buf_read_from_tls(struct buf_t *buf, + struct tor_tls_t *tls, size_t at_most); +int buf_flush_to_tls(struct buf_t *buf, struct tor_tls_t *tls, + size_t sz, size_t *buf_flushlen); + +#endif /* !defined(TOR_BUFFERS_TLS_H) */ diff --git a/src/lib/tls/ciphers.inc b/src/lib/tls/ciphers.inc new file mode 100644 index 0000000000..0084b3e325 --- /dev/null +++ b/src/lib/tls/ciphers.inc @@ -0,0 +1,100 @@ +/* This is an include file used to define the list of ciphers clients should + * advertise. Before including it, you should define the CIPHER and XCIPHER + * macros. + * + * This file was automatically generated by get_mozilla_ciphers.py; + * TLSv1.3 ciphers were added manually. + */ + +/* Here are the TLS1.3 ciphers. Note that we don't have XCIPHER instances + * here, since we don't want to ever fake them. + */ +#ifdef TLS1_3_TXT_AES_128_GCM_SHA256 + CIPHER(0x1301, TLS1_3_TXT_AES_128_GCM_SHA256) +#endif +#ifdef TLS1_3_TXT_AES_256_GCM_SHA384 + CIPHER(0x1302, TLS1_3_TXT_AES_256_GCM_SHA384) +#endif +#ifdef TLS1_3_TXT_CHACHA20_POLY1305_SHA256 + CIPHER(0x1303, TLS1_3_TXT_CHACHA20_POLY1305_SHA256) +#endif +#ifdef TLS1_3_TXT_AES_128_CCM_SHA256 + CIPHER(0x1304, TLS1_3_TXT_AES_128_CCM_SHA256) +#endif + +/* Here's the machine-generated list. */ +#ifdef TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + CIPHER(0xc02b, TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) +#else + XCIPHER(0xc02b, TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + CIPHER(0xc02f, TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256) +#else + XCIPHER(0xc02f, TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256) +#endif +#ifdef TLS1_TXT_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 + CIPHER(0xcca9, TLS1_TXT_ECDHE_ECDSA_WITH_CHACHA20_POLY1305) +#else + XCIPHER(0xcca9, TLS1_TXT_ECDHE_ECDSA_WITH_CHACHA20_POLY1305) +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305 + CIPHER(0xcca8, TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305) +#else + XCIPHER(0xcca8, TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305) +#endif +#ifdef TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + CIPHER(0xc02c, TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) +#else + XCIPHER(0xc02c, TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + CIPHER(0xc030, TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384) +#else + XCIPHER(0xc030, TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384) +#endif +#ifdef TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + CIPHER(0xc00a, TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) +#else + XCIPHER(0xc00a, TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) +#endif +#ifdef TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_CBC_SHA + CIPHER(0xc009, TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) +#else + XCIPHER(0xc009, TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA + CIPHER(0xc013, TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA) +#else + XCIPHER(0xc013, TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA) +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA + CIPHER(0xc014, TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA) +#else + XCIPHER(0xc014, TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA) +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_AES_128_SHA + CIPHER(0x0033, TLS1_TXT_DHE_RSA_WITH_AES_128_SHA) +#else + XCIPHER(0x0033, TLS1_TXT_DHE_RSA_WITH_AES_128_SHA) +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_AES_256_SHA + CIPHER(0x0039, TLS1_TXT_DHE_RSA_WITH_AES_256_SHA) +#else + XCIPHER(0x0039, TLS1_TXT_DHE_RSA_WITH_AES_256_SHA) +#endif +#ifdef TLS1_TXT_RSA_WITH_AES_128_SHA + CIPHER(0x002f, TLS1_TXT_RSA_WITH_AES_128_SHA) +#else + XCIPHER(0x002f, TLS1_TXT_RSA_WITH_AES_128_SHA) +#endif +#ifdef TLS1_TXT_RSA_WITH_AES_256_SHA + CIPHER(0x0035, TLS1_TXT_RSA_WITH_AES_256_SHA) +#else + XCIPHER(0x0035, TLS1_TXT_RSA_WITH_AES_256_SHA) +#endif +#ifdef SSL3_TXT_RSA_DES_192_CBC3_SHA + CIPHER(0x000a, SSL3_TXT_RSA_DES_192_CBC3_SHA) +#else + XCIPHER(0x000a, SSL3_TXT_RSA_DES_192_CBC3_SHA) +#endif diff --git a/src/lib/tls/include.am b/src/lib/tls/include.am new file mode 100644 index 0000000000..a664b29fb2 --- /dev/null +++ b/src/lib/tls/include.am @@ -0,0 +1,40 @@ + +noinst_LIBRARIES += src/lib/libtor-tls.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-tls-testing.a +endif + +src_lib_libtor_tls_a_SOURCES = \ + src/lib/tls/buffers_tls.c \ + src/lib/tls/tortls.c \ + src/lib/tls/x509.c + +if USE_NSS +src_lib_libtor_tls_a_SOURCES += \ + src/lib/tls/nss_countbytes.c \ + src/lib/tls/tortls_nss.c \ + src/lib/tls/x509_nss.c +else +src_lib_libtor_tls_a_SOURCES += \ + src/lib/tls/tortls_openssl.c \ + src/lib/tls/x509_openssl.c +endif + +src_lib_libtor_tls_a_CFLAGS = $(AM_CFLAGS) $(TOR_CFLAGS_CRYPTLIB) + +src_lib_libtor_tls_testing_a_SOURCES = \ + $(src_lib_libtor_tls_a_SOURCES) +src_lib_libtor_tls_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_tls_testing_a_CFLAGS = \ + $(AM_CFLAGS) $(TOR_CFLAGS_CRYPTLIB) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/tls/ciphers.inc \ + src/lib/tls/buffers_tls.h \ + src/lib/tls/nss_countbytes.h \ + src/lib/tls/tortls.h \ + src/lib/tls/tortls_internal.h \ + src/lib/tls/tortls_st.h \ + src/lib/tls/x509.h \ + src/lib/tls/x509_internal.h diff --git a/src/lib/tls/nss_countbytes.c b/src/lib/tls/nss_countbytes.c new file mode 100644 index 0000000000..7761727acd --- /dev/null +++ b/src/lib/tls/nss_countbytes.c @@ -0,0 +1,244 @@ +/* Copyright 2018-2019, The Tor Project Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file nss_countbytes.c + * \brief A PRFileDesc layer to let us count the number of bytes + * bytes actually written on a PRFileDesc. + **/ + +#include "orconfig.h" + +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/tls/nss_countbytes.h" + +#include <stdlib.h> +#include <string.h> + +#include <prio.h> + +/** Boolean: have we initialized this module */ +static bool countbytes_initialized = false; + +/** Integer to identity this layer. */ +static PRDescIdentity countbytes_layer_id = PR_INVALID_IO_LAYER; + +/** Table of methods for this layer.*/ +static PRIOMethods countbytes_methods; + +/** Default close function provided by NSPR. We use this to help + * implement our own close function.*/ +static PRStatus(*default_close_fn)(PRFileDesc *fd); + +static PRStatus countbytes_close_fn(PRFileDesc *fd); +static PRInt32 countbytes_read_fn(PRFileDesc *fd, void *buf, PRInt32 amount); +static PRInt32 countbytes_write_fn(PRFileDesc *fd, const void *buf, + PRInt32 amount); +static PRInt32 countbytes_writev_fn(PRFileDesc *fd, const PRIOVec *iov, + PRInt32 size, PRIntervalTime timeout); +static PRInt32 countbytes_send_fn(PRFileDesc *fd, const void *buf, + PRInt32 amount, PRIntn flags, + PRIntervalTime timeout); +static PRInt32 countbytes_recv_fn(PRFileDesc *fd, void *buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout); + +/** Private fields for the byte-counter layer. We cast this to and from + * PRFilePrivate*, which is supposed to be allowed. */ +typedef struct tor_nss_bytecounts_t { + uint64_t n_read; + uint64_t n_written; +} tor_nss_bytecounts_t; + +/** + * Initialize this module, if it is not already initialized. + **/ +void +tor_nss_countbytes_init(void) +{ + if (countbytes_initialized) + return; + + countbytes_layer_id = PR_GetUniqueIdentity("Tor byte-counting layer"); + tor_assert(countbytes_layer_id != PR_INVALID_IO_LAYER); + + memcpy(&countbytes_methods, PR_GetDefaultIOMethods(), sizeof(PRIOMethods)); + + default_close_fn = countbytes_methods.close; + countbytes_methods.close = countbytes_close_fn; + countbytes_methods.read = countbytes_read_fn; + countbytes_methods.write = countbytes_write_fn; + countbytes_methods.writev = countbytes_writev_fn; + countbytes_methods.send = countbytes_send_fn; + countbytes_methods.recv = countbytes_recv_fn; + /* NOTE: We aren't wrapping recvfrom, sendto, or sendfile, since I think + * NSS won't be using them for TLS connections. */ + + countbytes_initialized = true; +} + +/** + * Return the tor_nss_bytecounts_t object for a given IO layer. Asserts that + * the IO layer is in fact a layer created by this module. + */ +static tor_nss_bytecounts_t * +get_counts(PRFileDesc *fd) +{ + tor_assert(fd->identity == countbytes_layer_id); + return (tor_nss_bytecounts_t*) fd->secret; +} + +/** Helper: increment the read-count of an fd by n. */ +#define INC_READ(fd, n) STMT_BEGIN \ + get_counts(fd)->n_read += (n); \ + STMT_END + +/** Helper: increment the write-count of an fd by n. */ +#define INC_WRITTEN(fd, n) STMT_BEGIN \ + get_counts(fd)->n_written += (n); \ + STMT_END + +/** Implementation for PR_Close: frees the 'secret' field, then passes control + * to the default close function */ +static PRStatus +countbytes_close_fn(PRFileDesc *fd) +{ + tor_assert(fd); + + tor_nss_bytecounts_t *counts = (tor_nss_bytecounts_t *)fd->secret; + tor_free(counts); + fd->secret = NULL; + + return default_close_fn(fd); +} + +/** Implementation for PR_Read: Calls the lower-level read function, + * and records what it said. */ +static PRInt32 +countbytes_read_fn(PRFileDesc *fd, void *buf, PRInt32 amount) +{ + tor_assert(fd); + tor_assert(fd->lower); + + PRInt32 result = (fd->lower->methods->read)(fd->lower, buf, amount); + if (result > 0) + INC_READ(fd, result); + return result; +} +/** Implementation for PR_Write: Calls the lower-level write function, + * and records what it said. */ +static PRInt32 +countbytes_write_fn(PRFileDesc *fd, const void *buf, PRInt32 amount) +{ + tor_assert(fd); + tor_assert(fd->lower); + + PRInt32 result = (fd->lower->methods->write)(fd->lower, buf, amount); + if (result > 0) + INC_WRITTEN(fd, result); + return result; +} +/** Implementation for PR_Writev: Calls the lower-level writev function, + * and records what it said. */ +static PRInt32 +countbytes_writev_fn(PRFileDesc *fd, const PRIOVec *iov, + PRInt32 size, PRIntervalTime timeout) +{ + tor_assert(fd); + tor_assert(fd->lower); + + PRInt32 result = (fd->lower->methods->writev)(fd->lower, iov, size, timeout); + if (result > 0) + INC_WRITTEN(fd, result); + return result; +} +/** Implementation for PR_Send: Calls the lower-level send function, + * and records what it said. */ +static PRInt32 +countbytes_send_fn(PRFileDesc *fd, const void *buf, + PRInt32 amount, PRIntn flags, PRIntervalTime timeout) +{ + tor_assert(fd); + tor_assert(fd->lower); + + PRInt32 result = (fd->lower->methods->send)(fd->lower, buf, amount, flags, + timeout); + if (result > 0) + INC_WRITTEN(fd, result); + return result; +} +/** Implementation for PR_Recv: Calls the lower-level recv function, + * and records what it said. */ +static PRInt32 +countbytes_recv_fn(PRFileDesc *fd, void *buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout) +{ + tor_assert(fd); + tor_assert(fd->lower); + + PRInt32 result = (fd->lower->methods->recv)(fd->lower, buf, amount, flags, + timeout); + if (result > 0) + INC_READ(fd, result); + return result; +} + +/** + * Wrap a PRFileDesc from NSPR with a new PRFileDesc that will count the + * total number of bytes read and written. Return the new PRFileDesc. + * + * This function takes ownership of its input. + */ +PRFileDesc * +tor_wrap_prfiledesc_with_byte_counter(PRFileDesc *stack) +{ + if (BUG(! countbytes_initialized)) { + tor_nss_countbytes_init(); + } + + tor_nss_bytecounts_t *bytecounts = tor_malloc_zero(sizeof(*bytecounts)); + + PRFileDesc *newfd = PR_CreateIOLayerStub(countbytes_layer_id, + &countbytes_methods); + tor_assert(newfd); + newfd->secret = (PRFilePrivate *)bytecounts; + + /* This does some complicated messing around with the headers of these + objects; see the NSPR documentation for more. The upshot is that + after PushIOLayer, "stack" will be the head of the stack. + */ + PRStatus status = PR_PushIOLayer(stack, PR_TOP_IO_LAYER, newfd); + tor_assert(status == PR_SUCCESS); + + return stack; +} + +/** + * Given a PRFileDesc returned by tor_wrap_prfiledesc_with_byte_counter(), + * or another PRFileDesc wrapping that PRFileDesc, set the provided + * pointers to the number of bytes read and written on the descriptor since + * it was created. + * + * Return 0 on success, -1 on failure. + */ +int +tor_get_prfiledesc_byte_counts(PRFileDesc *fd, + uint64_t *n_read_out, + uint64_t *n_written_out) +{ + if (BUG(! countbytes_initialized)) { + tor_nss_countbytes_init(); + } + + tor_assert(fd); + PRFileDesc *bclayer = PR_GetIdentitiesLayer(fd, countbytes_layer_id); + if (BUG(bclayer == NULL)) + return -1; + + tor_nss_bytecounts_t *counts = get_counts(bclayer); + + *n_read_out = counts->n_read; + *n_written_out = counts->n_written; + + return 0; +} diff --git a/src/lib/tls/nss_countbytes.h b/src/lib/tls/nss_countbytes.h new file mode 100644 index 0000000000..8b31603923 --- /dev/null +++ b/src/lib/tls/nss_countbytes.h @@ -0,0 +1,25 @@ +/* Copyright 2018-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file nss_countbytes.h + * \brief Header for nss_countbytes.c, which lets us count the number of + * bytes actually written on a PRFileDesc. + **/ + +#ifndef TOR_NSS_COUNTBYTES_H +#define TOR_NSS_COUNTBYTES_H + +#include "lib/cc/torint.h" + +void tor_nss_countbytes_init(void); + +struct PRFileDesc; +struct PRFileDesc *tor_wrap_prfiledesc_with_byte_counter( + struct PRFileDesc *stack); + +int tor_get_prfiledesc_byte_counts(struct PRFileDesc *fd, + uint64_t *n_read_out, + uint64_t *n_written_out); + +#endif diff --git a/src/lib/tls/tortls.c b/src/lib/tls/tortls.c new file mode 100644 index 0000000000..4ca7c7d9d3 --- /dev/null +++ b/src/lib/tls/tortls.c @@ -0,0 +1,442 @@ +/* Copyright (c) 2003, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define TORTLS_PRIVATE +#define TOR_X509_PRIVATE +#include "lib/tls/x509.h" +#include "lib/tls/x509_internal.h" +#include "lib/tls/tortls.h" +#include "lib/tls/tortls_st.h" +#include "lib/tls/tortls_internal.h" +#include "lib/log/util_bug.h" +#include "lib/intmath/cmp.h" +#include "lib/crypt_ops/crypto_rsa.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/net/socket.h" + +#ifdef _WIN32 + #include <winsock2.h> + #include <ws2tcpip.h> +#endif + +#include <time.h> + +/** Global TLS contexts. We keep them here because nobody else needs + * to touch them. + * + * @{ */ +STATIC tor_tls_context_t *server_tls_context = NULL; +STATIC tor_tls_context_t *client_tls_context = NULL; +/**@}*/ + +/** + * Return the appropriate TLS context. + */ +tor_tls_context_t * +tor_tls_context_get(int is_server) +{ + return is_server ? server_tls_context : client_tls_context; +} + +/** Convert an errno (or a WSAerrno on windows) into a TOR_TLS_* error + * code. */ +int +tor_errno_to_tls_error(int e) +{ + switch (e) { + case SOCK_ERRNO(ECONNRESET): // most common + return TOR_TLS_ERROR_CONNRESET; + case SOCK_ERRNO(ETIMEDOUT): + return TOR_TLS_ERROR_TIMEOUT; + case SOCK_ERRNO(EHOSTUNREACH): + case SOCK_ERRNO(ENETUNREACH): + return TOR_TLS_ERROR_NO_ROUTE; + case SOCK_ERRNO(ECONNREFUSED): + return TOR_TLS_ERROR_CONNREFUSED; // least common + default: + return TOR_TLS_ERROR_MISC; + } +} + +/** Set *<b>link_cert_out</b> and *<b>id_cert_out</b> to the link certificate + * and ID certificate that we're currently using for our V3 in-protocol + * handshake's certificate chain. If <b>server</b> is true, provide the certs + * that we use in server mode (auth, ID); otherwise, provide the certs that we + * use in client mode. (link, ID) */ +int +tor_tls_get_my_certs(int server, + const tor_x509_cert_t **link_cert_out, + const tor_x509_cert_t **id_cert_out) +{ + tor_tls_context_t *ctx = tor_tls_context_get(server); + int rv = -1; + const tor_x509_cert_t *link_cert = NULL; + const tor_x509_cert_t *id_cert = NULL; + if (ctx) { + rv = 0; + link_cert = server ? ctx->my_link_cert : ctx->my_auth_cert; + id_cert = ctx->my_id_cert; + } + if (link_cert_out) + *link_cert_out = link_cert; + if (id_cert_out) + *id_cert_out = id_cert; + return rv; +} + +/** + * Return the authentication key that we use to authenticate ourselves as a + * client in the V3 in-protocol handshake. + */ +crypto_pk_t * +tor_tls_get_my_client_auth_key(void) +{ + tor_tls_context_t *context = tor_tls_context_get(0); + if (! context) + return NULL; + return context->auth_key; +} + +/** Increase the reference count of <b>ctx</b>. */ +void +tor_tls_context_incref(tor_tls_context_t *ctx) +{ + ++ctx->refcnt; +} + +/** Remove a reference to <b>ctx</b>, and free it if it has no more + * references. */ +void +tor_tls_context_decref(tor_tls_context_t *ctx) +{ + tor_assert(ctx); + if (--ctx->refcnt == 0) { + tor_tls_context_impl_free(ctx->ctx); + tor_x509_cert_free(ctx->my_link_cert); + tor_x509_cert_free(ctx->my_id_cert); + tor_x509_cert_free(ctx->my_auth_cert); + crypto_pk_free(ctx->link_key); + crypto_pk_free(ctx->auth_key); + /* LCOV_EXCL_BR_START since ctx will never be NULL here */ + tor_free(ctx); + /* LCOV_EXCL_BR_STOP */ + } +} + +/** Free all global TLS structures. */ +void +tor_tls_free_all(void) +{ + check_no_tls_errors(); + + if (server_tls_context) { + tor_tls_context_t *ctx = server_tls_context; + server_tls_context = NULL; + tor_tls_context_decref(ctx); + } + if (client_tls_context) { + tor_tls_context_t *ctx = client_tls_context; + client_tls_context = NULL; + tor_tls_context_decref(ctx); + } +} + +/** Given a TOR_TLS_* error code, return a string equivalent. */ +const char * +tor_tls_err_to_string(int err) +{ + if (err >= 0) + return "[Not an error.]"; + switch (err) { + case TOR_TLS_ERROR_MISC: return "misc error"; + case TOR_TLS_ERROR_IO: return "unexpected close"; + case TOR_TLS_ERROR_CONNREFUSED: return "connection refused"; + case TOR_TLS_ERROR_CONNRESET: return "connection reset"; + case TOR_TLS_ERROR_NO_ROUTE: return "host unreachable"; + case TOR_TLS_ERROR_TIMEOUT: return "connection timed out"; + case TOR_TLS_CLOSE: return "closed"; + case TOR_TLS_WANTREAD: return "want to read"; + case TOR_TLS_WANTWRITE: return "want to write"; + default: return "(unknown error code)"; + } +} + +/** Create new global client and server TLS contexts. + * + * If <b>server_identity</b> is NULL, this will not generate a server + * TLS context. If TOR_TLS_CTX_IS_PUBLIC_SERVER is set in <b>flags</b>, use + * the same TLS context for incoming and outgoing connections, and + * ignore <b>client_identity</b>. If one of TOR_TLS_CTX_USE_ECDHE_P{224,256} + * is set in <b>flags</b>, use that ECDHE group if possible; otherwise use + * the default ECDHE group. */ +int +tor_tls_context_init(unsigned flags, + crypto_pk_t *client_identity, + crypto_pk_t *server_identity, + unsigned int key_lifetime) +{ + int rv1 = 0; + int rv2 = 0; + const int is_public_server = flags & TOR_TLS_CTX_IS_PUBLIC_SERVER; + check_no_tls_errors(); + + if (is_public_server) { + tor_tls_context_t *new_ctx; + tor_tls_context_t *old_ctx; + + tor_assert(server_identity != NULL); + + rv1 = tor_tls_context_init_one(&server_tls_context, + server_identity, + key_lifetime, flags, 0); + + if (rv1 >= 0) { + new_ctx = server_tls_context; + tor_tls_context_incref(new_ctx); + old_ctx = client_tls_context; + client_tls_context = new_ctx; + + if (old_ctx != NULL) { + tor_tls_context_decref(old_ctx); + } + } else { + tls_log_errors(NULL, LOG_WARN, LD_CRYPTO, + "constructing a TLS context"); + } + } else { + if (server_identity != NULL) { + rv1 = tor_tls_context_init_one(&server_tls_context, + server_identity, + key_lifetime, + flags, + 0); + if (rv1 < 0) + tls_log_errors(NULL, LOG_WARN, LD_CRYPTO, + "constructing a server TLS context"); + } else { + tor_tls_context_t *old_ctx = server_tls_context; + server_tls_context = NULL; + + if (old_ctx != NULL) { + tor_tls_context_decref(old_ctx); + } + } + + rv2 = tor_tls_context_init_one(&client_tls_context, + client_identity, + key_lifetime, + flags, + 1); + if (rv2 < 0) + tls_log_errors(NULL, LOG_WARN, LD_CRYPTO, + "constructing a client TLS context"); + } + + return MIN(rv1, rv2); +} + +/** Create a new global TLS context. + * + * You can call this function multiple times. Each time you call it, + * it generates new certificates; all new connections will use + * the new SSL context. + */ +int +tor_tls_context_init_one(tor_tls_context_t **ppcontext, + crypto_pk_t *identity, + unsigned int key_lifetime, + unsigned int flags, + int is_client) +{ + tor_tls_context_t *new_ctx = tor_tls_context_new(identity, + key_lifetime, + flags, + is_client); + tor_tls_context_t *old_ctx = *ppcontext; + + if (new_ctx != NULL) { + *ppcontext = new_ctx; + + /* Free the old context if one existed. */ + if (old_ctx != NULL) { + /* This is safe even if there are open connections: we reference- + * count tor_tls_context_t objects. */ + tor_tls_context_decref(old_ctx); + } + } + + return ((new_ctx != NULL) ? 0 : -1); +} + +/** Size of the RSA key to use for our TLS link keys */ +#define RSA_LINK_KEY_BITS 2048 + +/** How long do identity certificates live? (sec) */ +#define IDENTITY_CERT_LIFETIME (365*24*60*60) + +/** + * Initialize the certificates and keys for a TLS context <b>result</b> + * + * Other arguments as for tor_tls_context_new(). + */ +int +tor_tls_context_init_certificates(tor_tls_context_t *result, + crypto_pk_t *identity, + unsigned key_lifetime, + unsigned flags) +{ + (void)flags; + int rv = -1; + char *nickname = NULL, *nn2 = NULL; + crypto_pk_t *rsa = NULL, *rsa_auth = NULL; + tor_x509_cert_impl_t *cert = NULL, *idcert = NULL, *authcert = NULL; + + nickname = crypto_random_hostname(8, 20, "www.", ".net"); + +#ifdef DISABLE_V3_LINKPROTO_SERVERSIDE + nn2 = crypto_random_hostname(8, 20, "www.", ".net"); +#else + nn2 = crypto_random_hostname(8, 20, "www.", ".com"); +#endif + + /* Generate short-term RSA key for use with TLS. */ + if (!(rsa = crypto_pk_new())) + goto error; + if (crypto_pk_generate_key_with_bits(rsa, RSA_LINK_KEY_BITS)<0) + goto error; + + /* Generate short-term RSA key for use in the in-protocol ("v3") + * authentication handshake. */ + if (!(rsa_auth = crypto_pk_new())) + goto error; + if (crypto_pk_generate_key(rsa_auth)<0) + goto error; + + /* Create a link certificate signed by identity key. */ + cert = tor_tls_create_certificate(rsa, identity, nickname, nn2, + key_lifetime); + /* Create self-signed certificate for identity key. */ + idcert = tor_tls_create_certificate(identity, identity, nn2, nn2, + IDENTITY_CERT_LIFETIME); + /* Create an authentication certificate signed by identity key. */ + authcert = tor_tls_create_certificate(rsa_auth, identity, nickname, nn2, + key_lifetime); + if (!cert || !idcert || !authcert) { + log_warn(LD_CRYPTO, "Error creating certificate"); + goto error; + } + + result->my_link_cert = tor_x509_cert_new(cert); + cert = NULL; + result->my_id_cert = tor_x509_cert_new(idcert); + idcert = NULL; + result->my_auth_cert = tor_x509_cert_new(authcert); + authcert = NULL; + if (!result->my_link_cert || !result->my_id_cert || !result->my_auth_cert) + goto error; + result->link_key = rsa; + rsa = NULL; + result->auth_key = rsa_auth; + rsa_auth = NULL; + + rv = 0; + error: + + tor_free(nickname); + tor_free(nn2); + + tor_x509_cert_impl_free(cert); + tor_x509_cert_impl_free(idcert); + tor_x509_cert_impl_free(authcert); + crypto_pk_free(rsa); + crypto_pk_free(rsa_auth); + + return rv; +} +/** Make future log messages about <b>tls</b> display the address + * <b>address</b>. + */ +void +tor_tls_set_logged_address(tor_tls_t *tls, const char *address) +{ + tor_assert(tls); + tor_free(tls->address); + tls->address = tor_strdup(address); +} + +/** Return whether this tls initiated the connect (client) or + * received it (server). */ +int +tor_tls_is_server(tor_tls_t *tls) +{ + tor_assert(tls); + return tls->isServer; +} + +/** Release resources associated with a TLS object. Does not close the + * underlying file descriptor. + */ +void +tor_tls_free_(tor_tls_t *tls) +{ + if (!tls) + return; + tor_assert(tls->ssl); + { + size_t r,w; + tor_tls_get_n_raw_bytes(tls,&r,&w); /* ensure written_by_tls is updated */ + } + tor_tls_impl_free(tls->ssl); + tls->ssl = NULL; +#ifdef ENABLE_OPENSSL + tls->negotiated_callback = NULL; +#endif + if (tls->context) + tor_tls_context_decref(tls->context); + tor_free(tls->address); + tls->magic = 0x99999999; + tor_free(tls); +} + +/** If the provided tls connection is authenticated and has a + * certificate chain that is currently valid and signed, then set + * *<b>identity_key</b> to the identity certificate's key and return + * 0. Else, return -1 and log complaints with log-level <b>severity</b>. + */ +int +tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_t **identity) +{ + tor_x509_cert_impl_t *cert = NULL, *id_cert = NULL; + tor_x509_cert_t *peer_x509 = NULL, *id_x509 = NULL; + tor_assert(tls); + tor_assert(identity); + int rv = -1; + + try_to_extract_certs_from_tls(severity, tls, &cert, &id_cert); + if (!cert) + goto done; + if (!id_cert) { + log_fn(severity,LD_PROTOCOL,"No distinct identity certificate found"); + goto done; + } + peer_x509 = tor_x509_cert_new(cert); + id_x509 = tor_x509_cert_new(id_cert); + cert = id_cert = NULL; /* Prevent double-free */ + + if (! tor_tls_cert_is_valid(severity, peer_x509, id_x509, time(NULL), 0)) { + goto done; + } + + *identity = tor_tls_cert_get_key(id_x509); + rv = 0; + + done: + tor_x509_cert_impl_free(cert); + tor_x509_cert_impl_free(id_cert); + tor_x509_cert_free(peer_x509); + tor_x509_cert_free(id_x509); + + return rv; +} diff --git a/src/lib/tls/tortls.h b/src/lib/tls/tortls.h new file mode 100644 index 0000000000..8efc7a1c98 --- /dev/null +++ b/src/lib/tls/tortls.h @@ -0,0 +1,160 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_TORTLS_H +#define TOR_TORTLS_H + +/** + * \file tortls.h + * \brief Headers for tortls.c + **/ + +#include "lib/crypt_ops/crypto_rsa.h" +#include "lib/testsupport/testsupport.h" +#include "lib/net/nettypes.h" + +/* Opaque structure to hold a TLS connection. */ +typedef struct tor_tls_t tor_tls_t; + +#ifdef TORTLS_PRIVATE +#ifdef ENABLE_OPENSSL +struct ssl_st; +struct ssl_ctx_st; +struct ssl_session_st; +typedef struct ssl_ctx_st tor_tls_context_impl_t; +typedef struct ssl_st tor_tls_impl_t; +#else +struct PRFileDesc; +typedef struct PRFileDesc tor_tls_context_impl_t; +typedef struct PRFileDesc tor_tls_impl_t; +#endif +#endif + +struct tor_x509_cert_t; + +/* Possible return values for most tor_tls_* functions. */ +#define MIN_TOR_TLS_ERROR_VAL_ -9 +#define TOR_TLS_ERROR_MISC -9 +/* Rename to unexpected close or something. XXXX */ +#define TOR_TLS_ERROR_IO -8 +#define TOR_TLS_ERROR_CONNREFUSED -7 +#define TOR_TLS_ERROR_CONNRESET -6 +#define TOR_TLS_ERROR_NO_ROUTE -5 +#define TOR_TLS_ERROR_TIMEOUT -4 +#define TOR_TLS_CLOSE -3 +#define TOR_TLS_WANTREAD -2 +#define TOR_TLS_WANTWRITE -1 +#define TOR_TLS_DONE 0 + +/** Collection of case statements for all TLS errors that are not due to + * underlying IO failure. */ +#define CASE_TOR_TLS_ERROR_ANY_NONIO \ + case TOR_TLS_ERROR_MISC: \ + case TOR_TLS_ERROR_CONNREFUSED: \ + case TOR_TLS_ERROR_CONNRESET: \ + case TOR_TLS_ERROR_NO_ROUTE: \ + case TOR_TLS_ERROR_TIMEOUT + +/** Use this macro in a switch statement to catch _any_ TLS error. That way, + * if more errors are added, your switches will still work. */ +#define CASE_TOR_TLS_ERROR_ANY \ + CASE_TOR_TLS_ERROR_ANY_NONIO: \ + case TOR_TLS_ERROR_IO + +#define TOR_TLS_IS_ERROR(rv) ((rv) < TOR_TLS_CLOSE) + +/** Holds a SSL_CTX object and related state used to configure TLS + * connections. + */ +typedef struct tor_tls_context_t tor_tls_context_t; + +const char *tor_tls_err_to_string(int err); +void tor_tls_get_state_description(tor_tls_t *tls, char *buf, size_t sz); +void tor_tls_free_all(void); + +#define TOR_TLS_CTX_IS_PUBLIC_SERVER (1u<<0) +#define TOR_TLS_CTX_USE_ECDHE_P256 (1u<<1) +#define TOR_TLS_CTX_USE_ECDHE_P224 (1u<<2) + +void tor_tls_init(void); +void tls_log_errors(tor_tls_t *tls, int severity, int domain, + const char *doing); +int tor_tls_context_init(unsigned flags, + crypto_pk_t *client_identity, + crypto_pk_t *server_identity, + unsigned int key_lifetime); +void tor_tls_context_incref(tor_tls_context_t *ctx); +void tor_tls_context_decref(tor_tls_context_t *ctx); +tor_tls_context_t *tor_tls_context_get(int is_server); +tor_tls_t *tor_tls_new(tor_socket_t sock, int is_server); +void tor_tls_set_logged_address(tor_tls_t *tls, const char *address); +void tor_tls_set_renegotiate_callback(tor_tls_t *tls, + void (*cb)(tor_tls_t *, void *arg), + void *arg); +int tor_tls_is_server(tor_tls_t *tls); +void tor_tls_release_socket(tor_tls_t *tls); +void tor_tls_free_(tor_tls_t *tls); +#define tor_tls_free(tls) FREE_AND_NULL(tor_tls_t, tor_tls_free_, (tls)) +int tor_tls_peer_has_cert(tor_tls_t *tls); +MOCK_DECL(struct tor_x509_cert_t *,tor_tls_get_peer_cert,(tor_tls_t *tls)); +MOCK_DECL(struct tor_x509_cert_t *,tor_tls_get_own_cert,(tor_tls_t *tls)); +int tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_t **identity); +MOCK_DECL(int, tor_tls_read, (tor_tls_t *tls, char *cp, size_t len)); +int tor_tls_write(tor_tls_t *tls, const char *cp, size_t n); +int tor_tls_handshake(tor_tls_t *tls); +int tor_tls_finish_handshake(tor_tls_t *tls); +void tor_tls_unblock_renegotiation(tor_tls_t *tls); +void tor_tls_block_renegotiation(tor_tls_t *tls); +void tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls); +int tor_tls_get_pending_bytes(tor_tls_t *tls); +size_t tor_tls_get_forced_write_size(tor_tls_t *tls); + +void tor_tls_get_n_raw_bytes(tor_tls_t *tls, + size_t *n_read, size_t *n_written); + +int tor_tls_get_buffer_sizes(tor_tls_t *tls, + size_t *rbuf_capacity, size_t *rbuf_bytes, + size_t *wbuf_capacity, size_t *wbuf_bytes); + +MOCK_DECL(double, tls_get_write_overhead_ratio, (void)); + +int tor_tls_used_v1_handshake(tor_tls_t *tls); +int tor_tls_get_num_server_handshakes(tor_tls_t *tls); +int tor_tls_server_got_renegotiate(tor_tls_t *tls); +MOCK_DECL(int,tor_tls_cert_matches_key,(const tor_tls_t *tls, + const struct tor_x509_cert_t *cert)); +MOCK_DECL(int,tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out)); +#ifdef ENABLE_OPENSSL +/* OpenSSL lets us see these master secrets; NSS sensibly does not. */ +#define HAVE_WORKING_TOR_TLS_GET_TLSSECRETS +#endif +MOCK_DECL(int,tor_tls_export_key_material,( + tor_tls_t *tls, uint8_t *secrets_out, + const uint8_t *context, + size_t context_len, + const char *label)); + +#ifdef ENABLE_OPENSSL +/* Log and abort if there are unhandled TLS errors in OpenSSL's error stack. + */ +#define check_no_tls_errors() check_no_tls_errors_(__FILE__,__LINE__) +void check_no_tls_errors_(const char *fname, int line); + +void tor_tls_log_one_error(tor_tls_t *tls, unsigned long err, + int severity, int domain, const char *doing); +#else +#define check_no_tls_errors() STMT_NIL +#endif + +int tor_tls_get_my_certs(int server, + const struct tor_x509_cert_t **link_cert_out, + const struct tor_x509_cert_t **id_cert_out); +crypto_pk_t *tor_tls_get_my_client_auth_key(void); + +const char *tor_tls_get_ciphersuite_name(tor_tls_t *tls); + +int evaluate_ecgroup_for_tls(const char *ecgroup); + +#endif /* !defined(TOR_TORTLS_H) */ diff --git a/src/lib/tls/tortls_internal.h b/src/lib/tls/tortls_internal.h new file mode 100644 index 0000000000..071c506561 --- /dev/null +++ b/src/lib/tls/tortls_internal.h @@ -0,0 +1,76 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TORTLS_INTERNAL_H +#define TORTLS_INTERNAL_H + +int tor_errno_to_tls_error(int e); +#ifdef ENABLE_OPENSSL +int tor_tls_get_error(tor_tls_t *tls, int r, int extra, + const char *doing, int severity, int domain); +#endif +MOCK_DECL(void, try_to_extract_certs_from_tls, + (int severity, tor_tls_t *tls, + tor_x509_cert_impl_t **cert_out, + tor_x509_cert_impl_t **id_cert_out)); + +tor_tls_context_t *tor_tls_context_new(crypto_pk_t *identity, + unsigned int key_lifetime, unsigned flags, int is_client); +int tor_tls_context_init_one(tor_tls_context_t **ppcontext, + crypto_pk_t *identity, + unsigned int key_lifetime, + unsigned int flags, + int is_client); +int tor_tls_context_init_certificates(tor_tls_context_t *result, + crypto_pk_t *identity, + unsigned key_lifetime, + unsigned flags); +void tor_tls_impl_free_(tor_tls_impl_t *ssl); +#define tor_tls_impl_free(tls) \ + FREE_AND_NULL(tor_tls_impl_t, tor_tls_impl_free_, (tls)) + +void tor_tls_context_impl_free_(tor_tls_context_impl_t *); +#define tor_tls_context_impl_free(ctx) \ + FREE_AND_NULL(tor_tls_context_impl_t, tor_tls_context_impl_free_, (ctx)) + +#ifdef ENABLE_OPENSSL +tor_tls_t *tor_tls_get_by_ssl(const struct ssl_st *ssl); +int tor_tls_client_is_using_v2_ciphers(const struct ssl_st *ssl); +void tor_tls_debug_state_callback(const struct ssl_st *ssl, + int type, int val); +void tor_tls_server_info_callback(const struct ssl_st *ssl, + int type, int val); +void tor_tls_allocate_tor_tls_object_ex_data_index(void); + +#if !defined(HAVE_SSL_SESSION_GET_MASTER_KEY) +size_t SSL_SESSION_get_master_key(struct ssl_session_st *s, + uint8_t *out, + size_t len); +#endif + +#ifdef TORTLS_OPENSSL_PRIVATE +int always_accept_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx); +int tor_tls_classify_client_ciphers(const struct ssl_st *ssl, + STACK_OF(SSL_CIPHER) *peer_ciphers); +STATIC int tor_tls_session_secret_cb(struct ssl_st *ssl, void *secret, + int *secret_len, + STACK_OF(SSL_CIPHER) *peer_ciphers, + CONST_IF_OPENSSL_1_1_API SSL_CIPHER **cipher, + void *arg); +STATIC int find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, + uint16_t cipher); +#endif +#endif + +#ifdef TOR_UNIT_TESTS +extern int tor_tls_object_ex_data_index; +extern tor_tls_context_t *server_tls_context; +extern tor_tls_context_t *client_tls_context; +extern uint16_t v2_cipher_list[]; +extern uint64_t total_bytes_written_over_tls; +extern uint64_t total_bytes_written_by_tls; +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* defined(TORTLS_INTERNAL_H) */ diff --git a/src/lib/tls/tortls_nss.c b/src/lib/tls/tortls_nss.c new file mode 100644 index 0000000000..00c4af0e97 --- /dev/null +++ b/src/lib/tls/tortls_nss.c @@ -0,0 +1,793 @@ +/* Copyright (c) 2003, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tortls_nss.c + * \brief Wrapper functions to present a consistent interface to + * TLS and SSL X.509 functions from NSS. + **/ + +#include "orconfig.h" + +#define TORTLS_PRIVATE +#define TOR_X509_PRIVATE + +#ifdef _WIN32 + #include <winsock2.h> + #include <ws2tcpip.h> +#endif + +#include "lib/crypt_ops/crypto_cipher.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_dh.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/crypt_ops/crypto_nss_mgt.h" +#include "lib/string/printf.h" + +#include "lib/tls/x509.h" +#include "lib/tls/x509_internal.h" +#include "lib/tls/tortls.h" +#include "lib/tls/tortls_st.h" +#include "lib/tls/tortls_internal.h" +#include "lib/tls/nss_countbytes.h" +#include "lib/log/util_bug.h" + +DISABLE_GCC_WARNING(strict-prototypes) +#include <prio.h> +// For access to rar sockets. +#include <private/pprio.h> +#include <ssl.h> +#include <sslt.h> +#include <sslproto.h> +#include <certt.h> +ENABLE_GCC_WARNING(strict-prototypes) + +static SECStatus always_accept_cert_cb(void *, PRFileDesc *, PRBool, PRBool); + +MOCK_IMPL(void, +try_to_extract_certs_from_tls,(int severity, tor_tls_t *tls, + tor_x509_cert_impl_t **cert_out, + tor_x509_cert_impl_t **id_cert_out)) +{ + tor_assert(tls); + tor_assert(cert_out); + tor_assert(id_cert_out); + (void) severity; + + *cert_out = *id_cert_out = NULL; + + CERTCertificate *peer = SSL_PeerCertificate(tls->ssl); + if (!peer) + return; + *cert_out = peer; /* Now owns pointer. */ + + CERTCertList *chain = SSL_PeerCertificateChain(tls->ssl); + CERTCertListNode *c = CERT_LIST_HEAD(chain); + for (; !CERT_LIST_END(c, chain); c = CERT_LIST_NEXT(c)) { + if (CERT_CompareCerts(c->cert, peer) == PR_FALSE) { + *id_cert_out = CERT_DupCertificate(c->cert); + break; + } + } + CERT_DestroyCertList(chain); +} + +static bool +we_like_ssl_cipher(SSLCipherAlgorithm ca) +{ + switch (ca) { + case ssl_calg_null: return false; + case ssl_calg_rc4: return false; + case ssl_calg_rc2: return false; + case ssl_calg_des: return false; + case ssl_calg_3des: return false; /* ???? */ + case ssl_calg_idea: return false; + case ssl_calg_fortezza: return false; + case ssl_calg_camellia: return false; + case ssl_calg_seed: return false; + + case ssl_calg_aes: return true; + case ssl_calg_aes_gcm: return true; + case ssl_calg_chacha20: return true; + default: return true; + } +} +static bool +we_like_ssl_kea(SSLKEAType kt) +{ + switch (kt) { + case ssl_kea_null: return false; + case ssl_kea_rsa: return false; /* ??? */ + case ssl_kea_fortezza: return false; + case ssl_kea_ecdh_psk: return false; + case ssl_kea_dh_psk: return false; + + case ssl_kea_dh: return true; + case ssl_kea_ecdh: return true; + case ssl_kea_tls13_any: return true; + + case ssl_kea_size: return true; /* prevent a warning. */ + default: return true; + } +} + +static bool +we_like_mac_algorithm(SSLMACAlgorithm ma) +{ + switch (ma) { + case ssl_mac_null: return false; + case ssl_mac_md5: return false; + case ssl_hmac_md5: return false; + + case ssl_mac_sha: return true; + case ssl_hmac_sha: return true; + case ssl_hmac_sha256: return true; + case ssl_mac_aead: return true; + case ssl_hmac_sha384: return true; + default: return true; + } +} + +static bool +we_like_auth_type(SSLAuthType at) +{ + switch (at) { + case ssl_auth_null: return false; + case ssl_auth_rsa_decrypt: return false; + case ssl_auth_dsa: return false; + case ssl_auth_kea: return false; + + case ssl_auth_ecdsa: return true; + case ssl_auth_ecdh_rsa: return true; + case ssl_auth_ecdh_ecdsa: return true; + case ssl_auth_rsa_sign: return true; + case ssl_auth_rsa_pss: return true; + case ssl_auth_psk: return true; + case ssl_auth_tls13_any: return true; + + case ssl_auth_size: return true; /* prevent a warning. */ + default: return true; + } +} + +tor_tls_context_t * +tor_tls_context_new(crypto_pk_t *identity, + unsigned int key_lifetime, unsigned flags, int is_client) +{ + SECStatus s; + tor_assert(identity); + + tor_tls_init(); + + tor_tls_context_t *ctx = tor_malloc_zero(sizeof(tor_tls_context_t)); + ctx->refcnt = 1; + + if (! is_client) { + if (tor_tls_context_init_certificates(ctx, identity, + key_lifetime, flags) < 0) { + goto err; + } + } + + { + /* Create the "model" PRFileDesc that we will use to base others on. */ + PRFileDesc *tcp = PR_NewTCPSocket(); + if (!tcp) + goto err; + + ctx->ctx = SSL_ImportFD(NULL, tcp); + if (!ctx->ctx) { + PR_Close(tcp); + goto err; + } + } + + // Configure the certificate. + if (!is_client) { + s = SSL_ConfigServerCert(ctx->ctx, + ctx->my_link_cert->cert, + (SECKEYPrivateKey *) + crypto_pk_get_nss_privkey(ctx->link_key), + NULL, /* ExtraServerCertData */ + 0 /* DataLen */); + if (s != SECSuccess) + goto err; + } + + // We need a certificate from the other side. + if (is_client) { + // XXXX does this do anything? + s = SSL_OptionSet(ctx->ctx, SSL_REQUIRE_CERTIFICATE, PR_TRUE); + if (s != SECSuccess) + goto err; + } + + // Always accept other side's cert; we'll check it ourselves in goofy + // tor ways. + s = SSL_AuthCertificateHook(ctx->ctx, always_accept_cert_cb, NULL); + + // We allow simultaneous read and write. + s = SSL_OptionSet(ctx->ctx, SSL_ENABLE_FDX, PR_TRUE); + if (s != SECSuccess) + goto err; + // XXXX SSL_ROLLBACK_DETECTION?? + // XXXX SSL_ENABLE_ALPN?? + + // Force client-mode or server_mode. + s = SSL_OptionSet(ctx->ctx, + is_client ? SSL_HANDSHAKE_AS_CLIENT : SSL_HANDSHAKE_AS_SERVER, + PR_TRUE); + if (s != SECSuccess) + goto err; + + // Disable everything before TLS 1.0; support everything else. + { + SSLVersionRange vrange; + memset(&vrange, 0, sizeof(vrange)); + s = SSL_VersionRangeGetSupported(ssl_variant_stream, &vrange); + if (s != SECSuccess) + goto err; + if (vrange.min < SSL_LIBRARY_VERSION_TLS_1_0) + vrange.min = SSL_LIBRARY_VERSION_TLS_1_0; + s = SSL_VersionRangeSet(ctx->ctx, &vrange); + if (s != SECSuccess) + goto err; + } + + // Only support strong ciphers. + { + const PRUint16 *ciphers = SSL_GetImplementedCiphers(); + const PRUint16 n_ciphers = SSL_GetNumImplementedCiphers(); + PRUint16 i; + for (i = 0; i < n_ciphers; ++i) { + SSLCipherSuiteInfo info; + memset(&info, 0, sizeof(info)); + s = SSL_GetCipherSuiteInfo(ciphers[i], &info, sizeof(info)); + if (s != SECSuccess) + goto err; + if (BUG(info.cipherSuite != ciphers[i])) + goto err; + int disable = info.effectiveKeyBits < 128 || + info.macBits < 128 || + !we_like_ssl_cipher(info.symCipher) || + !we_like_ssl_kea(info.keaType) || + !we_like_mac_algorithm(info.macAlgorithm) || + !we_like_auth_type(info.authType)/* Requires NSS 3.24 */; + + s = SSL_CipherPrefSet(ctx->ctx, ciphers[i], + disable ? PR_FALSE : PR_TRUE); + if (s != SECSuccess) + goto err; + } + } + + // Only use DH and ECDH keys once. + s = SSL_OptionSet(ctx->ctx, SSL_REUSE_SERVER_ECDHE_KEY, PR_FALSE); + if (s != SECSuccess) + goto err; + + // don't cache sessions. + s = SSL_OptionSet(ctx->ctx, SSL_NO_CACHE, PR_TRUE); + if (s != SECSuccess) + goto err; + + // Enable DH. + s = SSL_OptionSet(ctx->ctx, SSL_ENABLE_SERVER_DHE, PR_TRUE); + if (s != SECSuccess) + goto err; + + // Set DH and ECDH groups. + SSLNamedGroup groups[] = { + ssl_grp_ec_curve25519, + ssl_grp_ec_secp256r1, + ssl_grp_ec_secp224r1, + ssl_grp_ffdhe_2048, + }; + s = SSL_NamedGroupConfig(ctx->ctx, groups, ARRAY_LENGTH(groups)); + if (s != SECSuccess) + goto err; + + // These features are off by default, so we don't need to disable them: + // Session tickets + // Renegotiation + // Compression + + goto done; + err: + tor_tls_context_decref(ctx); + ctx = NULL; + done: + return ctx; +} + +void +tor_tls_context_impl_free_(tor_tls_context_impl_t *ctx) +{ + if (!ctx) + return; + PR_Close(ctx); +} + +void +tor_tls_get_state_description(tor_tls_t *tls, char *buf, size_t sz) +{ + (void)tls; + (void)buf; + (void)sz; + // AFAICT, NSS doesn't expose its internal state. + buf[0]=0; +} + +void +tor_tls_init(void) +{ + tor_nss_countbytes_init(); +} + +void +tls_log_errors(tor_tls_t *tls, int severity, int domain, + const char *doing) +{ + /* This implementation is a little different for NSS than it is for OpenSSL + -- it logs the last error whether anything actually failed or not. So we + have to only call it when something has gone wrong and we have a real + error to report. */ + + (void)tls; + PRErrorCode code = PORT_GetError(); + + const char *addr = tls ? tls->address : NULL; + const char *string = PORT_ErrorToString(code); + const char *name = PORT_ErrorToName(code); + char buf[16]; + if (!string) + string = "<unrecognized>"; + if (!name) { + tor_snprintf(buf, sizeof(buf), "%d", code); + name = buf; + } + + const char *with = addr ? " with " : ""; + addr = addr ? addr : ""; + if (doing) { + log_fn(severity, domain, "TLS error %s while %s%s%s: %s", + name, doing, with, addr, string); + } else { + log_fn(severity, domain, "TLS error %s%s%s: %s", name, string, + with, addr); + } +} + +tor_tls_t * +tor_tls_new(tor_socket_t sock, int is_server) +{ + (void)sock; + tor_tls_context_t *ctx = tor_tls_context_get(is_server); + + PRFileDesc *tcp = NULL; + if (SOCKET_OK(sock)) { + tcp = PR_ImportTCPSocket(sock); + } else { + tcp = PR_NewTCPSocket(); + } + + if (!tcp) + return NULL; + + PRFileDesc *count = tor_wrap_prfiledesc_with_byte_counter(tcp); + if (! count) + return NULL; + + PRFileDesc *ssl = SSL_ImportFD(ctx->ctx, count); + if (!ssl) { + PR_Close(tcp); + return NULL; + } + + tor_tls_t *tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->magic = TOR_TLS_MAGIC; + tls->context = ctx; + tor_tls_context_incref(ctx); + tls->ssl = ssl; + tls->socket = sock; + tls->state = TOR_TLS_ST_HANDSHAKE; + tls->isServer = !!is_server; + + if (!is_server) { + /* Set a random SNI */ + char *fake_hostname = crypto_random_hostname(4,25, "www.",".com"); + SSL_SetURL(tls->ssl, fake_hostname); + tor_free(fake_hostname); + } + SECStatus s = SSL_ResetHandshake(ssl, is_server ? PR_TRUE : PR_FALSE); + if (s != SECSuccess) { + tls_log_errors(tls, LOG_WARN, LD_CRYPTO, "resetting handshake state"); + } + + return tls; +} + +void +tor_tls_set_renegotiate_callback(tor_tls_t *tls, + void (*cb)(tor_tls_t *, void *arg), + void *arg) +{ + tor_assert(tls); + (void)cb; + (void)arg; + + /* We don't support renegotiation-based TLS with NSS. */ +} + +/** + * Tell the TLS library that the underlying socket for <b>tls</b> has been + * closed, and the library should not attempt to free that socket itself. + */ +void +tor_tls_release_socket(tor_tls_t *tls) +{ + if (! tls) + return; + + /* NSS doesn't have the equivalent of BIO_NO_CLOSE. If you replace the + * fd with something that's invalid, it causes a memory leak in PR_Close. + * + * If there were a way to put the PRFileDesc into the CLOSED state, that + * would prevent it from closing its fd -- but there doesn't seem to be a + * supported way to do that either. + * + * So instead: we make a new sacrificial socket, and replace the original + * socket with that one. This seems to be the best we can do, until we + * redesign the mainloop code enough to make this function unnecessary. + */ + tor_socket_t sock = + tor_open_socket_nonblocking(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (! SOCKET_OK(sock)) { + log_warn(LD_NET, "Out of sockets when trying to shut down an NSS " + "connection"); + return; + } + + PRFileDesc *tcp = PR_GetIdentitiesLayer(tls->ssl, PR_NSPR_IO_LAYER); + if (BUG(! tcp)) { + tor_close_socket(sock); + return; + } + + PR_ChangeFileDescNativeHandle(tcp, sock); + /* Tell our socket accounting layer that we don't own this socket any more: + * NSS is about to free it for us. */ + tor_release_socket_ownership(sock); +} + +void +tor_tls_impl_free_(tor_tls_impl_t *tls) +{ + // XXXX This will close the underlying fd, which our OpenSSL version does + // not do! + if (!tls) + return; + + PR_Close(tls); +} + +int +tor_tls_peer_has_cert(tor_tls_t *tls) +{ + CERTCertificate *cert = SSL_PeerCertificate(tls->ssl); + int result = (cert != NULL); + CERT_DestroyCertificate(cert); + return result; +} + +MOCK_IMPL(tor_x509_cert_t *, +tor_tls_get_peer_cert,(tor_tls_t *tls)) +{ + CERTCertificate *cert = SSL_PeerCertificate(tls->ssl); + if (cert) + return tor_x509_cert_new(cert); + else + return NULL; +} + +MOCK_IMPL(tor_x509_cert_t *, +tor_tls_get_own_cert,(tor_tls_t *tls)) +{ + tor_assert(tls); + CERTCertificate *cert = SSL_LocalCertificate(tls->ssl); + if (cert) + return tor_x509_cert_new(cert); + else + return NULL; +} + +MOCK_IMPL(int, +tor_tls_read, (tor_tls_t *tls, char *cp, size_t len)) +{ + tor_assert(tls); + tor_assert(cp); + tor_assert(len < INT_MAX); + + PRInt32 rv = PR_Read(tls->ssl, cp, (int)len); + // log_debug(LD_NET, "PR_Read(%zu) returned %d", n, (int)rv); + if (rv > 0) { + return rv; + } + if (rv == 0) + return TOR_TLS_CLOSE; + PRErrorCode err = PORT_GetError(); + if (err == PR_WOULD_BLOCK_ERROR) { + return TOR_TLS_WANTREAD; // XXXX ???? + } else { + tls_log_errors(tls, LOG_NOTICE, LD_CRYPTO, "reading"); // XXXX + return TOR_TLS_ERROR_MISC; // ???? + } +} + +int +tor_tls_write(tor_tls_t *tls, const char *cp, size_t n) +{ + tor_assert(tls); + tor_assert(cp || n == 0); + tor_assert(n < INT_MAX); + + PRInt32 rv = PR_Write(tls->ssl, cp, (int)n); + // log_debug(LD_NET, "PR_Write(%zu) returned %d", n, (int)rv); + if (rv > 0) { + return rv; + } + if (rv == 0) + return TOR_TLS_ERROR_MISC; + PRErrorCode err = PORT_GetError(); + + if (err == PR_WOULD_BLOCK_ERROR) { + return TOR_TLS_WANTWRITE; // XXXX ???? + } else { + tls_log_errors(tls, LOG_NOTICE, LD_CRYPTO, "writing"); // XXXX + return TOR_TLS_ERROR_MISC; // ???? + } +} + +int +tor_tls_handshake(tor_tls_t *tls) +{ + tor_assert(tls); + tor_assert(tls->state == TOR_TLS_ST_HANDSHAKE); + + SECStatus s = SSL_ForceHandshake(tls->ssl); + if (s == SECSuccess) { + tls->state = TOR_TLS_ST_OPEN; + log_debug(LD_NET, "SSL handshake is supposedly complete."); + return tor_tls_finish_handshake(tls); + } + if (PORT_GetError() == PR_WOULD_BLOCK_ERROR) + return TOR_TLS_WANTREAD; /* XXXX What about wantwrite? */ + + return TOR_TLS_ERROR_MISC; // XXXX +} + +int +tor_tls_finish_handshake(tor_tls_t *tls) +{ + tor_assert(tls); + // We don't need to do any of the weird handshake nonsense stuff on NSS, + // since we only support recent handshakes. + return TOR_TLS_DONE; +} + +void +tor_tls_unblock_renegotiation(tor_tls_t *tls) +{ + tor_assert(tls); + /* We don't support renegotiation with NSS. */ +} + +void +tor_tls_block_renegotiation(tor_tls_t *tls) +{ + tor_assert(tls); + /* We don't support renegotiation with NSS. */ +} + +void +tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls) +{ + tor_assert(tls); + /* We don't support renegotiation with NSS. */ +} + +int +tor_tls_get_pending_bytes(tor_tls_t *tls) +{ + tor_assert(tls); + int n = SSL_DataPending(tls->ssl); + if (n < 0) { + tls_log_errors(tls, LOG_WARN, LD_CRYPTO, "looking up pending bytes"); + return 0; + } + return (int)n; +} + +size_t +tor_tls_get_forced_write_size(tor_tls_t *tls) +{ + tor_assert(tls); + /* NSS doesn't have the same "forced write" restriction as openssl. */ + return 0; +} + +void +tor_tls_get_n_raw_bytes(tor_tls_t *tls, + size_t *n_read, size_t *n_written) +{ + tor_assert(tls); + tor_assert(n_read); + tor_assert(n_written); + uint64_t r, w; + if (tor_get_prfiledesc_byte_counts(tls->ssl, &r, &w) < 0) { + *n_read = *n_written = 0; + return; + } + + *n_read = (size_t)(r - tls->last_read_count); + *n_written = (size_t)(w - tls->last_write_count); + + tls->last_read_count = r; + tls->last_write_count = w; +} + +int +tor_tls_get_buffer_sizes(tor_tls_t *tls, + size_t *rbuf_capacity, size_t *rbuf_bytes, + size_t *wbuf_capacity, size_t *wbuf_bytes) +{ + tor_assert(tls); + tor_assert(rbuf_capacity); + tor_assert(rbuf_bytes); + tor_assert(wbuf_capacity); + tor_assert(wbuf_bytes); + + /* This is an acceptable way to say "we can't measure this." */ + return -1; +} + +MOCK_IMPL(double, +tls_get_write_overhead_ratio, (void)) +{ + /* XXX We don't currently have a way to measure this in NSS; we could do that + * XXX with a PRIO layer, but it'll take a little coding. */ + return 0.95; +} + +int +tor_tls_used_v1_handshake(tor_tls_t *tls) +{ + tor_assert(tls); + /* We don't support or allow the V1 handshake with NSS. + */ + return 0; +} + +int +tor_tls_server_got_renegotiate(tor_tls_t *tls) +{ + tor_assert(tls); + return 0; /* We don't support renegotiation with NSS */ +} + +MOCK_IMPL(int, +tor_tls_cert_matches_key,(const tor_tls_t *tls, + const struct tor_x509_cert_t *cert)) +{ + tor_assert(tls); + tor_assert(cert); + int rv = 0; + + CERTCertificate *peercert = SSL_PeerCertificate(tls->ssl); + if (!peercert) + goto done; + CERTSubjectPublicKeyInfo *peer_info = &peercert->subjectPublicKeyInfo; + CERTSubjectPublicKeyInfo *cert_info = &cert->cert->subjectPublicKeyInfo; + rv = SECOID_CompareAlgorithmID(&peer_info->algorithm, + &cert_info->algorithm) == 0 && + SECITEM_ItemsAreEqual(&peer_info->subjectPublicKey, + &cert_info->subjectPublicKey); + + done: + if (peercert) + CERT_DestroyCertificate(peercert); + return rv; +} + +MOCK_IMPL(int, +tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out)) +{ + tor_assert(tls); + tor_assert(secrets_out); + + /* There's no way to get this information out of NSS. */ + + return -1; +} + +MOCK_IMPL(int, +tor_tls_export_key_material,(tor_tls_t *tls, uint8_t *secrets_out, + const uint8_t *context, + size_t context_len, + const char *label)) +{ + tor_assert(tls); + tor_assert(secrets_out); + tor_assert(context); + tor_assert(label); + tor_assert(strlen(label) <= UINT_MAX); + tor_assert(context_len <= UINT_MAX); + + SECStatus s; + s = SSL_ExportKeyingMaterial(tls->ssl, + label, (unsigned)strlen(label), + PR_TRUE, context, (unsigned)context_len, + secrets_out, DIGEST256_LEN); + + return (s == SECSuccess) ? 0 : -1; +} + +const char * +tor_tls_get_ciphersuite_name(tor_tls_t *tls) +{ + tor_assert(tls); + + SSLChannelInfo channel_info; + SSLCipherSuiteInfo cipher_info; + + memset(&channel_info, 0, sizeof(channel_info)); + memset(&cipher_info, 0, sizeof(cipher_info)); + + SECStatus s = SSL_GetChannelInfo(tls->ssl, + &channel_info, sizeof(channel_info)); + if (s != SECSuccess) + return NULL; + + s = SSL_GetCipherSuiteInfo(channel_info.cipherSuite, + &cipher_info, sizeof(cipher_info)); + if (s != SECSuccess) + return NULL; + + return cipher_info.cipherSuiteName; +} + +/** The group we should use for ecdhe when none was selected. */ +#define SEC_OID_TOR_DEFAULT_ECDHE_GROUP SEC_OID_ANSIX962_EC_PRIME256V1 + +int +evaluate_ecgroup_for_tls(const char *ecgroup) +{ + SECOidTag tag; + + if (!ecgroup) + tag = SEC_OID_TOR_DEFAULT_ECDHE_GROUP; + else if (!strcasecmp(ecgroup, "P256")) + tag = SEC_OID_ANSIX962_EC_PRIME256V1; + else if (!strcasecmp(ecgroup, "P224")) + tag = SEC_OID_SECG_EC_SECP224R1; + else + return 0; + + /* I don't think we need any additional tests here for NSS */ + (void) tag; + + return 1; +} + +static SECStatus +always_accept_cert_cb(void *arg, PRFileDesc *ssl, PRBool checkSig, + PRBool isServer) +{ + (void)arg; + (void)ssl; + (void)checkSig; + (void)isServer; + return SECSuccess; +} diff --git a/src/lib/tls/tortls_openssl.c b/src/lib/tls/tortls_openssl.c new file mode 100644 index 0000000000..e702cb9dfa --- /dev/null +++ b/src/lib/tls/tortls_openssl.c @@ -0,0 +1,1795 @@ +/* Copyright (c) 2003, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tortls.c + * \brief Wrapper functions to present a consistent interface to + * TLS, SSL, and X.509 functions from OpenSSL. + **/ + +/* (Unlike other tor functions, these + * are prefixed with tor_ in order to avoid conflicting with OpenSSL + * functions and variables.) + */ + +#include "orconfig.h" + +#define TORTLS_PRIVATE +#define TORTLS_OPENSSL_PRIVATE +#define TOR_X509_PRIVATE + +#ifdef _WIN32 + /* We need to include these here, or else the dtls1.h header will include + * <winsock.h> and mess things up, in at least some openssl versions. */ + #include <winsock2.h> + #include <ws2tcpip.h> +#endif + +#include "lib/crypt_ops/crypto_cipher.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_dh.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/crypt_ops/compat_openssl.h" +#include "lib/tls/x509.h" +#include "lib/tls/x509_internal.h" + +/* Some versions of OpenSSL declare SSL_get_selected_srtp_profile twice in + * srtp.h. Suppress the GCC warning so we can build with -Wredundant-decl. */ +DISABLE_GCC_WARNING(redundant-decls) + +#include <openssl/opensslv.h> + +#ifdef OPENSSL_NO_EC +#error "We require OpenSSL with ECC support" +#endif + +#include <openssl/ssl.h> +#include <openssl/ssl3.h> +#include <openssl/err.h> +#include <openssl/tls1.h> +#include <openssl/asn1.h> +#include <openssl/bio.h> +#include <openssl/bn.h> +#include <openssl/rsa.h> + +ENABLE_GCC_WARNING(redundant-decls) + +#include "lib/tls/tortls.h" +#include "lib/tls/tortls_st.h" +#include "lib/tls/tortls_internal.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/container/smartlist.h" +#include "lib/string/compat_string.h" +#include "lib/string/printf.h" +#include "lib/net/socket.h" +#include "lib/intmath/cmp.h" +#include "lib/ctime/di_ops.h" +#include "lib/encoding/time_fmt.h" + +#include <stdlib.h> +#include <string.h> + +#include "lib/arch/bytes.h" + +/* Copied from or.h */ +#define LEGAL_NICKNAME_CHARACTERS \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +#define ADDR(tls) (((tls) && (tls)->address) ? tls->address : "peer") + +#if OPENSSL_VERSION_NUMBER < OPENSSL_V(1,0,0,'f') +/* This is a version of OpenSSL before 1.0.0f. It does not have + * the CVE-2011-4576 fix, and as such it can't use RELEASE_BUFFERS and + * SSL3 safely at the same time. + */ +#define DISABLE_SSL3_HANDSHAKE +#endif /* OPENSSL_VERSION_NUMBER < OPENSSL_V(1,0,0,'f') */ + +/* We redefine these so that we can run correctly even if the vendor gives us + * a version of OpenSSL that does not match its header files. (Apple: I am + * looking at you.) + */ +#ifndef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION +#define SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION 0x00040000L +#endif +#ifndef SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION +#define SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION 0x0010 +#endif + +/** Set to true iff openssl bug 7712 has been detected. */ +static int openssl_bug_7712_is_present = 0; + +/** Return values for tor_tls_classify_client_ciphers. + * + * @{ + */ +/** An error occurred when examining the client ciphers */ +#define CIPHERS_ERR -1 +/** The client cipher list indicates that a v1 handshake was in use. */ +#define CIPHERS_V1 1 +/** The client cipher list indicates that the client is using the v2 or the + * v3 handshake, but that it is (probably!) lying about what ciphers it + * supports */ +#define CIPHERS_V2 2 +/** The client cipher list indicates that the client is using the v2 or the + * v3 handshake, and that it is telling the truth about what ciphers it + * supports */ +#define CIPHERS_UNRESTRICTED 3 +/** @} */ + +/** The ex_data index in which we store a pointer to an SSL object's + * corresponding tor_tls_t object. */ +STATIC int tor_tls_object_ex_data_index = -1; + +/** Helper: Allocate tor_tls_object_ex_data_index. */ +void +tor_tls_allocate_tor_tls_object_ex_data_index(void) +{ + if (tor_tls_object_ex_data_index == -1) { + tor_tls_object_ex_data_index = + SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); + tor_assert(tor_tls_object_ex_data_index != -1); + } +} + +/** Helper: given a SSL* pointer, return the tor_tls_t object using that + * pointer. */ +tor_tls_t * +tor_tls_get_by_ssl(const SSL *ssl) +{ + tor_tls_t *result = SSL_get_ex_data(ssl, tor_tls_object_ex_data_index); + if (result) + tor_assert(result->magic == TOR_TLS_MAGIC); + return result; +} + +/** True iff tor_tls_init() has been called. */ +static int tls_library_is_initialized = 0; + +/* Module-internal error codes. */ +#define TOR_TLS_SYSCALL_ (MIN_TOR_TLS_ERROR_VAL_ - 2) +#define TOR_TLS_ZERORETURN_ (MIN_TOR_TLS_ERROR_VAL_ - 1) + +/** Write a description of the current state of <b>tls</b> into the + * <b>sz</b>-byte buffer at <b>buf</b>. */ +void +tor_tls_get_state_description(tor_tls_t *tls, char *buf, size_t sz) +{ + const char *ssl_state; + const char *tortls_state; + + if (PREDICT_UNLIKELY(!tls || !tls->ssl)) { + strlcpy(buf, "(No SSL object)", sz); + return; + } + + ssl_state = SSL_state_string_long(tls->ssl); + switch (tls->state) { +#define CASE(st) case TOR_TLS_ST_##st: tortls_state = " in "#st ; break + CASE(HANDSHAKE); + CASE(OPEN); + CASE(GOTCLOSE); + CASE(SENTCLOSE); + CASE(CLOSED); + CASE(RENEGOTIATE); +#undef CASE + case TOR_TLS_ST_BUFFEREVENT: + tortls_state = ""; + break; + default: + tortls_state = " in unknown TLS state"; + break; + } + + tor_snprintf(buf, sz, "%s%s", ssl_state, tortls_state); +} + +/** Log a single error <b>err</b> as returned by ERR_get_error(), which was + * received while performing an operation <b>doing</b> on <b>tls</b>. Log + * the message at <b>severity</b>, in log domain <b>domain</b>. */ +void +tor_tls_log_one_error(tor_tls_t *tls, unsigned long err, + int severity, int domain, const char *doing) +{ + const char *state = NULL, *addr; + const char *msg, *lib, *func; + + state = (tls && tls->ssl)?SSL_state_string_long(tls->ssl):"---"; + + addr = tls ? tls->address : NULL; + + /* Some errors are known-benign, meaning they are the fault of the other + * side of the connection. The caller doesn't know this, so override the + * priority for those cases. */ + switch (ERR_GET_REASON(err)) { + case SSL_R_HTTP_REQUEST: + case SSL_R_HTTPS_PROXY_REQUEST: + case SSL_R_RECORD_LENGTH_MISMATCH: +#ifndef OPENSSL_1_1_API + case SSL_R_RECORD_TOO_LARGE: +#endif + case SSL_R_UNKNOWN_PROTOCOL: + case SSL_R_UNSUPPORTED_PROTOCOL: + severity = LOG_INFO; + break; + default: + break; + } + + msg = (const char*)ERR_reason_error_string(err); + lib = (const char*)ERR_lib_error_string(err); + func = (const char*)ERR_func_error_string(err); + if (!msg) msg = "(null)"; + if (!lib) lib = "(null)"; + if (!func) func = "(null)"; + if (doing) { + tor_log(severity, domain, "TLS error while %s%s%s: %s (in %s:%s:%s)", + doing, addr?" with ":"", addr?addr:"", + msg, lib, func, state); + } else { + tor_log(severity, domain, "TLS error%s%s: %s (in %s:%s:%s)", + addr?" with ":"", addr?addr:"", + msg, lib, func, state); + } +} + +/** Log all pending tls errors at level <b>severity</b> in log domain + * <b>domain</b>. Use <b>doing</b> to describe our current activities. + */ +void +tls_log_errors(tor_tls_t *tls, int severity, int domain, const char *doing) +{ + unsigned long err; + + while ((err = ERR_get_error()) != 0) { + tor_tls_log_one_error(tls, err, severity, domain, doing); + } +} + +#define CATCH_SYSCALL 1 +#define CATCH_ZERO 2 + +/** Given a TLS object and the result of an SSL_* call, use + * SSL_get_error to determine whether an error has occurred, and if so + * which one. Return one of TOR_TLS_{DONE|WANTREAD|WANTWRITE|ERROR}. + * If extra&CATCH_SYSCALL is true, return TOR_TLS_SYSCALL_ instead of + * reporting syscall errors. If extra&CATCH_ZERO is true, return + * TOR_TLS_ZERORETURN_ instead of reporting zero-return errors. + * + * If an error has occurred, log it at level <b>severity</b> and describe the + * current action as <b>doing</b>. + */ +int +tor_tls_get_error(tor_tls_t *tls, int r, int extra, + const char *doing, int severity, int domain) +{ + int err = SSL_get_error(tls->ssl, r); + int tor_error = TOR_TLS_ERROR_MISC; + switch (err) { + case SSL_ERROR_NONE: + return TOR_TLS_DONE; + case SSL_ERROR_WANT_READ: + return TOR_TLS_WANTREAD; + case SSL_ERROR_WANT_WRITE: + return TOR_TLS_WANTWRITE; + case SSL_ERROR_SYSCALL: + if (extra&CATCH_SYSCALL) + return TOR_TLS_SYSCALL_; + if (r == 0) { + tor_log(severity, LD_NET, "TLS error: unexpected close while %s (%s)", + doing, SSL_state_string_long(tls->ssl)); + tor_error = TOR_TLS_ERROR_IO; + } else { + int e = tor_socket_errno(tls->socket); + tor_log(severity, LD_NET, + "TLS error: <syscall error while %s> (errno=%d: %s; state=%s)", + doing, e, tor_socket_strerror(e), + SSL_state_string_long(tls->ssl)); + tor_error = tor_errno_to_tls_error(e); + } + tls_log_errors(tls, severity, domain, doing); + return tor_error; + case SSL_ERROR_ZERO_RETURN: + if (extra&CATCH_ZERO) + return TOR_TLS_ZERORETURN_; + tor_log(severity, LD_NET, "TLS connection closed while %s in state %s", + doing, SSL_state_string_long(tls->ssl)); + tls_log_errors(tls, severity, domain, doing); + return TOR_TLS_CLOSE; + default: + tls_log_errors(tls, severity, domain, doing); + return TOR_TLS_ERROR_MISC; + } +} + +/** Initialize OpenSSL, unless it has already been initialized. + */ +void +tor_tls_init(void) +{ + check_no_tls_errors(); + + if (!tls_library_is_initialized) { +#ifdef OPENSSL_1_1_API + OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL); +#else + SSL_library_init(); + SSL_load_error_strings(); +#endif + +#if (SIZEOF_VOID_P >= 8 && \ + OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,0,1)) + long version = OpenSSL_version_num(); + + /* LCOV_EXCL_START : we can't test these lines on the same machine */ + if (version >= OPENSSL_V_SERIES(1,0,1)) { + /* Warn if we could *almost* be running with much faster ECDH. + If we're built for a 64-bit target, using OpenSSL 1.0.1, but we + don't have one of the built-in __uint128-based speedups, we are + just one build operation away from an accelerated handshake. + + (We could be looking at OPENSSL_NO_EC_NISTP_64_GCC_128 instead of + doing this test, but that gives compile-time options, not runtime + behavior.) + */ + EC_KEY *key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + const EC_GROUP *g = key ? EC_KEY_get0_group(key) : NULL; + const EC_METHOD *m = g ? EC_GROUP_method_of(g) : NULL; + const int warn = (m == EC_GFp_simple_method() || + m == EC_GFp_mont_method() || + m == EC_GFp_nist_method()); + EC_KEY_free(key); + + if (warn) + log_notice(LD_GENERAL, "We were built to run on a 64-bit CPU, with " + "OpenSSL 1.0.1 or later, but with a version of OpenSSL " + "that apparently lacks accelerated support for the NIST " + "P-224 and P-256 groups. Building openssl with such " + "support (using the enable-ec_nistp_64_gcc_128 option " + "when configuring it) would make ECDH much faster."); + } + /* LCOV_EXCL_STOP */ +#endif /* (SIZEOF_VOID_P >= 8 && ... */ + + tor_tls_allocate_tor_tls_object_ex_data_index(); + + tls_library_is_initialized = 1; + } +} + +/** We need to give OpenSSL a callback to verify certificates. This is + * it: We always accept peer certs and complete the handshake. We + * don't validate them until later. + */ +int +always_accept_verify_cb(int preverify_ok, + X509_STORE_CTX *x509_ctx) +{ + (void) preverify_ok; + (void) x509_ctx; + return 1; +} + +/** List of ciphers that servers should select from when the client might be + * claiming extra unsupported ciphers in order to avoid fingerprinting. */ +static const char SERVER_CIPHER_LIST[] = +#ifdef TLS1_3_TXT_AES_128_GCM_SHA256 + /* This one can never actually get selected, since if the client lists it, + * we will assume that the client is honest, and not use this list. + * Nonetheless we list it if it's available, so that the server doesn't + * conclude that it has no valid ciphers if it's running with TLS1.3. + */ + TLS1_3_TXT_AES_128_GCM_SHA256 ":" +#endif + TLS1_TXT_DHE_RSA_WITH_AES_256_SHA ":" + TLS1_TXT_DHE_RSA_WITH_AES_128_SHA; + +/** List of ciphers that servers should select from when we actually have + * our choice of what cipher to use. */ +static const char UNRESTRICTED_SERVER_CIPHER_LIST[] = + /* Here are the TLS 1.3 ciphers we like, in the order we prefer. */ +#ifdef TLS1_3_TXT_AES_256_GCM_SHA384 + TLS1_3_TXT_AES_256_GCM_SHA384 ":" +#endif +#ifdef TLS1_3_TXT_CHACHA20_POLY1305_SHA256 + TLS1_3_TXT_CHACHA20_POLY1305_SHA256 ":" +#endif +#ifdef TLS1_3_TXT_AES_128_GCM_SHA256 + TLS1_3_TXT_AES_128_GCM_SHA256 ":" +#endif +#ifdef TLS1_3_TXT_AES_128_CCM_SHA256 + TLS1_3_TXT_AES_128_CCM_SHA256 ":" +#endif + + /* This list is autogenerated with the gen_server_ciphers.py script; + * don't hand-edit it. */ +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ":" +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ":" +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_256_SHA384 + TLS1_TXT_ECDHE_RSA_WITH_AES_256_SHA384 ":" +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_128_SHA256 + TLS1_TXT_ECDHE_RSA_WITH_AES_128_SHA256 ":" +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA + TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA ":" +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA + TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA ":" +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_AES_256_GCM_SHA384 + TLS1_TXT_DHE_RSA_WITH_AES_256_GCM_SHA384 ":" +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_AES_128_GCM_SHA256 + TLS1_TXT_DHE_RSA_WITH_AES_128_GCM_SHA256 ":" +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_AES_256_CCM + TLS1_TXT_DHE_RSA_WITH_AES_256_CCM ":" +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_AES_128_CCM + TLS1_TXT_DHE_RSA_WITH_AES_128_CCM ":" +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_AES_256_SHA256 + TLS1_TXT_DHE_RSA_WITH_AES_256_SHA256 ":" +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_AES_128_SHA256 + TLS1_TXT_DHE_RSA_WITH_AES_128_SHA256 ":" +#endif + /* Required */ + TLS1_TXT_DHE_RSA_WITH_AES_256_SHA ":" + /* Required */ + TLS1_TXT_DHE_RSA_WITH_AES_128_SHA ":" +#ifdef TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305 + TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305 ":" +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_CHACHA20_POLY1305 + TLS1_TXT_DHE_RSA_WITH_CHACHA20_POLY1305 +#endif + ; + +/* Note: to set up your own private testing network with link crypto + * disabled, set your Tors' cipher list to + * (SSL3_TXT_RSA_NULL_SHA). If you do this, you won't be able to communicate + * with any of the "real" Tors, though. */ + +#define CIPHER(id, name) name ":" +#define XCIPHER(id, name) +/** List of ciphers that clients should advertise, omitting items that + * our OpenSSL doesn't know about. */ +static const char CLIENT_CIPHER_LIST[] = +#include "ciphers.inc" + /* Tell it not to use SSLv2 ciphers, so that it can select an SSLv3 version + * of any cipher we say. */ + "!SSLv2" + ; +#undef CIPHER +#undef XCIPHER + +/** Return true iff the other side of <b>tls</b> has authenticated to us, and + * the key certified in <b>cert</b> is the same as the key they used to do it. + */ +MOCK_IMPL(int, +tor_tls_cert_matches_key,(const tor_tls_t *tls, const tor_x509_cert_t *cert)) +{ + tor_x509_cert_t *peer = tor_tls_get_peer_cert((tor_tls_t *)tls); + if (!peer) + return 0; + + X509 *peercert = peer->cert; + EVP_PKEY *link_key = NULL, *cert_key = NULL; + int result; + + link_key = X509_get_pubkey(peercert); + cert_key = X509_get_pubkey(cert->cert); + + result = link_key && cert_key && EVP_PKEY_cmp(cert_key, link_key) == 1; + + tor_x509_cert_free(peer); + if (link_key) + EVP_PKEY_free(link_key); + if (cert_key) + EVP_PKEY_free(cert_key); + + return result; +} + +void +tor_tls_context_impl_free_(struct ssl_ctx_st *ctx) +{ + if (!ctx) + return; + SSL_CTX_free(ctx); +} + +/** The group we should use for ecdhe when none was selected. */ +#define NID_tor_default_ecdhe_group NID_X9_62_prime256v1 + +/** Create a new TLS context for use with Tor TLS handshakes. + * <b>identity</b> should be set to the identity key used to sign the + * certificate. + */ +tor_tls_context_t * +tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime, + unsigned flags, int is_client) +{ + EVP_PKEY *pkey = NULL; + tor_tls_context_t *result = NULL; + + tor_tls_init(); + + result = tor_malloc_zero(sizeof(tor_tls_context_t)); + result->refcnt = 1; + + if (! is_client) { + if (tor_tls_context_init_certificates(result, identity, key_lifetime, + flags) < 0) { + goto error; + } + } + +#if 0 + /* Tell OpenSSL to only use TLS1. This may have subtly different results + * from SSLv23_method() with SSLv2 and SSLv3 disabled, so we need to do some + * investigation before we consider adjusting it. It should be compatible + * with existing Tors. */ + if (!(result->ctx = SSL_CTX_new(TLSv1_method()))) + goto error; +#endif /* 0 */ + + /* Tell OpenSSL to use TLS 1.0 or later but not SSL2 or SSL3. */ +#ifdef HAVE_TLS_METHOD + if (!(result->ctx = SSL_CTX_new(TLS_method()))) + goto error; +#else + if (!(result->ctx = SSL_CTX_new(SSLv23_method()))) + goto error; +#endif /* defined(HAVE_TLS_METHOD) */ + +#ifdef HAVE_SSL_CTX_SET_SECURITY_LEVEL + /* Level 1 re-enables RSA1024 and DH1024 for compatibility with old tors */ + SSL_CTX_set_security_level(result->ctx, 1); +#endif + + SSL_CTX_set_options(result->ctx, SSL_OP_NO_SSLv2); + SSL_CTX_set_options(result->ctx, SSL_OP_NO_SSLv3); + + /* Prefer the server's ordering of ciphers: the client's ordering has + * historically been chosen for fingerprinting resistance. */ + SSL_CTX_set_options(result->ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + + /* Disable TLS tickets if they're supported. We never want to use them; + * using them can make our perfect forward secrecy a little worse, *and* + * create an opportunity to fingerprint us (since it's unusual to use them + * with TLS sessions turned off). + * + * In 0.2.4, clients advertise support for them though, to avoid a TLS + * distinguishability vector. This can give us worse PFS, though, if we + * get a server that doesn't set SSL_OP_NO_TICKET. With luck, there will + * be few such servers by the time 0.2.4 is more stable. + */ +#ifdef SSL_OP_NO_TICKET + if (! is_client) { + SSL_CTX_set_options(result->ctx, SSL_OP_NO_TICKET); + } +#endif + + SSL_CTX_set_options(result->ctx, SSL_OP_SINGLE_DH_USE); + SSL_CTX_set_options(result->ctx, SSL_OP_SINGLE_ECDH_USE); + +#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION + SSL_CTX_set_options(result->ctx, + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); +#endif + /* Yes, we know what we are doing here. No, we do not treat a renegotiation + * as authenticating any earlier-received data. + */ + { + SSL_CTX_set_options(result->ctx, + SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); + } + + /* Don't actually allow compression; it uses RAM and time, it makes TLS + * vulnerable to CRIME-style attacks, and most of the data we transmit over + * TLS is encrypted (and therefore uncompressible) anyway. */ +#ifdef SSL_OP_NO_COMPRESSION + SSL_CTX_set_options(result->ctx, SSL_OP_NO_COMPRESSION); +#endif +#if OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,1,0) +#ifndef OPENSSL_NO_COMP + if (result->ctx->comp_methods) + result->ctx->comp_methods = NULL; +#endif +#endif /* OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,1,0) */ + +#ifdef SSL_MODE_RELEASE_BUFFERS + SSL_CTX_set_mode(result->ctx, SSL_MODE_RELEASE_BUFFERS); +#endif + if (! is_client) { + if (result->my_link_cert && + !SSL_CTX_use_certificate(result->ctx, + result->my_link_cert->cert)) { + goto error; + } + if (result->my_id_cert) { + X509_STORE *s = SSL_CTX_get_cert_store(result->ctx); + tor_assert(s); + X509_STORE_add_cert(s, result->my_id_cert->cert); + } + } + SSL_CTX_set_session_cache_mode(result->ctx, SSL_SESS_CACHE_OFF); + if (!is_client) { + tor_assert(result->link_key); + if (!(pkey = crypto_pk_get_openssl_evp_pkey_(result->link_key,1))) + goto error; + if (!SSL_CTX_use_PrivateKey(result->ctx, pkey)) + goto error; + EVP_PKEY_free(pkey); + pkey = NULL; + if (!SSL_CTX_check_private_key(result->ctx)) + goto error; + } + + { + DH *dh = crypto_dh_new_openssl_tls(); + tor_assert(dh); + SSL_CTX_set_tmp_dh(result->ctx, dh); + DH_free(dh); + } +/* We check for this function in two ways, since it might be either a symbol + * or a macro. */ +#if defined(SSL_CTX_set1_groups_list) || defined(HAVE_SSL_CTX_SET1_GROUPS_LIST) + { + const char *list; + if (flags & TOR_TLS_CTX_USE_ECDHE_P224) + list = "P-224:P-256"; + else if (flags & TOR_TLS_CTX_USE_ECDHE_P256) + list = "P-256:P-224"; + else + list = "P-256:P-224"; + int r = (int) SSL_CTX_set1_groups_list(result->ctx, list); + if (r < 0) + goto error; + } +#else + if (! is_client) { + int nid; + EC_KEY *ec_key; + if (flags & TOR_TLS_CTX_USE_ECDHE_P224) + nid = NID_secp224r1; + else if (flags & TOR_TLS_CTX_USE_ECDHE_P256) + nid = NID_X9_62_prime256v1; + else + nid = NID_tor_default_ecdhe_group; + /* Use P-256 for ECDHE. */ + ec_key = EC_KEY_new_by_curve_name(nid); + if (ec_key != NULL) /*XXXX Handle errors? */ + SSL_CTX_set_tmp_ecdh(result->ctx, ec_key); + EC_KEY_free(ec_key); + } +#endif + SSL_CTX_set_verify(result->ctx, SSL_VERIFY_PEER, + always_accept_verify_cb); + /* let us realloc bufs that we're writing from */ + SSL_CTX_set_mode(result->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + return result; + + error: + tls_log_errors(NULL, LOG_WARN, LD_NET, "creating TLS context"); + if (pkey) + EVP_PKEY_free(pkey); + tor_tls_context_decref(result); + return NULL; +} + +/** Invoked when a TLS state changes: log the change at severity 'debug' */ +void +tor_tls_debug_state_callback(const SSL *ssl, int type, int val) +{ + /* LCOV_EXCL_START since this depends on whether debug is captured or not */ + log_debug(LD_HANDSHAKE, "SSL %p is now in state %s [type=%d,val=%d].", + ssl, SSL_state_string_long(ssl), type, val); + /* LCOV_EXCL_STOP */ +} + +/* Return the name of the negotiated ciphersuite in use on <b>tls</b> */ +const char * +tor_tls_get_ciphersuite_name(tor_tls_t *tls) +{ + return SSL_get_cipher(tls->ssl); +} + +/* Here's the old V2 cipher list we sent from 0.2.1.1-alpha up to + * 0.2.3.17-beta. If a client is using this list, we can't believe the ciphers + * that it claims to support. We'll prune this list to remove the ciphers + * *we* don't recognize. */ +STATIC uint16_t v2_cipher_list[] = { + 0xc00a, /* TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CBC_SHA */ + 0xc014, /* TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA */ + 0x0039, /* TLS1_TXT_DHE_RSA_WITH_AES_256_SHA */ + 0x0038, /* TLS1_TXT_DHE_DSS_WITH_AES_256_SHA */ + 0xc00f, /* TLS1_TXT_ECDH_RSA_WITH_AES_256_CBC_SHA */ + 0xc005, /* TLS1_TXT_ECDH_ECDSA_WITH_AES_256_CBC_SHA */ + 0x0035, /* TLS1_TXT_RSA_WITH_AES_256_SHA */ + 0xc007, /* TLS1_TXT_ECDHE_ECDSA_WITH_RC4_128_SHA */ + 0xc009, /* TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_CBC_SHA */ + 0xc011, /* TLS1_TXT_ECDHE_RSA_WITH_RC4_128_SHA */ + 0xc013, /* TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA */ + 0x0033, /* TLS1_TXT_DHE_RSA_WITH_AES_128_SHA */ + 0x0032, /* TLS1_TXT_DHE_DSS_WITH_AES_128_SHA */ + 0xc00c, /* TLS1_TXT_ECDH_RSA_WITH_RC4_128_SHA */ + 0xc00e, /* TLS1_TXT_ECDH_RSA_WITH_AES_128_CBC_SHA */ + 0xc002, /* TLS1_TXT_ECDH_ECDSA_WITH_RC4_128_SHA */ + 0xc004, /* TLS1_TXT_ECDH_ECDSA_WITH_AES_128_CBC_SHA */ + 0x0004, /* SSL3_TXT_RSA_RC4_128_MD5 */ + 0x0005, /* SSL3_TXT_RSA_RC4_128_SHA */ + 0x002f, /* TLS1_TXT_RSA_WITH_AES_128_SHA */ + 0xc008, /* TLS1_TXT_ECDHE_ECDSA_WITH_DES_192_CBC3_SHA */ + 0xc012, /* TLS1_TXT_ECDHE_RSA_WITH_DES_192_CBC3_SHA */ + 0x0016, /* SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA */ + 0x0013, /* SSL3_TXT_EDH_DSS_DES_192_CBC3_SHA */ + 0xc00d, /* TLS1_TXT_ECDH_RSA_WITH_DES_192_CBC3_SHA */ + 0xc003, /* TLS1_TXT_ECDH_ECDSA_WITH_DES_192_CBC3_SHA */ + 0xfeff, /* SSL3_TXT_RSA_FIPS_WITH_3DES_EDE_CBC_SHA */ + 0x000a, /* SSL3_TXT_RSA_DES_192_CBC3_SHA */ + 0 +}; +/** Have we removed the unrecognized ciphers from v2_cipher_list yet? */ +static int v2_cipher_list_pruned = 0; + +/** Return 0 if <b>m</b> does not support the cipher with ID <b>cipher</b>; + * return 1 if it does support it, or if we have no way to tell. */ +int +find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, uint16_t cipher) +{ + const SSL_CIPHER *c; +#ifdef HAVE_SSL_CIPHER_FIND + (void) m; + { + unsigned char cipherid[3]; + tor_assert(ssl); + set_uint16(cipherid, tor_htons(cipher)); + cipherid[2] = 0; /* If ssl23_get_cipher_by_char finds no cipher starting + * with a two-byte 'cipherid', it may look for a v2 + * cipher with the appropriate 3 bytes. */ + c = SSL_CIPHER_find((SSL*)ssl, cipherid); + if (c) + tor_assert((SSL_CIPHER_get_id(c) & 0xffff) == cipher); + return c != NULL; + } +#else /* !(defined(HAVE_SSL_CIPHER_FIND)) */ + +# if defined(HAVE_STRUCT_SSL_METHOD_ST_GET_CIPHER_BY_CHAR) + if (m && m->get_cipher_by_char) { + unsigned char cipherid[3]; + set_uint16(cipherid, tor_htons(cipher)); + cipherid[2] = 0; /* If ssl23_get_cipher_by_char finds no cipher starting + * with a two-byte 'cipherid', it may look for a v2 + * cipher with the appropriate 3 bytes. */ + c = m->get_cipher_by_char(cipherid); + if (c) + tor_assert((c->id & 0xffff) == cipher); + return c != NULL; + } +#endif /* defined(HAVE_STRUCT_SSL_METHOD_ST_GET_CIPHER_BY_CHAR) */ +# ifndef OPENSSL_1_1_API + if (m && m->get_cipher && m->num_ciphers) { + /* It would seem that some of the "let's-clean-up-openssl" forks have + * removed the get_cipher_by_char function. Okay, so now you get a + * quadratic search. + */ + int i; + for (i = 0; i < m->num_ciphers(); ++i) { + c = m->get_cipher(i); + if (c && (c->id & 0xffff) == cipher) { + return 1; + } + } + return 0; + } +#endif /* !defined(OPENSSL_1_1_API) */ + (void) ssl; + (void) m; + (void) cipher; + return 1; /* No way to search */ +#endif /* defined(HAVE_SSL_CIPHER_FIND) */ +} + +/** Remove from v2_cipher_list every cipher that we don't support, so that + * comparing v2_cipher_list to a client's cipher list will give a sensible + * result. */ +static void +prune_v2_cipher_list(const SSL *ssl) +{ + uint16_t *inp, *outp; +#ifdef HAVE_TLS_METHOD + const SSL_METHOD *m = TLS_method(); +#else + const SSL_METHOD *m = SSLv23_method(); +#endif + + inp = outp = v2_cipher_list; + while (*inp) { + if (find_cipher_by_id(ssl, m, *inp)) { + *outp++ = *inp++; + } else { + inp++; + } + } + *outp = 0; + + v2_cipher_list_pruned = 1; +} + +/** Examine the client cipher list in <b>ssl</b>, and determine what kind of + * client it is. Return one of CIPHERS_ERR, CIPHERS_V1, CIPHERS_V2, + * CIPHERS_UNRESTRICTED. + **/ +int +tor_tls_classify_client_ciphers(const SSL *ssl, + STACK_OF(SSL_CIPHER) *peer_ciphers) +{ + int i, res; + tor_tls_t *tor_tls; + if (PREDICT_UNLIKELY(!v2_cipher_list_pruned)) + prune_v2_cipher_list(ssl); + + tor_tls = tor_tls_get_by_ssl(ssl); + if (tor_tls && tor_tls->client_cipher_list_type) + return tor_tls->client_cipher_list_type; + + /* If we reached this point, we just got a client hello. See if there is + * a cipher list. */ + if (!peer_ciphers) { + log_info(LD_NET, "No ciphers on session"); + res = CIPHERS_ERR; + goto done; + } + /* Now we need to see if there are any ciphers whose presence means we're + * dealing with an updated Tor. */ + for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) { + const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(peer_ciphers, i); + const char *ciphername = SSL_CIPHER_get_name(cipher); + if (strcmp(ciphername, TLS1_TXT_DHE_RSA_WITH_AES_128_SHA) && + strcmp(ciphername, TLS1_TXT_DHE_RSA_WITH_AES_256_SHA) && + strcmp(ciphername, SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA) && + strcmp(ciphername, "(NONE)")) { + log_debug(LD_NET, "Got a non-version-1 cipher called '%s'", ciphername); + // return 1; + goto v2_or_higher; + } + } + res = CIPHERS_V1; + goto done; + v2_or_higher: + { + const uint16_t *v2_cipher = v2_cipher_list; + for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) { + const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(peer_ciphers, i); + uint16_t id = SSL_CIPHER_get_id(cipher) & 0xffff; + if (id == 0x00ff) /* extended renegotiation indicator. */ + continue; + if (!id || id != *v2_cipher) { + res = CIPHERS_UNRESTRICTED; + goto dump_ciphers; + } + ++v2_cipher; + } + if (*v2_cipher != 0) { + res = CIPHERS_UNRESTRICTED; + goto dump_ciphers; + } + res = CIPHERS_V2; + } + + dump_ciphers: + { + smartlist_t *elts = smartlist_new(); + char *s; + for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) { + const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(peer_ciphers, i); + const char *ciphername = SSL_CIPHER_get_name(cipher); + smartlist_add(elts, (char*)ciphername); + } + s = smartlist_join_strings(elts, ":", 0, NULL); + log_debug(LD_NET, "Got a %s V2/V3 cipher list from %s. It is: '%s'", + (res == CIPHERS_V2) ? "fictitious" : "real", ADDR(tor_tls), s); + tor_free(s); + smartlist_free(elts); + } + done: + if (tor_tls) + return tor_tls->client_cipher_list_type = res; + + return res; +} + +/** Return true iff the cipher list suggested by the client for <b>ssl</b> is + * a list that indicates that the client knows how to do the v2 TLS connection + * handshake. */ +int +tor_tls_client_is_using_v2_ciphers(const SSL *ssl) +{ + STACK_OF(SSL_CIPHER) *ciphers; +#ifdef HAVE_SSL_GET_CLIENT_CIPHERS + ciphers = SSL_get_client_ciphers(ssl); +#else + SSL_SESSION *session; + if (!(session = SSL_get_session((SSL *)ssl))) { + log_info(LD_NET, "No session on TLS?"); + return CIPHERS_ERR; + } + ciphers = session->ciphers; +#endif /* defined(HAVE_SSL_GET_CLIENT_CIPHERS) */ + + return tor_tls_classify_client_ciphers(ssl, ciphers) >= CIPHERS_V2; +} + +/** Invoked when we're accepting a connection on <b>ssl</b>, and the connection + * changes state. We use this: + * <ul><li>To alter the state of the handshake partway through, so we + * do not send or request extra certificates in v2 handshakes.</li> + * <li>To detect renegotiation</li></ul> + */ +void +tor_tls_server_info_callback(const SSL *ssl, int type, int val) +{ + tor_tls_t *tls; + (void) val; + + IF_BUG_ONCE(ssl == NULL) { + return; // LCOV_EXCL_LINE + } + + tor_tls_debug_state_callback(ssl, type, val); + + if (type != SSL_CB_ACCEPT_LOOP) + return; + + OSSL_HANDSHAKE_STATE ssl_state = SSL_get_state(ssl); + if (! STATE_IS_SW_SERVER_HELLO(ssl_state)) + return; + tls = tor_tls_get_by_ssl(ssl); + if (tls) { + /* Check whether we're watching for renegotiates. If so, this is one! */ + if (tls->negotiated_callback) + tls->got_renegotiate = 1; + } else { + log_warn(LD_BUG, "Couldn't look up the tls for an SSL*. How odd!"); + return; + } + + /* Now check the cipher list. */ + if (tor_tls_client_is_using_v2_ciphers(ssl)) { + if (tls->wasV2Handshake) + return; /* We already turned this stuff off for the first handshake; + * This is a renegotiation. */ + + /* Yes, we're casting away the const from ssl. This is very naughty of us. + * Let's hope openssl doesn't notice! */ + + /* Set SSL_MODE_NO_AUTO_CHAIN to keep from sending back any extra certs. */ + SSL_set_mode((SSL*) ssl, SSL_MODE_NO_AUTO_CHAIN); + /* Don't send a hello request. */ + SSL_set_verify((SSL*) ssl, SSL_VERIFY_NONE, NULL); + + if (tls) { + tls->wasV2Handshake = 1; + } else { + /* LCOV_EXCL_START this line is not reachable */ + log_warn(LD_BUG, "Couldn't look up the tls for an SSL*. How odd!"); + /* LCOV_EXCL_STOP */ + } + } +} + +/** Callback to get invoked on a server after we've read the list of ciphers + * the client supports, but before we pick our own ciphersuite. + * + * We can't abuse an info_cb for this, since by the time one of the + * client_hello info_cbs is called, we've already picked which ciphersuite to + * use. + * + * Technically, this function is an abuse of this callback, since the point of + * a session_secret_cb is to try to set up and/or verify a shared-secret for + * authentication on the fly. But as long as we return 0, we won't actually be + * setting up a shared secret, and all will be fine. + */ +int +tor_tls_session_secret_cb(SSL *ssl, void *secret, int *secret_len, + STACK_OF(SSL_CIPHER) *peer_ciphers, + CONST_IF_OPENSSL_1_1_API SSL_CIPHER **cipher, + void *arg) +{ + (void) secret; + (void) secret_len; + (void) peer_ciphers; + (void) cipher; + (void) arg; + + if (tor_tls_classify_client_ciphers(ssl, peer_ciphers) == + CIPHERS_UNRESTRICTED) { + SSL_set_cipher_list(ssl, UNRESTRICTED_SERVER_CIPHER_LIST); + } + + SSL_set_session_secret_cb(ssl, NULL, NULL); + + return 0; +} +static void +tor_tls_setup_session_secret_cb(tor_tls_t *tls) +{ + SSL_set_session_secret_cb(tls->ssl, tor_tls_session_secret_cb, NULL); +} + +/** Create a new TLS object from a file descriptor, and a flag to + * determine whether it is functioning as a server. + */ +tor_tls_t * +tor_tls_new(tor_socket_t sock, int isServer) +{ + BIO *bio = NULL; + tor_tls_t *result = tor_malloc_zero(sizeof(tor_tls_t)); + tor_tls_context_t *context = tor_tls_context_get(isServer); + result->magic = TOR_TLS_MAGIC; + + check_no_tls_errors(); + tor_assert(context); /* make sure somebody made it first */ + if (!(result->ssl = SSL_new(context->ctx))) { + tls_log_errors(NULL, LOG_WARN, LD_NET, "creating SSL object"); + tor_free(result); + goto err; + } + +#ifdef SSL_set_tlsext_host_name + /* Browsers use the TLS hostname extension, so we should too. */ + if (!isServer) { + char *fake_hostname = crypto_random_hostname(4,25, "www.",".com"); + SSL_set_tlsext_host_name(result->ssl, fake_hostname); + tor_free(fake_hostname); + } +#endif /* defined(SSL_set_tlsext_host_name) */ + +#ifdef SSL_CTRL_SET_MAX_PROTO_VERSION + if (openssl_bug_7712_is_present) { + /* We can't actually use TLS 1.3 until this bug is fixed. */ + SSL_set_max_proto_version(result->ssl, TLS1_2_VERSION); + } +#endif + + if (!SSL_set_cipher_list(result->ssl, + isServer ? SERVER_CIPHER_LIST : CLIENT_CIPHER_LIST)) { + tls_log_errors(NULL, LOG_WARN, LD_NET, "setting ciphers"); +#ifdef SSL_set_tlsext_host_name + SSL_set_tlsext_host_name(result->ssl, NULL); +#endif + SSL_free(result->ssl); + tor_free(result); + goto err; + } + result->socket = sock; + bio = BIO_new_socket(sock, BIO_CLOSE); + if (! bio) { + tls_log_errors(NULL, LOG_WARN, LD_NET, "opening BIO"); +#ifdef SSL_set_tlsext_host_name + SSL_set_tlsext_host_name(result->ssl, NULL); +#endif + SSL_free(result->ssl); + tor_free(result); + goto err; + } + { + int set_worked = + SSL_set_ex_data(result->ssl, tor_tls_object_ex_data_index, result); + if (!set_worked) { + log_warn(LD_BUG, + "Couldn't set the tls for an SSL*; connection will fail"); + } + } + SSL_set_bio(result->ssl, bio, bio); + tor_tls_context_incref(context); + result->context = context; + result->state = TOR_TLS_ST_HANDSHAKE; + result->isServer = isServer; + result->wantwrite_n = 0; + result->last_write_count = (unsigned long) BIO_number_written(bio); + result->last_read_count = (unsigned long) BIO_number_read(bio); + if (result->last_write_count || result->last_read_count) { + log_warn(LD_NET, "Newly created BIO has read count %lu, write count %lu", + result->last_read_count, result->last_write_count); + } + if (isServer) { + SSL_set_info_callback(result->ssl, tor_tls_server_info_callback); + } else { + SSL_set_info_callback(result->ssl, tor_tls_debug_state_callback); + } + + if (isServer) + tor_tls_setup_session_secret_cb(result); + + goto done; + err: + result = NULL; + done: + /* Not expected to get called. */ + tls_log_errors(NULL, LOG_WARN, LD_NET, "creating tor_tls_t object"); + return result; +} + +/** Set <b>cb</b> to be called with argument <b>arg</b> whenever <b>tls</b> + * next gets a client-side renegotiate in the middle of a read. Do not + * invoke this function until <em>after</em> initial handshaking is done! + */ +void +tor_tls_set_renegotiate_callback(tor_tls_t *tls, + void (*cb)(tor_tls_t *, void *arg), + void *arg) +{ + tls->negotiated_callback = cb; + tls->callback_arg = arg; + tls->got_renegotiate = 0; + if (cb) { + SSL_set_info_callback(tls->ssl, tor_tls_server_info_callback); + } else { + SSL_set_info_callback(tls->ssl, tor_tls_debug_state_callback); + } +} + +/** If this version of openssl requires it, turn on renegotiation on + * <b>tls</b>. + */ +void +tor_tls_unblock_renegotiation(tor_tls_t *tls) +{ + /* Yes, we know what we are doing here. No, we do not treat a renegotiation + * as authenticating any earlier-received data. */ + SSL_set_options(tls->ssl, + SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); +} + +/** If this version of openssl supports it, turn off renegotiation on + * <b>tls</b>. (Our protocol never requires this for security, but it's nice + * to use belt-and-suspenders here.) + */ +void +tor_tls_block_renegotiation(tor_tls_t *tls) +{ +#ifdef SUPPORT_UNSAFE_RENEGOTIATION_FLAG + tls->ssl->s3->flags &= ~SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; +#else + (void) tls; +#endif +} + +/** Assert that the flags that allow legacy renegotiation are still set */ +void +tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls) +{ +#if defined(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION) && \ + SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION != 0 + long options = SSL_get_options(tls->ssl); + tor_assert(0 != (options & SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)); +#else + (void) tls; +#endif /* defined(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION) && ... */ +} + +/** + * Tell the TLS library that the underlying socket for <b>tls</b> has been + * closed, and the library should not attempt to free that socket itself. + */ +void +tor_tls_release_socket(tor_tls_t *tls) +{ + if (! tls) + return; + + BIO *rbio, *wbio; + rbio = SSL_get_rbio(tls->ssl); + wbio = SSL_get_wbio(tls->ssl); + + if (rbio) { + (void) BIO_set_close(rbio, BIO_NOCLOSE); + } + if (wbio && wbio != rbio) { + (void) BIO_set_close(wbio, BIO_NOCLOSE); + } +} + +void +tor_tls_impl_free_(tor_tls_impl_t *ssl) +{ + if (!ssl) + return; + +#ifdef SSL_set_tlsext_host_name + SSL_set_tlsext_host_name(ssl, NULL); +#endif + SSL_free(ssl); +} + +/** Underlying function for TLS reading. Reads up to <b>len</b> + * characters from <b>tls</b> into <b>cp</b>. On success, returns the + * number of characters read. On failure, returns TOR_TLS_ERROR, + * TOR_TLS_CLOSE, TOR_TLS_WANTREAD, or TOR_TLS_WANTWRITE. + */ +MOCK_IMPL(int, +tor_tls_read,(tor_tls_t *tls, char *cp, size_t len)) +{ + int r, err; + tor_assert(tls); + tor_assert(tls->ssl); + tor_assert(tls->state == TOR_TLS_ST_OPEN); + tor_assert(len<INT_MAX); + r = SSL_read(tls->ssl, cp, (int)len); + if (r > 0) { + if (tls->got_renegotiate) { + /* Renegotiation happened! */ + log_info(LD_NET, "Got a TLS renegotiation from %s", ADDR(tls)); + if (tls->negotiated_callback) + tls->negotiated_callback(tls, tls->callback_arg); + tls->got_renegotiate = 0; + } + return r; + } + err = tor_tls_get_error(tls, r, CATCH_ZERO, "reading", LOG_DEBUG, LD_NET); + if (err == TOR_TLS_ZERORETURN_ || err == TOR_TLS_CLOSE) { + log_debug(LD_NET,"read returned r=%d; TLS is closed",r); + tls->state = TOR_TLS_ST_CLOSED; + return TOR_TLS_CLOSE; + } else { + tor_assert(err != TOR_TLS_DONE); + log_debug(LD_NET,"read returned r=%d, err=%d",r,err); + return err; + } +} + +/** Total number of bytes that we've used TLS to send. Used to track TLS + * overhead. */ +STATIC uint64_t total_bytes_written_over_tls = 0; +/** Total number of bytes that TLS has put on the network for us. Used to + * track TLS overhead. */ +STATIC uint64_t total_bytes_written_by_tls = 0; + +/** Underlying function for TLS writing. Write up to <b>n</b> + * characters from <b>cp</b> onto <b>tls</b>. On success, returns the + * number of characters written. On failure, returns TOR_TLS_ERROR, + * TOR_TLS_WANTREAD, or TOR_TLS_WANTWRITE. + */ +int +tor_tls_write(tor_tls_t *tls, const char *cp, size_t n) +{ + int r, err; + tor_assert(tls); + tor_assert(tls->ssl); + tor_assert(tls->state == TOR_TLS_ST_OPEN); + tor_assert(n < INT_MAX); + if (n == 0) + return 0; + if (tls->wantwrite_n) { + /* if WANTWRITE last time, we must use the _same_ n as before */ + tor_assert(n >= tls->wantwrite_n); + log_debug(LD_NET,"resuming pending-write, (%d to flush, reusing %d)", + (int)n, (int)tls->wantwrite_n); + n = tls->wantwrite_n; + tls->wantwrite_n = 0; + } + r = SSL_write(tls->ssl, cp, (int)n); + err = tor_tls_get_error(tls, r, 0, "writing", LOG_INFO, LD_NET); + if (err == TOR_TLS_DONE) { + total_bytes_written_over_tls += r; + return r; + } + if (err == TOR_TLS_WANTWRITE || err == TOR_TLS_WANTREAD) { + tls->wantwrite_n = n; + } + return err; +} + +/** Perform initial handshake on <b>tls</b>. When finished, returns + * TOR_TLS_DONE. On failure, returns TOR_TLS_ERROR, TOR_TLS_WANTREAD, + * or TOR_TLS_WANTWRITE. + */ +int +tor_tls_handshake(tor_tls_t *tls) +{ + int r; + tor_assert(tls); + tor_assert(tls->ssl); + tor_assert(tls->state == TOR_TLS_ST_HANDSHAKE); + + check_no_tls_errors(); + + OSSL_HANDSHAKE_STATE oldstate = SSL_get_state(tls->ssl); + + if (tls->isServer) { + log_debug(LD_HANDSHAKE, "About to call SSL_accept on %p (%s)", tls, + SSL_state_string_long(tls->ssl)); + r = SSL_accept(tls->ssl); + } else { + log_debug(LD_HANDSHAKE, "About to call SSL_connect on %p (%s)", tls, + SSL_state_string_long(tls->ssl)); + r = SSL_connect(tls->ssl); + } + + OSSL_HANDSHAKE_STATE newstate = SSL_get_state(tls->ssl); + + if (oldstate != newstate) + log_debug(LD_HANDSHAKE, "After call, %p was in state %s", + tls, SSL_state_string_long(tls->ssl)); + /* We need to call this here and not earlier, since OpenSSL has a penchant + * for clearing its flags when you say accept or connect. */ + tor_tls_unblock_renegotiation(tls); + r = tor_tls_get_error(tls,r,0, "handshaking", LOG_INFO, LD_HANDSHAKE); + if (ERR_peek_error() != 0) { + tls_log_errors(tls, tls->isServer ? LOG_INFO : LOG_WARN, LD_HANDSHAKE, + "handshaking"); + return TOR_TLS_ERROR_MISC; + } + if (r == TOR_TLS_DONE) { + tls->state = TOR_TLS_ST_OPEN; + return tor_tls_finish_handshake(tls); + } + return r; +} + +/** Perform the final part of the initial TLS handshake on <b>tls</b>. This + * should be called for the first handshake only: it determines whether the v1 + * or the v2 handshake was used, and adjusts things for the renegotiation + * handshake as appropriate. + * + * tor_tls_handshake() calls this on its own; you only need to call this if + * bufferevent is doing the handshake for you. + */ +int +tor_tls_finish_handshake(tor_tls_t *tls) +{ + int r = TOR_TLS_DONE; + check_no_tls_errors(); + if (tls->isServer) { + SSL_set_info_callback(tls->ssl, NULL); + SSL_set_verify(tls->ssl, SSL_VERIFY_PEER, always_accept_verify_cb); + SSL_clear_mode(tls->ssl, SSL_MODE_NO_AUTO_CHAIN); + if (tor_tls_client_is_using_v2_ciphers(tls->ssl)) { + /* This check is redundant, but back when we did it in the callback, + * we might have not been able to look up the tor_tls_t if the code + * was buggy. Fixing that. */ + if (!tls->wasV2Handshake) { + log_warn(LD_BUG, "For some reason, wasV2Handshake didn't" + " get set. Fixing that."); + } + tls->wasV2Handshake = 1; + log_debug(LD_HANDSHAKE, "Completed V2 TLS handshake with client; waiting" + " for renegotiation."); + } else { + tls->wasV2Handshake = 0; + } + } else { + /* Client-side */ + tls->wasV2Handshake = 1; + /* XXXX this can move, probably? -NM */ + if (SSL_set_cipher_list(tls->ssl, SERVER_CIPHER_LIST) == 0) { + tls_log_errors(NULL, LOG_WARN, LD_HANDSHAKE, "re-setting ciphers"); + r = TOR_TLS_ERROR_MISC; + } + } + tls_log_errors(NULL, LOG_WARN, LD_NET, "finishing the handshake"); + return r; +} + +/** Return true iff this TLS connection is authenticated. + */ +int +tor_tls_peer_has_cert(tor_tls_t *tls) +{ + X509 *cert; + cert = SSL_get_peer_certificate(tls->ssl); + tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, "getting peer certificate"); + if (!cert) + return 0; + X509_free(cert); + return 1; +} + +/** Return a newly allocated copy of the peer certificate, or NULL if there + * isn't one. */ +MOCK_IMPL(tor_x509_cert_t *, +tor_tls_get_peer_cert,(tor_tls_t *tls)) +{ + X509 *cert; + cert = SSL_get_peer_certificate(tls->ssl); + tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, "getting peer certificate"); + if (!cert) + return NULL; + return tor_x509_cert_new(cert); +} + +/** Return a newly allocated copy of the cerficate we used on the connection, + * or NULL if somehow we didn't use one. */ +MOCK_IMPL(tor_x509_cert_t *, +tor_tls_get_own_cert,(tor_tls_t *tls)) +{ + X509 *cert = SSL_get_certificate(tls->ssl); + tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, + "getting own-connection certificate"); + if (!cert) + return NULL; + /* Fun inconsistency: SSL_get_peer_certificate increments the reference + * count, but SSL_get_certificate does not. */ + X509 *duplicate = X509_dup(cert); + if (BUG(duplicate == NULL)) + return NULL; + return tor_x509_cert_new(duplicate); +} + +/** Helper function: try to extract a link certificate and an identity + * certificate from <b>tls</b>, and store them in *<b>cert_out</b> and + * *<b>id_cert_out</b> respectively. Log all messages at level + * <b>severity</b>. + * + * Note that a reference is added both of the returned certificates. */ +MOCK_IMPL(void, +try_to_extract_certs_from_tls,(int severity, tor_tls_t *tls, + X509 **cert_out, X509 **id_cert_out)) +{ + X509 *cert = NULL, *id_cert = NULL; + STACK_OF(X509) *chain = NULL; + int num_in_chain, i; + *cert_out = *id_cert_out = NULL; + if (!(cert = SSL_get_peer_certificate(tls->ssl))) + return; + *cert_out = cert; + if (!(chain = SSL_get_peer_cert_chain(tls->ssl))) + return; + num_in_chain = sk_X509_num(chain); + /* 1 means we're receiving (server-side), and it's just the id_cert. + * 2 means we're connecting (client-side), and it's both the link + * cert and the id_cert. + */ + if (num_in_chain < 1) { + log_fn(severity,LD_PROTOCOL, + "Unexpected number of certificates in chain (%d)", + num_in_chain); + return; + } + for (i=0; i<num_in_chain; ++i) { + id_cert = sk_X509_value(chain, i); + if (X509_cmp(id_cert, cert) != 0) + break; + } + *id_cert_out = id_cert ? X509_dup(id_cert) : NULL; +} + +/** Return the number of bytes available for reading from <b>tls</b>. + */ +int +tor_tls_get_pending_bytes(tor_tls_t *tls) +{ + tor_assert(tls); + return SSL_pending(tls->ssl); +} + +/** If <b>tls</b> requires that the next write be of a particular size, + * return that size. Otherwise, return 0. */ +size_t +tor_tls_get_forced_write_size(tor_tls_t *tls) +{ + return tls->wantwrite_n; +} + +/** Sets n_read and n_written to the number of bytes read and written, + * respectively, on the raw socket used by <b>tls</b> since the last time this + * function was called on <b>tls</b>. */ +void +tor_tls_get_n_raw_bytes(tor_tls_t *tls, size_t *n_read, size_t *n_written) +{ + BIO *wbio, *tmpbio; + unsigned long r, w; + r = (unsigned long) BIO_number_read(SSL_get_rbio(tls->ssl)); + /* We want the number of bytes actually for real written. Unfortunately, + * sometimes OpenSSL replaces the wbio on tls->ssl with a buffering bio, + * which makes the answer turn out wrong. Let's cope with that. Note + * that this approach will fail if we ever replace tls->ssl's BIOs with + * buffering bios for reasons of our own. As an alternative, we could + * save the original BIO for tls->ssl in the tor_tls_t structure, but + * that would be tempting fate. */ + wbio = SSL_get_wbio(tls->ssl); +#if OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5) + /* BIO structure is opaque as of OpenSSL 1.1.0-pre5-dev. Again, not + * supposed to use this form of the version macro, but the OpenSSL developers + * introduced major API changes in the pre-release stage. + */ + if (BIO_method_type(wbio) == BIO_TYPE_BUFFER && + (tmpbio = BIO_next(wbio)) != NULL) + wbio = tmpbio; +#else /* !(OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5)) */ + if (wbio->method == BIO_f_buffer() && (tmpbio = BIO_next(wbio)) != NULL) + wbio = tmpbio; +#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5) */ + w = (unsigned long) BIO_number_written(wbio); + + /* We are ok with letting these unsigned ints go "negative" here: + * If we wrapped around, this should still give us the right answer, unless + * we wrapped around by more than ULONG_MAX since the last time we called + * this function. + */ + *n_read = (size_t)(r - tls->last_read_count); + *n_written = (size_t)(w - tls->last_write_count); + if (*n_read > INT_MAX || *n_written > INT_MAX) { + log_warn(LD_BUG, "Preposterously large value in tor_tls_get_n_raw_bytes. " + "r=%lu, last_read=%lu, w=%lu, last_written=%lu", + r, tls->last_read_count, w, tls->last_write_count); + } + total_bytes_written_by_tls += *n_written; + tls->last_read_count = r; + tls->last_write_count = w; +} + +/** Return a ratio of the bytes that TLS has sent to the bytes that we've told + * it to send. Used to track whether our TLS records are getting too tiny. */ +MOCK_IMPL(double, +tls_get_write_overhead_ratio,(void)) +{ + if (total_bytes_written_over_tls == 0) + return 1.0; + + return ((double)total_bytes_written_by_tls) / + ((double)total_bytes_written_over_tls); +} + +/** Implement check_no_tls_errors: If there are any pending OpenSSL + * errors, log an error message. */ +void +check_no_tls_errors_(const char *fname, int line) +{ + if (ERR_peek_error() == 0) + return; + log_warn(LD_CRYPTO, "Unhandled OpenSSL errors found at %s:%d: ", + tor_fix_source_file(fname), line); + tls_log_errors(NULL, LOG_WARN, LD_NET, NULL); +} + +/** Return true iff the initial TLS connection at <b>tls</b> did not use a v2 + * TLS handshake. Output is undefined if the handshake isn't finished. */ +int +tor_tls_used_v1_handshake(tor_tls_t *tls) +{ + return ! tls->wasV2Handshake; +} + +/** Return true iff the server TLS connection <b>tls</b> got the renegotiation + * request it was waiting for. */ +int +tor_tls_server_got_renegotiate(tor_tls_t *tls) +{ + return tls->got_renegotiate; +} + +#ifndef HAVE_SSL_GET_CLIENT_RANDOM +static size_t +SSL_get_client_random(SSL *s, uint8_t *out, size_t len) +{ + if (len == 0) + return SSL3_RANDOM_SIZE; + tor_assert(len == SSL3_RANDOM_SIZE); + tor_assert(s->s3); + memcpy(out, s->s3->client_random, len); + return len; +} +#endif /* !defined(HAVE_SSL_GET_CLIENT_RANDOM) */ + +#ifndef HAVE_SSL_GET_SERVER_RANDOM +static size_t +SSL_get_server_random(SSL *s, uint8_t *out, size_t len) +{ + if (len == 0) + return SSL3_RANDOM_SIZE; + tor_assert(len == SSL3_RANDOM_SIZE); + tor_assert(s->s3); + memcpy(out, s->s3->server_random, len); + return len; +} +#endif /* !defined(HAVE_SSL_GET_SERVER_RANDOM) */ + +#ifndef HAVE_SSL_SESSION_GET_MASTER_KEY +size_t +SSL_SESSION_get_master_key(SSL_SESSION *s, uint8_t *out, size_t len) +{ + tor_assert(s); + if (len == 0) + return s->master_key_length; + tor_assert(len == (size_t)s->master_key_length); + tor_assert(out); + memcpy(out, s->master_key, len); + return len; +} +#endif /* !defined(HAVE_SSL_SESSION_GET_MASTER_KEY) */ + +/** Set the DIGEST256_LEN buffer at <b>secrets_out</b> to the value used in + * the v3 handshake to prove that the client knows the TLS secrets for the + * connection <b>tls</b>. Return 0 on success, -1 on failure. + */ +MOCK_IMPL(int, +tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out)) +{ +#define TLSSECRET_MAGIC "Tor V3 handshake TLS cross-certification" + uint8_t buf[128]; + size_t len; + tor_assert(tls); + + SSL *const ssl = tls->ssl; + SSL_SESSION *const session = SSL_get_session(ssl); + + tor_assert(ssl); + tor_assert(session); + + const size_t server_random_len = SSL_get_server_random(ssl, NULL, 0); + const size_t client_random_len = SSL_get_client_random(ssl, NULL, 0); + const size_t master_key_len = SSL_SESSION_get_master_key(session, NULL, 0); + + tor_assert(server_random_len); + tor_assert(client_random_len); + tor_assert(master_key_len); + + len = client_random_len + server_random_len + strlen(TLSSECRET_MAGIC) + 1; + tor_assert(len <= sizeof(buf)); + + { + size_t r = SSL_get_client_random(ssl, buf, client_random_len); + tor_assert(r == client_random_len); + } + + { + size_t r = SSL_get_server_random(ssl, + buf+client_random_len, + server_random_len); + tor_assert(r == server_random_len); + } + + uint8_t *master_key = tor_malloc_zero(master_key_len); + { + size_t r = SSL_SESSION_get_master_key(session, master_key, master_key_len); + tor_assert(r == master_key_len); + } + + uint8_t *nextbuf = buf + client_random_len + server_random_len; + memcpy(nextbuf, TLSSECRET_MAGIC, strlen(TLSSECRET_MAGIC) + 1); + + /* + The value is an HMAC, using the TLS master key as the HMAC key, of + client_random | server_random | TLSSECRET_MAGIC + */ + crypto_hmac_sha256((char*)secrets_out, + (char*)master_key, + master_key_len, + (char*)buf, len); + memwipe(buf, 0, sizeof(buf)); + memwipe(master_key, 0, master_key_len); + tor_free(master_key); + + return 0; +} + +/** Using the RFC5705 key material exporting construction, and the + * provided <b>context</b> (<b>context_len</b> bytes long) and + * <b>label</b> (a NUL-terminated string), compute a 32-byte secret in + * <b>secrets_out</b> that only the parties to this TLS session can + * compute. Return 0 on success; -1 on failure; and -2 on failure + * caused by OpenSSL bug 7712. + */ +MOCK_IMPL(int, +tor_tls_export_key_material,(tor_tls_t *tls, uint8_t *secrets_out, + const uint8_t *context, + size_t context_len, + const char *label)) +{ + tor_assert(tls); + tor_assert(tls->ssl); + + int r = SSL_export_keying_material(tls->ssl, + secrets_out, DIGEST256_LEN, + label, strlen(label), + context, context_len, 1); + + if (r != 1) { + int severity = openssl_bug_7712_is_present ? LOG_WARN : LOG_DEBUG; + tls_log_errors(tls, severity, LD_NET, "exporting keying material"); + } + +#ifdef TLS1_3_VERSION + if (r != 1 && + strlen(label) > 12 && + SSL_version(tls->ssl) >= TLS1_3_VERSION) { + + if (! openssl_bug_7712_is_present) { + /* We might have run into OpenSSL issue 7712, which caused OpenSSL + * 1.1.1a to not handle long labels. Let's test to see if we have. + */ + r = SSL_export_keying_material(tls->ssl, secrets_out, DIGEST256_LEN, + "short", 5, context, context_len, 1); + if (r == 1) { + /* A short label succeeds, but a long label fails. This was openssl + * issue 7712. */ + openssl_bug_7712_is_present = 1; + log_warn(LD_GENERAL, "Detected OpenSSL bug 7712: disabling TLS 1.3 on " + "future connections. A fix is expected to appear in OpenSSL " + "1.1.1b."); + } + } + if (openssl_bug_7712_is_present) + return -2; + else + return -1; + } +#endif + + return (r == 1) ? 0 : -1; +} + +/** Examine the amount of memory used and available for buffers in <b>tls</b>. + * Set *<b>rbuf_capacity</b> to the amount of storage allocated for the read + * buffer and *<b>rbuf_bytes</b> to the amount actually used. + * Set *<b>wbuf_capacity</b> to the amount of storage allocated for the write + * buffer and *<b>wbuf_bytes</b> to the amount actually used. + * + * Return 0 on success, -1 on failure.*/ +int +tor_tls_get_buffer_sizes(tor_tls_t *tls, + size_t *rbuf_capacity, size_t *rbuf_bytes, + size_t *wbuf_capacity, size_t *wbuf_bytes) +{ +#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) + (void)tls; + (void)rbuf_capacity; + (void)rbuf_bytes; + (void)wbuf_capacity; + (void)wbuf_bytes; + + return -1; +#else /* !(OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0)) */ + if (tls->ssl->s3->rbuf.buf) + *rbuf_capacity = tls->ssl->s3->rbuf.len; + else + *rbuf_capacity = 0; + if (tls->ssl->s3->wbuf.buf) + *wbuf_capacity = tls->ssl->s3->wbuf.len; + else + *wbuf_capacity = 0; + *rbuf_bytes = tls->ssl->s3->rbuf.left; + *wbuf_bytes = tls->ssl->s3->wbuf.left; + return 0; +#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) */ +} + +/** Check whether the ECC group requested is supported by the current OpenSSL + * library instance. Return 1 if the group is supported, and 0 if not. + */ +int +evaluate_ecgroup_for_tls(const char *ecgroup) +{ + EC_KEY *ec_key; + int nid; + int ret; + + if (!ecgroup) + nid = NID_tor_default_ecdhe_group; + else if (!strcasecmp(ecgroup, "P256")) + nid = NID_X9_62_prime256v1; + else if (!strcasecmp(ecgroup, "P224")) + nid = NID_secp224r1; + else + return 0; + + ec_key = EC_KEY_new_by_curve_name(nid); + ret = (ec_key != NULL); + EC_KEY_free(ec_key); + + return ret; +} diff --git a/src/lib/tls/tortls_st.h b/src/lib/tls/tortls_st.h new file mode 100644 index 0000000000..3f7ea8ac6a --- /dev/null +++ b/src/lib/tls/tortls_st.h @@ -0,0 +1,75 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_TORTLS_ST_H +#define TOR_TORTLS_ST_H + +#include "lib/net/socket.h" + +#define TOR_TLS_MAGIC 0x71571571 + +typedef enum { + TOR_TLS_ST_HANDSHAKE, TOR_TLS_ST_OPEN, TOR_TLS_ST_GOTCLOSE, + TOR_TLS_ST_SENTCLOSE, TOR_TLS_ST_CLOSED, TOR_TLS_ST_RENEGOTIATE, + TOR_TLS_ST_BUFFEREVENT +} tor_tls_state_t; +#define tor_tls_state_bitfield_t ENUM_BF(tor_tls_state_t) + +struct tor_tls_context_t { + int refcnt; + tor_tls_context_impl_t *ctx; + struct tor_x509_cert_t *my_link_cert; + struct tor_x509_cert_t *my_id_cert; + struct tor_x509_cert_t *my_auth_cert; + crypto_pk_t *link_key; + crypto_pk_t *auth_key; +}; + +/** Holds a SSL object and its associated data. Members are only + * accessed from within tortls.c. + */ +struct tor_tls_t { + uint32_t magic; + tor_tls_context_t *context; /** A link to the context object for this tls. */ + tor_tls_impl_t *ssl; /**< An OpenSSL SSL object or NSS PRFileDesc. */ + tor_socket_t socket; /**< The underlying file descriptor for this TLS + * connection. */ + char *address; /**< An address to log when describing this connection. */ + tor_tls_state_bitfield_t state : 3; /**< The current SSL state, + * depending on which operations + * have completed successfully. */ + unsigned int isServer:1; /**< True iff this is a server-side connection */ + unsigned int wasV2Handshake:1; /**< True iff the original handshake for + * this connection used the updated version + * of the connection protocol (client sends + * different cipher list, server sends only + * one certificate). */ + /** True iff we should call negotiated_callback when we're done reading. */ + unsigned int got_renegotiate:1; +#ifdef ENABLE_OPENSSL + /** Return value from tor_tls_classify_client_ciphers, or 0 if we haven't + * called that function yet. */ + int8_t client_cipher_list_type; + size_t wantwrite_n; /**< 0 normally, >0 if we returned wantwrite last + * time. */ + /** Last values retrieved from BIO_number_read()/write(); see + * tor_tls_get_n_raw_bytes() for usage. + */ + unsigned long last_write_count; + unsigned long last_read_count; + /** If set, a callback to invoke whenever the client tries to renegotiate + * the handshake. */ + void (*negotiated_callback)(tor_tls_t *tls, void *arg); + /** Argument to pass to negotiated_callback. */ + void *callback_arg; +#endif +#ifdef ENABLE_NSS + /** Last values retried from tor_get_prfiledesc_byte_counts(). */ + uint64_t last_write_count; + uint64_t last_read_count; +#endif +}; + +#endif diff --git a/src/lib/tls/x509.c b/src/lib/tls/x509.c new file mode 100644 index 0000000000..67a8b49b9d --- /dev/null +++ b/src/lib/tls/x509.c @@ -0,0 +1,143 @@ +/* Copyright (c) 2003, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file x509_openssl.c + * \brief Wrapper functions to present a consistent interface to + * X.509 functions. + **/ + +#define TOR_X509_PRIVATE +#include "lib/tls/x509.h" +#include "lib/tls/x509_internal.h" +#include "lib/log/util_bug.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" + +/** Choose the start and end times for a certificate */ +void +tor_tls_pick_certificate_lifetime(time_t now, + unsigned int cert_lifetime, + time_t *start_time_out, + time_t *end_time_out) +{ + time_t start_time, end_time; + /* Make sure we're part-way through the certificate lifetime, rather + * than having it start right now. Don't choose quite uniformly, since + * then we might pick a time where we're about to expire. Lastly, be + * sure to start on a day boundary. */ + /* Our certificate lifetime will be cert_lifetime no matter what, but if we + * start cert_lifetime in the past, we'll have 0 real lifetime. instead we + * start up to (cert_lifetime - min_real_lifetime - start_granularity) in + * the past. */ + const time_t min_real_lifetime = 24*3600; + const time_t start_granularity = 24*3600; + time_t earliest_start_time; + /* Don't actually start in the future! */ + if (cert_lifetime <= min_real_lifetime + start_granularity) { + earliest_start_time = now - 1; + } else { + earliest_start_time = now + min_real_lifetime + start_granularity + - cert_lifetime; + } + start_time = crypto_rand_time_range(earliest_start_time, now); + /* Round the start time back to the start of a day. */ + start_time -= start_time % start_granularity; + + end_time = start_time + cert_lifetime; + + *start_time_out = start_time; + *end_time_out = end_time; +} + +/** Return a set of digests for the public key in <b>cert</b>, or NULL if this + * cert's public key is not one we know how to take the digest of. */ +const common_digests_t * +tor_x509_cert_get_id_digests(const tor_x509_cert_t *cert) +{ + if (cert->pkey_digests_set) + return &cert->pkey_digests; + else + return NULL; +} + +/** Return a set of digests for the public key in <b>cert</b>. */ +const common_digests_t * +tor_x509_cert_get_cert_digests(const tor_x509_cert_t *cert) +{ + return &cert->cert_digests; +} + +/** Free all storage held in <b>cert</b> */ +void +tor_x509_cert_free_(tor_x509_cert_t *cert) +{ + if (! cert) + return; + tor_x509_cert_impl_free(cert->cert); +#ifdef ENABLE_OPENSSL + tor_free(cert->encoded); +#endif + memwipe(cert, 0x03, sizeof(*cert)); + /* LCOV_EXCL_BR_START since cert will never be NULL here */ + tor_free(cert); + /* LCOV_EXCL_BR_STOP */ +} + +/** + * Allocate a new tor_x509_cert_t to hold the certificate "x509_cert". + * + * Steals a reference to x509_cert. + */ +MOCK_IMPL(tor_x509_cert_t *, +tor_x509_cert_new,(tor_x509_cert_impl_t *x509_cert)) +{ + tor_x509_cert_t *cert; + + if (!x509_cert) + return NULL; + + cert = tor_malloc_zero(sizeof(tor_x509_cert_t)); + cert->cert = x509_cert; + + if (tor_x509_cert_set_cached_der_encoding(cert) < 0) + goto err; + + { + const uint8_t *encoded=NULL; + size_t encoded_len=0; + tor_x509_cert_get_der(cert, &encoded, &encoded_len); + tor_assert(encoded); + crypto_common_digests(&cert->cert_digests, (char *)encoded, encoded_len); + } + + { + crypto_pk_t *pk = tor_tls_cert_get_key(cert); + if (pk) { + if (crypto_pk_get_common_digests(pk, &cert->pkey_digests) < 0) { + log_warn(LD_CRYPTO, "unable to compute digests of certificate key"); + crypto_pk_free(pk); + goto err; + } + } + cert->pkey_digests_set = 1; + crypto_pk_free(pk); + } + + return cert; + err: + log_err(LD_CRYPTO, "Couldn't wrap encoded X509 certificate."); + tor_x509_cert_free(cert); + return NULL; +} + +/** Return a new copy of <b>cert</b>. */ +tor_x509_cert_t * +tor_x509_cert_dup(const tor_x509_cert_t *cert) +{ + tor_assert(cert); + tor_assert(cert->cert); + return tor_x509_cert_new(tor_x509_cert_impl_dup_(cert->cert)); +} diff --git a/src/lib/tls/x509.h b/src/lib/tls/x509.h new file mode 100644 index 0000000000..5e6660de5c --- /dev/null +++ b/src/lib/tls/x509.h @@ -0,0 +1,75 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_X509_H +#define TOR_X509_H + +/** + * \file x509.h + * \brief Headers for tortls.c + **/ + +#include "lib/crypt_ops/crypto_rsa.h" +#include "lib/testsupport/testsupport.h" + +/* Opaque structure to hold an X509 certificate. */ +typedef struct tor_x509_cert_t tor_x509_cert_t; + +#ifdef ENABLE_NSS +typedef struct CERTCertificateStr tor_x509_cert_impl_t; +#elif defined(ENABLE_OPENSSL) +typedef struct x509_st tor_x509_cert_impl_t; +#endif + +#ifdef TOR_X509_PRIVATE +/** Structure that we use for a single certificate. */ +struct tor_x509_cert_t { + tor_x509_cert_impl_t *cert; +#ifdef ENABLE_OPENSSL + uint8_t *encoded; + size_t encoded_len; +#endif + unsigned pkey_digests_set : 1; + common_digests_t cert_digests; + common_digests_t pkey_digests; +}; +#endif + +void tor_tls_pick_certificate_lifetime(time_t now, + unsigned cert_lifetime, + time_t *start_time_out, + time_t *end_time_out); + +#ifdef TOR_UNIT_TESTS +tor_x509_cert_t *tor_x509_cert_replace_expiration( + const tor_x509_cert_t *inp, + time_t new_expiration_time, + crypto_pk_t *signing_key); +#endif + +tor_x509_cert_t *tor_x509_cert_dup(const tor_x509_cert_t *cert); + +void tor_x509_cert_free_(tor_x509_cert_t *cert); +#define tor_x509_cert_free(c) \ + FREE_AND_NULL(tor_x509_cert_t, tor_x509_cert_free_, (c)) +tor_x509_cert_t *tor_x509_cert_decode(const uint8_t *certificate, + size_t certificate_len); +void tor_x509_cert_get_der(const tor_x509_cert_t *cert, + const uint8_t **encoded_out, size_t *size_out); + +const common_digests_t *tor_x509_cert_get_id_digests( + const tor_x509_cert_t *cert); +const common_digests_t *tor_x509_cert_get_cert_digests( + const tor_x509_cert_t *cert); + +crypto_pk_t *tor_tls_cert_get_key(tor_x509_cert_t *cert); + +int tor_tls_cert_is_valid(int severity, + const tor_x509_cert_t *cert, + const tor_x509_cert_t *signing_cert, + time_t now, + int check_rsa_1024); + +#endif diff --git a/src/lib/tls/x509_internal.h b/src/lib/tls/x509_internal.h new file mode 100644 index 0000000000..bf2bec9689 --- /dev/null +++ b/src/lib/tls/x509_internal.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_X509_INTERNAL_H +#define TOR_X509_INTERNAL_H + +/** + * \file x509.h + * \brief Internal headers for tortls.c + **/ + +#include "lib/crypt_ops/crypto_rsa.h" +#include "lib/testsupport/testsupport.h" + +/** + * How skewed do we allow our clock to be with respect to certificates that + * seem to be expired? (seconds) + */ +#define TOR_X509_PAST_SLOP (2*24*60*60) +/** + * How skewed do we allow our clock to be with respect to certificates that + * seem to come from the future? (seconds) + */ +#define TOR_X509_FUTURE_SLOP (30*24*60*60) + +MOCK_DECL(tor_x509_cert_impl_t *, tor_tls_create_certificate, + (crypto_pk_t *rsa, + crypto_pk_t *rsa_sign, + const char *cname, + const char *cname_sign, + unsigned int cert_lifetime)); +MOCK_DECL(tor_x509_cert_t *, tor_x509_cert_new, + (tor_x509_cert_impl_t *x509_cert)); + +int tor_x509_check_cert_lifetime_internal(int severity, + const tor_x509_cert_impl_t *cert, + time_t now, + int past_tolerance, + int future_tolerance); + +void tor_x509_cert_impl_free_(tor_x509_cert_impl_t *cert); +#define tor_x509_cert_impl_free(cert) \ + FREE_AND_NULL(tor_x509_cert_impl_t, tor_x509_cert_impl_free_, (cert)) +tor_x509_cert_impl_t *tor_x509_cert_impl_dup_(tor_x509_cert_impl_t *cert); +#ifdef ENABLE_OPENSSL +int tor_x509_cert_set_cached_der_encoding(tor_x509_cert_t *cert); +#else +#define tor_x509_cert_set_cached_der_encoding(cert) (0) +#endif + +#endif diff --git a/src/lib/tls/x509_nss.c b/src/lib/tls/x509_nss.c new file mode 100644 index 0000000000..fb4af54c52 --- /dev/null +++ b/src/lib/tls/x509_nss.c @@ -0,0 +1,458 @@ +/* Copyright (c) 2003, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file x509_nss.c + * \brief Wrapper functions to present a consistent interface to + * X.509 functions from NSS. + **/ + +#define TOR_X509_PRIVATE +#include "lib/tls/x509.h" +#include "lib/tls/x509_internal.h" +#include "lib/tls/tortls.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/crypt_ops/crypto_nss_mgt.h" +#include "lib/log/util_bug.h" +#include "lib/encoding/time_fmt.h" +#include "lib/string/printf.h" + +#include <pk11pub.h> +#include <cryptohi.h> +#include <cert.h> +#include <keyhi.h> +#include <time.h> + +/* Units of PRTime per second. + * + * (PRTime is based in microseconds since the Unix + * epoch.) */ +#define PRTIME_PER_SEC (1000*1000) + +static tor_x509_cert_impl_t *tor_x509_cert_decode_internal( + const uint8_t *certificate, int certificate_len); + +static tor_x509_cert_impl_t * +tor_tls_create_certificate_internal(crypto_pk_t *rsa, + crypto_pk_t *rsa_sign, + CERTName *subject_dn, + CERTName *issuer_dn, + time_t start_time, + time_t end_time) +{ + if (! crypto_pk_key_is_private(rsa_sign)) { + return NULL; + } + + const SECKEYPublicKey *subject_key = crypto_pk_get_nss_pubkey(rsa); + const SECKEYPrivateKey *signing_key = crypto_pk_get_nss_privkey(rsa_sign); + SECStatus s; + + CERTSubjectPublicKeyInfo *subject_spki = NULL; + CERTCertificateRequest *request = NULL; + CERTValidity *validity = NULL; + CERTCertificate *cert = NULL; + SECItem der = { .data = NULL, .len = 0 }; + SECItem signed_der = { .data = NULL, .len = 0 }; + + CERTCertificate *result_cert = NULL; + + validity = CERT_CreateValidity(((PRTime)start_time) * PRTIME_PER_SEC, + ((PRTime)end_time) * PRTIME_PER_SEC); + if (BUG(! validity)) { + /* LCOV_EXCL_START */ + crypto_nss_log_errors(LOG_WARN, "creating a validity object"); + goto err; + /* LCOV_EXCL_STOP */ + } + + unsigned long serial_number; + crypto_rand((char*)&serial_number, sizeof(serial_number)); + + subject_spki = SECKEY_CreateSubjectPublicKeyInfo(subject_key); + if (!subject_spki) + goto err; + + /* Make a CSR ... */ + // XXX do we need to set any attributes? + request = CERT_CreateCertificateRequest(subject_dn, + subject_spki, + NULL /* attributes */); + if (!request) + goto err; + + /* Put it into a certificate ... */ + cert = CERT_CreateCertificate(serial_number, + issuer_dn, + validity, + request); + if (!cert) + goto err; + + /* version 3 cert */ + *cert->version.data = 2; /* 2 means version 3. */ + cert->version.len = 1; + + // XXX do we need to set anything else on the cert? + + /* Sign it. */ + KeyType privkey_type = SECKEY_GetPrivateKeyType(signing_key); + SECOidTag oid_tag = SEC_GetSignatureAlgorithmOidTag(privkey_type, + SEC_OID_SHA256); + if (oid_tag == SEC_OID_UNKNOWN) + goto err; + s = SECOID_SetAlgorithmID(cert->arena, &cert->signature, oid_tag, NULL); + if (s != SECSuccess) + goto err; + + void *tmp; + tmp = SEC_ASN1EncodeItem(cert->arena, &der, cert, + SEC_ASN1_GET(CERT_CertificateTemplate)); + if (!tmp) + goto err; + +#if 0 + s = SEC_DerSignDataWithAlgorithmID(cert->arena, + &signed_der, + der.data, der.len, + (SECKEYPrivateKey *)signing_key,//const + &cert->signature); +#else + s = SEC_DerSignData(cert->arena, + &signed_der, + der.data, der.len, + (SECKEYPrivateKey *)signing_key,//const + SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION); +#endif + + if (s != SECSuccess) + goto err; + + /* Re-parse it, to make sure all the certificates we actually use + * appear via being decoded. */ + result_cert = tor_x509_cert_decode_internal(signed_der.data, signed_der.len); + +#if 1 + { + // Can we check the cert we just signed? + tor_assert(result_cert); + SECKEYPublicKey *issuer_pk = (SECKEYPublicKey *) + crypto_pk_get_nss_pubkey(rsa_sign); + SECStatus cert_ok = CERT_VerifySignedDataWithPublicKey( + &result_cert->signatureWrap, issuer_pk, NULL); + tor_assert(cert_ok == SECSuccess); + } +#endif + + err: + if (subject_spki) + SECKEY_DestroySubjectPublicKeyInfo(subject_spki); + if (request) + CERT_DestroyCertificateRequest(request); + if (validity) + CERT_DestroyValidity(validity); + + // unnecessary, since these are allocated in the cert's arena. + //SECITEM_FreeItem(&der, PR_FALSE); + //SECITEM_FreeItem(&signed_der, PR_FALSE); + if (cert) + CERT_DestroyCertificate(cert); + + return result_cert; +} + +MOCK_IMPL(tor_x509_cert_impl_t *, +tor_tls_create_certificate,(crypto_pk_t *rsa, + crypto_pk_t *rsa_sign, + const char *cname, + const char *cname_sign, + unsigned int cert_lifetime)) +{ + tor_assert(rsa); + tor_assert(rsa_sign); + tor_assert(cname); + tor_assert(cname_sign); + + char *cname_rfc_1485 = NULL, *cname_sign_rfc_1485 = NULL; + CERTName *subject_dn = NULL, *issuer_dn = NULL; + time_t start_time; + time_t end_time; + CERTCertificate *result = NULL; + + tor_asprintf(&cname_rfc_1485, "CN=%s", cname); + tor_asprintf(&cname_sign_rfc_1485, "CN=%s", cname_sign); + + subject_dn = CERT_AsciiToName(cname_rfc_1485); + issuer_dn = CERT_AsciiToName(cname_sign_rfc_1485); + if (!subject_dn || !issuer_dn) + goto err; + + tor_tls_pick_certificate_lifetime(time(NULL), cert_lifetime, + &start_time, &end_time); + + result = tor_tls_create_certificate_internal(rsa, + rsa_sign, + subject_dn, + issuer_dn, + start_time, + end_time); + err: + tor_free(cname_rfc_1485); + tor_free(cname_sign_rfc_1485); + if (subject_dn) + CERT_DestroyName(subject_dn); + if (issuer_dn) + CERT_DestroyName(issuer_dn); + + return result; +} + +/** Set *<b>encoded_out</b> and *<b>size_out</b> to <b>cert</b>'s encoded DER + * representation and length, respectively. */ +void +tor_x509_cert_get_der(const tor_x509_cert_t *cert, + const uint8_t **encoded_out, size_t *size_out) +{ + tor_assert(cert); + tor_assert(cert->cert); + tor_assert(encoded_out); + tor_assert(size_out); + + const SECItem *item = &cert->cert->derCert; + *encoded_out = item->data; + *size_out = (size_t)item->len; +} + +void +tor_x509_cert_impl_free_(tor_x509_cert_impl_t *cert) +{ + if (cert) + CERT_DestroyCertificate(cert); +} + +tor_x509_cert_impl_t * +tor_x509_cert_impl_dup_(tor_x509_cert_impl_t *cert) +{ + if (cert) + return CERT_DupCertificate(cert); + else + return NULL; +} + +/** + * As tor_x509_cert_decode, but return the NSS certificate type +*/ +static tor_x509_cert_impl_t * +tor_x509_cert_decode_internal(const uint8_t *certificate, + int certificate_len) +{ + tor_assert(certificate); + if (certificate_len > INT_MAX) + return NULL; + + SECItem der = { .type = siBuffer, + .data = (unsigned char *)certificate, + .len = certificate_len }; + CERTCertDBHandle *certdb = CERT_GetDefaultCertDB(); + tor_assert(certdb); + return CERT_NewTempCertificate(certdb, + &der, + NULL /* nickname */, + PR_FALSE, /* isPerm */ + PR_TRUE /* CopyDER */); +} + +tor_x509_cert_t * +tor_x509_cert_decode(const uint8_t *certificate, + size_t certificate_len) +{ + CERTCertificate *cert = tor_x509_cert_decode_internal(certificate, + (int)certificate_len); + if (! cert) { + crypto_nss_log_errors(LOG_INFO, "decoding certificate"); + return NULL; + } + + tor_x509_cert_t *newcert = tor_x509_cert_new(cert); + + return newcert; +} + +crypto_pk_t * +tor_tls_cert_get_key(tor_x509_cert_t *cert) +{ + tor_assert(cert); + CERTSubjectPublicKeyInfo *spki = &cert->cert->subjectPublicKeyInfo; + SECKEYPublicKey *pub = SECKEY_ExtractPublicKey(spki); // we own this pointer + if (pub == NULL) + return NULL; + + if (SECKEY_GetPublicKeyType(pub) != rsaKey) { + SECKEY_DestroyPublicKey(pub); + return NULL; + } + + return crypto_pk_new_from_nss_pubkey(pub); +} + +int +tor_tls_cert_is_valid(int severity, + const tor_x509_cert_t *cert, + const tor_x509_cert_t *signing_cert, + time_t now, + int check_rsa_1024) +{ + int result = 0; + + tor_assert(cert); + tor_assert(signing_cert); + + SECKEYPublicKey *pk = CERT_ExtractPublicKey(signing_cert->cert); + if (pk == NULL) { + log_fn(severity, LD_CRYPTO, + "Invalid certificate: could not extract issuer key"); + goto fail; + } + + SECStatus s = CERT_VerifySignedDataWithPublicKey(&cert->cert->signatureWrap, + pk, NULL); + if (s != SECSuccess) { + log_fn(severity, LD_CRYPTO, + "Invalid certificate: could not validate signature."); + goto fail; + } + + if (tor_x509_check_cert_lifetime_internal(severity, + cert->cert, + now, + TOR_X509_PAST_SLOP, + TOR_X509_FUTURE_SLOP) < 0) + goto fail; + + if (check_rsa_1024) { + /* We require that this is a 1024-bit RSA key, for legacy reasons .:p */ + if (SECKEY_GetPublicKeyType(pk) != rsaKey || + SECKEY_PublicKeyStrengthInBits(pk) != 1024) { + log_fn(severity, LD_CRYPTO, "Invalid certificate: Key is not RSA1024."); + goto fail; + } + } else { + /* We require that this key is at least minimally strong. */ + unsigned min_bits = (SECKEY_GetPublicKeyType(pk) == ecKey) ? 128: 1024; + if (SECKEY_PublicKeyStrengthInBits(pk) < min_bits) { + log_fn(severity, LD_CRYPTO, "Invalid certificate: Key is too weak."); + goto fail; + } + } + + /* The certificate is valid. */ + result = 1; + + fail: + if (pk) + SECKEY_DestroyPublicKey(pk); + return result; +} + +static void +log_cert_lifetime(int severity, + const char *status, + time_t now, + PRTime notBefore, + PRTime notAfter) +{ + log_fn(severity, LD_GENERAL, + "Certificate %s. Either their clock is set wrong, or your clock " + "is incorrect.", status); + + char nowbuf[ISO_TIME_LEN+1]; + char nbbuf[ISO_TIME_LEN+1]; + char nabuf[ISO_TIME_LEN+1]; + + format_iso_time(nowbuf, now); + format_iso_time(nbbuf, notBefore / PRTIME_PER_SEC); + format_iso_time(nabuf, notAfter / PRTIME_PER_SEC); + + log_fn(severity, LD_GENERAL, + "(The certificate is valid from %s until %s. Your time is %s.)", + nbbuf, nabuf, nowbuf); +} + +int +tor_x509_check_cert_lifetime_internal(int severity, + const tor_x509_cert_impl_t *cert, + time_t now, + int past_tolerance, + int future_tolerance) +{ + tor_assert(cert); + + PRTime notBefore=0, notAfter=0; + int64_t t; + SECStatus r = CERT_GetCertTimes(cert, ¬Before, ¬After); + if (r != SECSuccess) { + log_fn(severity, LD_CRYPTO, + "Couldn't get validity times from certificate"); + return -1; + } + + t = ((int64_t)now) + future_tolerance; + t *= PRTIME_PER_SEC; + if (notBefore > t) { + log_cert_lifetime(severity, "not yet valid", now, + notBefore, notAfter); + return -1; + } + + t = ((int64_t)now) - past_tolerance; + t *= PRTIME_PER_SEC; + if (notAfter < t) { + log_cert_lifetime(severity, "already expired", now, + notBefore, notAfter); + return -1; + } + + return 0; +} + +#ifdef TOR_UNIT_TESTS +tor_x509_cert_t * +tor_x509_cert_replace_expiration(const tor_x509_cert_t *inp, + time_t new_expiration_time, + crypto_pk_t *signing_key) +{ + tor_assert(inp); + tor_assert(signing_key); + + PRTime notBefore=0, notAfter=0; + SECStatus r = CERT_GetCertTimes(inp->cert, ¬Before, ¬After); + if (r != SECSuccess) + return NULL; + + time_t start_time = notBefore / PRTIME_PER_SEC; + if (new_expiration_time < start_time) { + /* This prevents an NSS error. */ + start_time = new_expiration_time - 10; + } + + crypto_pk_t *subject_key = tor_tls_cert_get_key((tor_x509_cert_t *)inp); + if (!subject_key) + return NULL; + + CERTCertificate *newcert; + + newcert = tor_tls_create_certificate_internal(subject_key, + signing_key, + &inp->cert->subject, + &inp->cert->issuer, + start_time, + new_expiration_time); + + crypto_pk_free(subject_key); + + return newcert ? tor_x509_cert_new(newcert) : NULL; +} +#endif diff --git a/src/lib/tls/x509_openssl.c b/src/lib/tls/x509_openssl.c new file mode 100644 index 0000000000..a344279c22 --- /dev/null +++ b/src/lib/tls/x509_openssl.c @@ -0,0 +1,464 @@ +/* Copyright (c) 2003, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file x509_openssl.c + * \brief Wrapper functions to present a consistent interface to + * X.509 functions from OpenSSL. + **/ + +#define TOR_X509_PRIVATE +#include "lib/tls/x509.h" +#include "lib/tls/x509_internal.h" +#include "lib/tls/tortls.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/crypt_ops/compat_openssl.h" + +/* Some versions of OpenSSL declare SSL_get_selected_srtp_profile twice in + * srtp.h. Suppress the GCC warning so we can build with -Wredundant-decl. */ +DISABLE_GCC_WARNING(redundant-decls) + +#include <openssl/opensslv.h> + +#ifdef OPENSSL_NO_EC +#error "We require OpenSSL with ECC support" +#endif + +#include <openssl/err.h> +#include <openssl/asn1.h> +#include <openssl/bio.h> +#include <openssl/bn.h> +#include <openssl/evp.h> +#include <openssl/objects.h> +#include <openssl/rsa.h> +#include <openssl/x509.h> + +ENABLE_GCC_WARNING(redundant-decls) + +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/ctime/di_ops.h" +#include "lib/encoding/time_fmt.h" + +#include <stdlib.h> +#include <string.h> + +#ifdef OPENSSL_1_1_API +#define X509_get_notBefore_const(cert) \ + X509_get0_notBefore(cert) +#define X509_get_notAfter_const(cert) \ + X509_get0_notAfter(cert) +#ifndef X509_get_notBefore +#define X509_get_notBefore(cert) \ + X509_getm_notBefore(cert) +#endif +#ifndef X509_get_notAfter +#define X509_get_notAfter(cert) \ + X509_getm_notAfter(cert) +#endif +#else /* ! OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) */ +#define X509_get_notBefore_const(cert) \ + ((const ASN1_TIME*) X509_get_notBefore((X509 *)cert)) +#define X509_get_notAfter_const(cert) \ + ((const ASN1_TIME*) X509_get_notAfter((X509 *)cert)) +#endif + +/** Return a newly allocated X509 name with commonName <b>cname</b>. */ +static X509_NAME * +tor_x509_name_new(const char *cname) +{ + int nid; + X509_NAME *name; + /* LCOV_EXCL_BR_START : these branches will only fail on OOM errors */ + if (!(name = X509_NAME_new())) + return NULL; + if ((nid = OBJ_txt2nid("commonName")) == NID_undef) goto error; + if (!(X509_NAME_add_entry_by_NID(name, nid, MBSTRING_ASC, + (unsigned char*)cname, -1, -1, 0))) + goto error; + /* LCOV_EXCL_BR_STOP */ + return name; + + /* LCOV_EXCL_START : these lines will only execute on out of memory errors*/ + error: + X509_NAME_free(name); + return NULL; + /* LCOV_EXCL_STOP */ +} + +/** Generate and sign an X509 certificate with the public key <b>rsa</b>, + * signed by the private key <b>rsa_sign</b>. The commonName of the + * certificate will be <b>cname</b>; the commonName of the issuer will be + * <b>cname_sign</b>. The cert will be valid for <b>cert_lifetime</b> + * seconds, starting from some time in the past. + * + * Return a certificate on success, NULL on failure. + */ +MOCK_IMPL(X509 *, +tor_tls_create_certificate,(crypto_pk_t *rsa, + crypto_pk_t *rsa_sign, + const char *cname, + const char *cname_sign, + unsigned int cert_lifetime)) +{ + /* OpenSSL generates self-signed certificates with random 64-bit serial + * numbers, so let's do that too. */ +#define SERIAL_NUMBER_SIZE 8 + + time_t start_time, end_time; + BIGNUM *serial_number = NULL; + unsigned char serial_tmp[SERIAL_NUMBER_SIZE]; + EVP_PKEY *sign_pkey = NULL, *pkey=NULL; + X509 *x509 = NULL; + X509_NAME *name = NULL, *name_issuer=NULL; + + tor_tls_init(); + + time_t now = time(NULL); + + tor_tls_pick_certificate_lifetime(now, cert_lifetime, + &start_time, &end_time); + + tor_assert(rsa); + tor_assert(cname); + tor_assert(rsa_sign); + tor_assert(cname_sign); + if (!(sign_pkey = crypto_pk_get_openssl_evp_pkey_(rsa_sign,1))) + goto error; + if (!(pkey = crypto_pk_get_openssl_evp_pkey_(rsa,0))) + goto error; + if (!(x509 = X509_new())) + goto error; + if (!(X509_set_version(x509, 2))) + goto error; + + { /* our serial number is 8 random bytes. */ + crypto_rand((char *)serial_tmp, sizeof(serial_tmp)); + if (!(serial_number = BN_bin2bn(serial_tmp, sizeof(serial_tmp), NULL))) + goto error; + if (!(BN_to_ASN1_INTEGER(serial_number, X509_get_serialNumber(x509)))) + goto error; + } + + if (!(name = tor_x509_name_new(cname))) + goto error; + if (!(X509_set_subject_name(x509, name))) + goto error; + if (!(name_issuer = tor_x509_name_new(cname_sign))) + goto error; + if (!(X509_set_issuer_name(x509, name_issuer))) + goto error; + + if (!X509_time_adj(X509_get_notBefore(x509),0,&start_time)) + goto error; + if (!X509_time_adj(X509_get_notAfter(x509),0,&end_time)) + goto error; + if (!X509_set_pubkey(x509, pkey)) + goto error; + + if (!X509_sign(x509, sign_pkey, EVP_sha256())) + goto error; + + goto done; + error: + if (x509) { + X509_free(x509); + x509 = NULL; + } + done: + tls_log_errors(NULL, LOG_WARN, LD_NET, "generating certificate"); + if (sign_pkey) + EVP_PKEY_free(sign_pkey); + if (pkey) + EVP_PKEY_free(pkey); + if (serial_number) + BN_clear_free(serial_number); + if (name) + X509_NAME_free(name); + if (name_issuer) + X509_NAME_free(name_issuer); + return x509; + +#undef SERIAL_NUMBER_SIZE +} + +/** Set the 'encoded' and 'encoded_len' fields of "cert" from cert->cert. */ +int +tor_x509_cert_set_cached_der_encoding(tor_x509_cert_t *cert) +{ + unsigned char *buf = NULL; + int length = i2d_X509(cert->cert, &buf); + + if (length <= 0 || buf == NULL) { + return -1; + } + cert->encoded_len = (size_t) length; + cert->encoded = tor_malloc(length); + memcpy(cert->encoded, buf, length); + OPENSSL_free(buf); + return 0; +} + +void +tor_x509_cert_impl_free_(tor_x509_cert_impl_t *cert) +{ + if (cert) + X509_free(cert); +} + +tor_x509_cert_impl_t * +tor_x509_cert_impl_dup_(tor_x509_cert_impl_t *cert) +{ + if (cert) + return X509_dup(cert); + else + return NULL; +} + +/** Set *<b>encoded_out</b> and *<b>size_out</b> to <b>cert</b>'s encoded DER + * representation and length, respectively. */ +void +tor_x509_cert_get_der(const tor_x509_cert_t *cert, + const uint8_t **encoded_out, size_t *size_out) +{ + tor_assert(cert); + tor_assert(encoded_out); + tor_assert(size_out); + *encoded_out = cert->encoded; + *size_out = cert->encoded_len; +} + +/** Read a DER-encoded X509 cert, of length exactly <b>certificate_len</b>, + * from a <b>certificate</b>. Return a newly allocated tor_x509_cert_t on + * success and NULL on failure. */ +tor_x509_cert_t * +tor_x509_cert_decode(const uint8_t *certificate, size_t certificate_len) +{ + X509 *x509; + const unsigned char *cp = (const unsigned char *)certificate; + tor_x509_cert_t *newcert; + tor_assert(certificate); + check_no_tls_errors(); + + if (certificate_len > INT_MAX) + goto err; + + x509 = d2i_X509(NULL, &cp, (int)certificate_len); + + if (!x509) + goto err; /* Couldn't decode */ + if (cp - certificate != (int)certificate_len) { + X509_free(x509); + goto err; /* Didn't use all the bytes */ + } + newcert = tor_x509_cert_new(x509); + if (!newcert) { + goto err; + } + if (newcert->encoded_len != certificate_len || + fast_memneq(newcert->encoded, certificate, certificate_len)) { + /* Cert wasn't in DER */ + tor_x509_cert_free(newcert); + goto err; + } + return newcert; + err: + tls_log_errors(NULL, LOG_INFO, LD_CRYPTO, "decoding a certificate"); + return NULL; +} + +/** + * Return a newly allocated copy of the public key that a certificate + * certifies. Watch out! This returns NULL if the cert's key is not RSA. + */ +crypto_pk_t * +tor_tls_cert_get_key(tor_x509_cert_t *cert) +{ + crypto_pk_t *result = NULL; + EVP_PKEY *pkey = X509_get_pubkey(cert->cert); + RSA *rsa; + if (!pkey) + return NULL; + rsa = EVP_PKEY_get1_RSA(pkey); + if (!rsa) { + EVP_PKEY_free(pkey); + return NULL; + } + result = crypto_new_pk_from_openssl_rsa_(rsa); + EVP_PKEY_free(pkey); + return result; +} + +/** Check whether <b>cert</b> is well-formed, currently live, and correctly + * signed by the public key in <b>signing_cert</b>. If <b>check_rsa_1024</b>, + * make sure that it has an RSA key with 1024 bits; otherwise, just check that + * the key is long enough. Return 1 if the cert is good, and 0 if it's bad or + * we couldn't check it. */ +int +tor_tls_cert_is_valid(int severity, + const tor_x509_cert_t *cert, + const tor_x509_cert_t *signing_cert, + time_t now, + int check_rsa_1024) +{ + check_no_tls_errors(); + EVP_PKEY *cert_key; + int r, key_ok = 0; + + if (!signing_cert || !cert) + goto bad; + + EVP_PKEY *signing_key = X509_get_pubkey(signing_cert->cert); + if (!signing_key) + goto bad; + r = X509_verify(cert->cert, signing_key); + EVP_PKEY_free(signing_key); + if (r <= 0) + goto bad; + + /* okay, the signature checked out right. Now let's check the check the + * lifetime. */ + if (tor_x509_check_cert_lifetime_internal(severity, cert->cert, now, + TOR_X509_PAST_SLOP, + TOR_X509_FUTURE_SLOP) < 0) + goto bad; + + cert_key = X509_get_pubkey(cert->cert); + if (check_rsa_1024 && cert_key) { + RSA *rsa = EVP_PKEY_get1_RSA(cert_key); +#ifdef OPENSSL_1_1_API + if (rsa && RSA_bits(rsa) == 1024) { +#else + if (rsa && BN_num_bits(rsa->n) == 1024) { +#endif + key_ok = 1; + } else { + log_fn(severity, LD_CRYPTO, "Invalid certificate: Key is not RSA1024."); + } + + if (rsa) + RSA_free(rsa); + } else if (cert_key) { + int min_bits = 1024; +#ifdef EVP_PKEY_EC + if (EVP_PKEY_base_id(cert_key) == EVP_PKEY_EC) + min_bits = 128; +#endif + if (EVP_PKEY_bits(cert_key) >= min_bits) + key_ok = 1; + } + EVP_PKEY_free(cert_key); + if (!key_ok) + goto bad; + + /* XXXX compare DNs or anything? */ + + return 1; + bad: + tls_log_errors(NULL, LOG_INFO, LD_CRYPTO, "checking a certificate"); + return 0; +} + +/** Warn that a certificate lifetime extends through a certain range. */ +static void +log_cert_lifetime(int severity, const X509 *cert, const char *problem, + time_t now) +{ + BIO *bio = NULL; + BUF_MEM *buf; + char *s1=NULL, *s2=NULL; + char mytime[33]; + struct tm tm; + size_t n; + + if (problem) + tor_log(severity, LD_GENERAL, + "Certificate %s. Either their clock is set wrong, or your clock " + "is wrong.", + problem); + + if (!(bio = BIO_new(BIO_s_mem()))) { + log_warn(LD_GENERAL, "Couldn't allocate BIO!"); goto end; + } + if (!(ASN1_TIME_print(bio, X509_get_notBefore_const(cert)))) { + tls_log_errors(NULL, LOG_WARN, LD_NET, "printing certificate lifetime"); + goto end; + } + BIO_get_mem_ptr(bio, &buf); + s1 = tor_strndup(buf->data, buf->length); + + (void)BIO_reset(bio); + if (!(ASN1_TIME_print(bio, X509_get_notAfter_const(cert)))) { + tls_log_errors(NULL, LOG_WARN, LD_NET, "printing certificate lifetime"); + goto end; + } + BIO_get_mem_ptr(bio, &buf); + s2 = tor_strndup(buf->data, buf->length); + + n = strftime(mytime, 32, "%b %d %H:%M:%S %Y UTC", tor_gmtime_r(&now, &tm)); + if (n > 0) { + tor_log(severity, LD_GENERAL, + "(certificate lifetime runs from %s through %s. Your time is %s.)", + s1,s2,mytime); + } else { + tor_log(severity, LD_GENERAL, + "(certificate lifetime runs from %s through %s. " + "Couldn't get your time.)", + s1, s2); + } + + end: + /* Not expected to get invoked */ + tls_log_errors(NULL, LOG_WARN, LD_NET, "getting certificate lifetime"); + if (bio) + BIO_free(bio); + tor_free(s1); + tor_free(s2); +} + +/** Helper: check whether <b>cert</b> is expired give or take + * <b>past_tolerance</b> seconds, or not-yet-valid give or take + * <b>future_tolerance</b> seconds. (Relative to the current time + * <b>now</b>.) If it is live, return 0. If it is not live, log a message + * and return -1. */ +int +tor_x509_check_cert_lifetime_internal(int severity, const X509 *cert, + time_t now, + int past_tolerance, int future_tolerance) +{ + time_t t; + + t = now + future_tolerance; + if (X509_cmp_time(X509_get_notBefore_const(cert), &t) > 0) { + log_cert_lifetime(severity, cert, "not yet valid", now); + return -1; + } + t = now - past_tolerance; + if (X509_cmp_time(X509_get_notAfter_const(cert), &t) < 0) { + log_cert_lifetime(severity, cert, "already expired", now); + return -1; + } + + return 0; +} + +#ifdef TOR_UNIT_TESTS +/* Testing only: return a new x509 cert with the same contents as <b>inp</b>, + but with the expiration time <b>new_expiration_time</b>, signed with + <b>signing_key</b>. */ +STATIC tor_x509_cert_t * +tor_x509_cert_replace_expiration(const tor_x509_cert_t *inp, + time_t new_expiration_time, + crypto_pk_t *signing_key) +{ + X509 *newc = X509_dup(inp->cert); + X509_time_adj(X509_get_notAfter(newc), 0, &new_expiration_time); + EVP_PKEY *pk = crypto_pk_get_openssl_evp_pkey_(signing_key, 1); + tor_assert(X509_sign(newc, pk, EVP_sha256())); + EVP_PKEY_free(pk); + return tor_x509_cert_new(newc); +} +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/lib/trace/.may_include b/src/lib/trace/.may_include new file mode 100644 index 0000000000..45cd13676b --- /dev/null +++ b/src/lib/trace/.may_include @@ -0,0 +1,3 @@ +orconfig.h +lib/log/*.h +lib/trace/*.h diff --git a/src/lib/trace/debug.h b/src/lib/trace/debug.h new file mode 100644 index 0000000000..e35616cf50 --- /dev/null +++ b/src/lib/trace/debug.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file debug.h + * \brief Macros for debugging our event-trace support. + **/ + +#ifndef TOR_TRACE_LOG_DEBUG_H +#define TOR_TRACE_LOG_DEBUG_H + +#include "lib/log/log.h" + +/* Stringify pre-processor trick. */ +#define XSTR(d) STR(d) +#define STR(s) #s + +/* Send every event to a debug log level. This is useful to debug new trace + * events without implementing them for a specific event tracing framework. + * Note that the arguments are ignored since at this step we do not know the + * types and amount there is. */ + +/* Example on how to map a tracepoint to log_debug(). */ +#undef tor_trace +#define tor_trace(subsystem, name, args...) \ + log_debug(LD_GENERAL, "Trace event \"" XSTR(name) "\" from " \ + "\"" XSTR(subsystem) "\" hit. " \ + "(line "XSTR(__LINE__) ")") + +#endif /* TOR_TRACE_LOG_DEBUG_H */ diff --git a/src/lib/trace/events.h b/src/lib/trace/events.h new file mode 100644 index 0000000000..1e1e7b9d16 --- /dev/null +++ b/src/lib/trace/events.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file events.h + * \brief Header file for Tor event tracing. + **/ + +#ifndef TOR_TRACE_EVENTS_H +#define TOR_TRACE_EVENTS_H + +/* + * The following defines a generic event tracing function name that has to be + * used to trace events in the code base. + * + * That generic function is then defined by a event tracing framework. For + * instance, the "log debug" framework sends all trace events to log_debug() + * which is defined in src/trace/debug.h which can only be enabled at compile + * time (--enable-event-tracing-debug). + * + * By default, every trace events in the code base are replaced by a NOP. See + * doc/HACKING/Tracing.md for more information on how to use event tracing or + * add events. + */ + +#ifdef TOR_EVENT_TRACING_ENABLED +/* Map every trace event to a per subsystem macro. */ +#define tor_trace(subsystem, name, ...) \ + tor_trace_##subsystem(name, __VA_ARGS__) + +/* Enable event tracing for the debug framework where all trace events are + * mapped to a log_debug(). */ +#ifdef USE_EVENT_TRACING_DEBUG +#include "lib/trace/debug.h" +#endif + +#else /* TOR_EVENT_TRACING_ENABLED */ + +/* Reaching this point, we NOP every event declaration because event tracing + * is not been enabled at compile time. */ +#define tor_trace(subsystem, name, args...) + +#endif /* TOR_EVENT_TRACING_ENABLED */ + +#endif /* TOR_TRACE_EVENTS_H */ diff --git a/src/lib/trace/include.am b/src/lib/trace/include.am new file mode 100644 index 0000000000..6f10c98744 --- /dev/null +++ b/src/lib/trace/include.am @@ -0,0 +1,18 @@ + +noinst_LIBRARIES += \ + src/lib/libtor-trace.a + +TRACEHEADERS = \ + src/lib/trace/trace.h \ + src/lib/trace/events.h + +if USE_EVENT_TRACING_DEBUG +TRACEHEADERS += \ + src/lib/trace/debug.h +endif + +# Library source files. +src_lib_libtor_trace_a_SOURCES = \ + src/lib/trace/trace.c + +noinst_HEADERS+= $(TRACEHEADERS) diff --git a/src/lib/trace/trace.c b/src/lib/trace/trace.c new file mode 100644 index 0000000000..18be63c5a8 --- /dev/null +++ b/src/lib/trace/trace.c @@ -0,0 +1,17 @@ +/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file trace.c + * \brief Common functions for event-tracing implementation + * + * See trace.h and doc/HACKING/Tracing.md for more information. + **/ + +#include "lib/trace/trace.h" + +/** Initialize the tracing library. */ +void +tor_trace_init(void) +{ +} diff --git a/src/lib/trace/trace.h b/src/lib/trace/trace.h new file mode 100644 index 0000000000..606d435568 --- /dev/null +++ b/src/lib/trace/trace.h @@ -0,0 +1,14 @@ +/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file trace.h + * \brief Header for trace.c + **/ + +#ifndef TOR_TRACE_TRACE_H +#define TOR_TRACE_TRACE_H + +void tor_trace_init(void); + +#endif // TOR_TRACE_TRACE_H diff --git a/src/lib/wallclock/.may_include b/src/lib/wallclock/.may_include new file mode 100644 index 0000000000..dc010da063 --- /dev/null +++ b/src/lib/wallclock/.may_include @@ -0,0 +1,6 @@ +orconfig.h +lib/cc/*.h +lib/err/*.h +lib/wallclock/*.h +lib/string/*.h +lib/testsupport/*.h diff --git a/src/lib/wallclock/approx_time.c b/src/lib/wallclock/approx_time.c new file mode 100644 index 0000000000..ee498702d5 --- /dev/null +++ b/src/lib/wallclock/approx_time.c @@ -0,0 +1,43 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file approx_time.c + * \brief Cache the last result of time(), for performance and testing. + **/ + +#include "orconfig.h" +#include "lib/wallclock/approx_time.h" + +/* ===== + * Cached time + * ===== */ + +#ifndef TIME_IS_FAST +/** Cached estimate of the current time. Updated around once per second; + * may be a few seconds off if we are really busy. This is a hack to avoid + * calling time(NULL) (which not everybody has optimized) on critical paths. + */ +static time_t cached_approx_time = 0; + +/** Return a cached estimate of the current time from when + * update_approx_time() was last called. This is a hack to avoid calling + * time(NULL) on critical paths: please do not even think of calling it + * anywhere else. */ +time_t +approx_time(void) +{ + return cached_approx_time; +} + +/** Update the cached estimate of the current time. This function SHOULD be + * called once per second, and MUST be called before the first call to + * get_approx_time. */ +void +update_approx_time(time_t now) +{ + cached_approx_time = now; +} +#endif /* !defined(TIME_IS_FAST) */ diff --git a/src/lib/wallclock/approx_time.h b/src/lib/wallclock/approx_time.h new file mode 100644 index 0000000000..e6b53f2c27 --- /dev/null +++ b/src/lib/wallclock/approx_time.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file approx_time.h + * \brief Header for approx_time.c + **/ + +#ifndef TOR_APPROX_TIME_H +#define TOR_APPROX_TIME_H + +#include <time.h> + +/* Cached time */ +#ifdef TIME_IS_FAST +#define approx_time() time(NULL) +#define update_approx_time(t) STMT_NIL +#else +time_t approx_time(void); +void update_approx_time(time_t now); +#endif /* defined(TIME_IS_FAST) */ + +#endif diff --git a/src/lib/wallclock/include.am b/src/lib/wallclock/include.am new file mode 100644 index 0000000000..1961639bd7 --- /dev/null +++ b/src/lib/wallclock/include.am @@ -0,0 +1,22 @@ + +noinst_LIBRARIES += src/lib/libtor-wallclock.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-wallclock-testing.a +endif + +src_lib_libtor_wallclock_a_SOURCES = \ + src/lib/wallclock/approx_time.c \ + src/lib/wallclock/time_to_tm.c \ + src/lib/wallclock/tor_gettimeofday.c + +src_lib_libtor_wallclock_testing_a_SOURCES = \ + $(src_lib_libtor_wallclock_a_SOURCES) +src_lib_libtor_wallclock_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_wallclock_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +noinst_HEADERS += \ + src/lib/wallclock/approx_time.h \ + src/lib/wallclock/timeval.h \ + src/lib/wallclock/time_to_tm.h \ + src/lib/wallclock/tor_gettimeofday.h diff --git a/src/lib/wallclock/time_to_tm.c b/src/lib/wallclock/time_to_tm.c new file mode 100644 index 0000000000..f7cb21827b --- /dev/null +++ b/src/lib/wallclock/time_to_tm.c @@ -0,0 +1,200 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file time_to_tm.c + * \brief Convert to struct tm, portably. + **/ + +#include "orconfig.h" +#include "lib/cc/torint.h" +#include "lib/cc/compat_compiler.h" +#include "lib/wallclock/time_to_tm.h" +#include "lib/string/printf.h" +#include "lib/err/torerr.h" + +#include <errno.h> +#include <time.h> +#include <string.h> +#include <stdlib.h> + +#if !defined(_WIN32) +/** Defined iff we need to add locks when defining fake versions of reentrant + * versions of time-related functions. */ +#define TIME_FNS_NEED_LOCKS +#endif + +/** Helper: Deal with confused or out-of-bounds values from localtime_r and + * friends. (On some platforms, they can give out-of-bounds values or can + * return NULL.) If <b>islocal</b>, this is a localtime result; otherwise + * it's from gmtime. The function returns <b>r</b>, when given <b>timep</b> + * as its input. If we need to store new results, store them in + * <b>resultbuf</b>. */ +static struct tm * +correct_tm(int islocal, const time_t *timep, struct tm *resultbuf, + struct tm *r, char **err_out) +{ + const char *outcome; + + if (PREDICT_LIKELY(r)) { + /* We can't strftime dates after 9999 CE, and we want to avoid dates + * before 1 CE (avoiding the year 0 issue and negative years). */ + if (r->tm_year > 8099) { + r->tm_year = 8099; + r->tm_mon = 11; + r->tm_mday = 31; + r->tm_yday = 364; + r->tm_wday = 6; + r->tm_hour = 23; + r->tm_min = 59; + r->tm_sec = 59; + } else if (r->tm_year < (1-1900)) { + r->tm_year = (1-1900); + r->tm_mon = 0; + r->tm_mday = 1; + r->tm_yday = 0; + r->tm_wday = 0; + r->tm_hour = 0; + r->tm_min = 0; + r->tm_sec = 0; + } + return r; + } + + /* If we get here, gmtime or localtime returned NULL. It might have done + * this because of overrun or underrun, or it might have done it because of + * some other weird issue. */ + if (timep) { + if (*timep < 0) { + r = resultbuf; + r->tm_year = 70; /* 1970 CE */ + r->tm_mon = 0; + r->tm_mday = 1; + r->tm_yday = 0; + r->tm_wday = 0; + r->tm_hour = 0; + r->tm_min = 0 ; + r->tm_sec = 0; + outcome = "Rounding up to 1970"; + goto done; + } else if (*timep >= INT32_MAX) { + /* Rounding down to INT32_MAX isn't so great, but keep in mind that we + * only do it if gmtime/localtime tells us NULL. */ + r = resultbuf; + r->tm_year = 137; /* 2037 CE */ + r->tm_mon = 11; + r->tm_mday = 31; + r->tm_yday = 364; + r->tm_wday = 6; + r->tm_hour = 23; + r->tm_min = 59; + r->tm_sec = 59; + outcome = "Rounding down to 2037"; + goto done; + } + } + + /* If we get here, then gmtime/localtime failed without getting an extreme + * value for *timep */ + /* LCOV_EXCL_START */ + r = resultbuf; + memset(resultbuf, 0, sizeof(struct tm)); + outcome="can't recover"; + /* LCOV_EXCL_STOP */ + done: + if (err_out) { + tor_asprintf(err_out, "%s(%"PRId64") failed with error %s: %s", + islocal?"localtime":"gmtime", + timep?((int64_t)*timep):0, + strerror(errno), + outcome); + } + return r; +} + +/** @{ */ +/** As localtime_r, but defined for platforms that don't have it: + * + * Convert *<b>timep</b> to a struct tm in local time, and store the value in + * *<b>result</b>. Return the result on success, or NULL on failure. + */ +#ifdef HAVE_LOCALTIME_R +struct tm * +tor_localtime_r_msg(const time_t *timep, struct tm *result, char **err_out) +{ + struct tm *r; + r = localtime_r(timep, result); + return correct_tm(1, timep, result, r, err_out); +} +#elif defined(TIME_FNS_NEED_LOCKS) +struct tm * +tor_localtime_r_msg(const time_t *timep, struct tm *result, char **err_out) +{ + struct tm *r; + static tor_mutex_t *m=NULL; + if (!m) { m=tor_mutex_new(); } + raw_assert(result); + tor_mutex_acquire(m); + r = localtime(timep); + if (r) + memcpy(result, r, sizeof(struct tm)); + tor_mutex_release(m); + return correct_tm(1, timep, result, r, err_out); +} +#else +struct tm * +tor_localtime_r_msg(const time_t *timep, struct tm *result, char **err_out) +{ + struct tm *r; + raw_assert(result); + r = localtime(timep); + if (r) + memcpy(result, r, sizeof(struct tm)); + return correct_tm(1, timep, result, r, err_out); +} +#endif /* defined(HAVE_LOCALTIME_R) || ... */ +/** @} */ + +/** @{ */ +/** As gmtime_r, but defined for platforms that don't have it: + * + * Convert *<b>timep</b> to a struct tm in UTC, and store the value in + * *<b>result</b>. Return the result on success, or NULL on failure. + */ +#ifdef HAVE_GMTIME_R +struct tm * +tor_gmtime_r_msg(const time_t *timep, struct tm *result, char **err_out) +{ + struct tm *r; + r = gmtime_r(timep, result); + return correct_tm(0, timep, result, r, err_out); +} +#elif defined(TIME_FNS_NEED_LOCKS) +struct tm * +tor_gmtime_r_msg(const time_t *timep, struct tm *result, char **err_out) +{ + struct tm *r; + static tor_mutex_t *m=NULL; + if (!m) { m=tor_mutex_new(); } + raw_assert(result); + tor_mutex_acquire(m); + r = gmtime(timep); + if (r) + memcpy(result, r, sizeof(struct tm)); + tor_mutex_release(m); + return correct_tm(0, timep, result, r, err_out); +} +#else +struct tm * +tor_gmtime_r_msg(const time_t *timep, struct tm *result, char **err_out) +{ + struct tm *r; + raw_assert(result); + r = gmtime(timep); + if (r) + memcpy(result, r, sizeof(struct tm)); + return correct_tm(0, timep, result, r, err_out); +} +#endif /* defined(HAVE_GMTIME_R) || ... */ diff --git a/src/lib/wallclock/time_to_tm.h b/src/lib/wallclock/time_to_tm.h new file mode 100644 index 0000000000..abe78c0efe --- /dev/null +++ b/src/lib/wallclock/time_to_tm.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file time_to_tm.h + * \brief Header for time_to_tm.c + **/ + +#ifndef TOR_WALLCLOCK_TIME_TO_TM_H +#define TOR_WALLCLOCK_TIME_TO_TM_H + +#include <sys/types.h> + +struct tm; +struct tm *tor_localtime_r_msg(const time_t *timep, struct tm *result, + char **err_out); +struct tm *tor_gmtime_r_msg(const time_t *timep, struct tm *result, + char **err_out); + +#endif diff --git a/src/lib/wallclock/timeval.h b/src/lib/wallclock/timeval.h new file mode 100644 index 0000000000..4967e939bf --- /dev/null +++ b/src/lib/wallclock/timeval.h @@ -0,0 +1,65 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file timeval.h + * + * \brief Declarations for timeval-related macros that some platforms + * are missing. + **/ + +#ifndef TOR_TIMEVAL_H +#define TOR_TIMEVAL_H + +#include "orconfig.h" +#include "lib/cc/torint.h" + +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#ifndef timeradd +/** Replacement for timeradd on platforms that do not have it: sets tvout to + * the sum of tv1 and tv2. */ +#define timeradd(tv1,tv2,tvout) \ + do { \ + (tvout)->tv_sec = (tv1)->tv_sec + (tv2)->tv_sec; \ + (tvout)->tv_usec = (tv1)->tv_usec + (tv2)->tv_usec; \ + if ((tvout)->tv_usec >= 1000000) { \ + (tvout)->tv_usec -= 1000000; \ + (tvout)->tv_sec++; \ + } \ + } while (0) +#endif /* !defined(timeradd) */ + +#ifndef timersub +/** Replacement for timersub on platforms that do not have it: sets tvout to + * tv1 minus tv2. */ +#define timersub(tv1,tv2,tvout) \ + do { \ + (tvout)->tv_sec = (tv1)->tv_sec - (tv2)->tv_sec; \ + (tvout)->tv_usec = (tv1)->tv_usec - (tv2)->tv_usec; \ + if ((tvout)->tv_usec < 0) { \ + (tvout)->tv_usec += 1000000; \ + (tvout)->tv_sec--; \ + } \ + } while (0) +#endif /* !defined(timersub) */ + +#ifndef timercmp +/** Replacement for timercmp on platforms that do not have it: returns true + * iff the relational operator "op" makes the expression tv1 op tv2 true. + * + * Note that while this definition should work for all boolean operators, some + * platforms' native timercmp definitions do not support >=, <=, or ==. So + * don't use those. + */ +#define timercmp(tv1,tv2,op) \ + (((tv1)->tv_sec == (tv2)->tv_sec) ? \ + ((tv1)->tv_usec op (tv2)->tv_usec) : \ + ((tv1)->tv_sec op (tv2)->tv_sec)) +#endif /* !defined(timercmp) */ + +#endif diff --git a/src/lib/wallclock/tor_gettimeofday.c b/src/lib/wallclock/tor_gettimeofday.c new file mode 100644 index 0000000000..63538f3b81 --- /dev/null +++ b/src/lib/wallclock/tor_gettimeofday.c @@ -0,0 +1,82 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tor_gettimeofday.c + * \brief Implementat gettimeofday() for windows, and other platforms without + * it. + **/ + +#include "orconfig.h" +#include "lib/err/torerr.h" +#include "lib/wallclock/tor_gettimeofday.h" +#include "lib/cc/torint.h" + +#include <stddef.h> +#include <stdlib.h> + +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#ifdef _WIN32 +#include <windows.h> +#endif + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#ifndef HAVE_GETTIMEOFDAY +#ifdef HAVE_FTIME +#include <sys/timeb.h> +#endif +#endif + +/** Set *timeval to the current time of day. On error, log and terminate. + * (Same as gettimeofday(timeval,NULL), but never returns -1.) + */ +MOCK_IMPL(void, +tor_gettimeofday, (struct timeval *timeval)) +{ +#ifdef _WIN32 + /* Epoch bias copied from perl: number of units between windows epoch and + * Unix epoch. */ +#define EPOCH_BIAS UINT64_C(116444736000000000) +#define UNITS_PER_SEC UINT64_C(10000000) +#define USEC_PER_SEC UINT64_C(1000000) +#define UNITS_PER_USEC UINT64_C(10) + union { + uint64_t ft_64; + FILETIME ft_ft; + } ft; + /* number of 100-nsec units since Jan 1, 1601 */ + GetSystemTimeAsFileTime(&ft.ft_ft); + if (ft.ft_64 < EPOCH_BIAS) { + /* LCOV_EXCL_START */ + raw_assert_unreached_msg("System time is before 1970; failing."); + /* LCOV_EXCL_STOP */ + } + ft.ft_64 -= EPOCH_BIAS; + timeval->tv_sec = (unsigned) (ft.ft_64 / UNITS_PER_SEC); + timeval->tv_usec = (unsigned) ((ft.ft_64 / UNITS_PER_USEC) % USEC_PER_SEC); +#elif defined(HAVE_GETTIMEOFDAY) + if (gettimeofday(timeval, NULL)) { + /* LCOV_EXCL_START */ + /* If gettimeofday dies, we have either given a bad timezone (we didn't), + or segfaulted.*/ + raw_assert_unreached_msg("gettimeofday failed"); + /* LCOV_EXCL_STOP */ + } +#elif defined(HAVE_FTIME) + struct timeb tb; + ftime(&tb); + timeval->tv_sec = tb.time; + timeval->tv_usec = tb.millitm * 1000; +#else +#error "No way to get time." +#endif /* defined(_WIN32) || ... */ + return; +} diff --git a/src/lib/wallclock/tor_gettimeofday.h b/src/lib/wallclock/tor_gettimeofday.h new file mode 100644 index 0000000000..c7fff9747a --- /dev/null +++ b/src/lib/wallclock/tor_gettimeofday.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tor_gettimeofday.h + * \brief Header for tor_gettimeofday.c + **/ + +#ifndef TOR_GETTIMEOFDAY_H +#define TOR_GETTIMEOFDAY_H + +#include "lib/testsupport/testsupport.h" + +struct timeval; + +MOCK_DECL(void, tor_gettimeofday, (struct timeval *timeval)); + +#endif |