LCOV - code coverage report
Current view: top level - feature/dirparse - microdesc_parse.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 140 143 97.9 %
Date: 2021-11-24 03:28:48 Functions: 6 6 100.0 %

          Line data    Source code
       1             : /* Copyright (c) 2001 Matej Pfajfar.
       2             :  * Copyright (c) 2001-2004, Roger Dingledine.
       3             :  * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
       4             :  * Copyright (c) 2007-2021, The Tor Project, Inc. */
       5             : /* See LICENSE for licensing information */
       6             : 
       7             : /**
       8             :  * \file microdesc_parse.c
       9             :  * \brief Code to parse and validate microdescriptors.
      10             :  **/
      11             : 
      12             : #include "core/or/or.h"
      13             : 
      14             : #include "app/config/config.h"
      15             : #include "core/or/policies.h"
      16             : #include "feature/dirparse/microdesc_parse.h"
      17             : #include "feature/dirparse/parsecommon.h"
      18             : #include "feature/dirparse/routerparse.h"
      19             : #include "feature/nodelist/microdesc.h"
      20             : #include "feature/nodelist/nickname.h"
      21             : #include "feature/nodelist/nodefamily.h"
      22             : #include "feature/relay/router.h"
      23             : #include "lib/crypt_ops/crypto_curve25519.h"
      24             : #include "lib/crypt_ops/crypto_ed25519.h"
      25             : #include "lib/crypt_ops/crypto_format.h"
      26             : #include "lib/memarea/memarea.h"
      27             : 
      28             : #include "feature/nodelist/microdesc_st.h"
      29             : 
      30             : /** List of tokens recognized in microdescriptors */
      31             : // clang-format off
      32             : static token_rule_t microdesc_token_table[] = {
      33             :   T1_START("onion-key",        K_ONION_KEY,        NO_ARGS,     NEED_KEY_1024),
      34             :   T1("ntor-onion-key",         K_ONION_KEY_NTOR,   GE(1),       NO_OBJ ),
      35             :   T0N("id",                    K_ID,               GE(2),       NO_OBJ ),
      36             :   T0N("a",                     K_A,                GE(1),       NO_OBJ ),
      37             :   T01("family",                K_FAMILY,           CONCAT_ARGS, NO_OBJ ),
      38             :   T01("p",                     K_P,                CONCAT_ARGS, NO_OBJ ),
      39             :   T01("p6",                    K_P6,               CONCAT_ARGS, NO_OBJ ),
      40             :   A01("@last-listed",          A_LAST_LISTED,      CONCAT_ARGS, NO_OBJ ),
      41             :   END_OF_TABLE
      42             : };
      43             : // clang-format on
      44             : 
      45             : /** Assuming that s starts with a microdesc, return the start of the
      46             :  * *NEXT* one.  Return NULL on "not found." */
      47             : static const char *
      48          57 : find_start_of_next_microdesc(const char *s, const char *eos)
      49             : {
      50          57 :   int started_with_annotations;
      51          57 :   s = eat_whitespace_eos(s, eos);
      52          57 :   if (!s)
      53             :     return NULL;
      54             : 
      55             : #define CHECK_LENGTH() STMT_BEGIN \
      56             :     if (eos - s < 32)             \
      57             :       return NULL;                \
      58             :   STMT_END
      59             : 
      60             : #define NEXT_LINE() STMT_BEGIN            \
      61             :     s = memchr(s, '\n', eos-s);           \
      62             :     if (!s || eos - s <= 1)               \
      63             :       return NULL;                        \
      64             :     s++;                                  \
      65             :   STMT_END
      66             : 
      67          57 :   CHECK_LENGTH();
      68             : 
      69          57 :   started_with_annotations = (*s == '@');
      70             : 
      71          57 :   if (started_with_annotations) {
      72             :     /* Start by advancing to the first non-annotation line. */
      73          18 :     while (*s == '@')
      74           9 :       NEXT_LINE();
      75             :   }
      76          57 :   CHECK_LENGTH();
      77             : 
      78             :   /* Now we should be pointed at an onion-key line.  If we are, then skip
      79             :    * it. */
      80          55 :   if (!strcmpstart(s, "onion-key"))
      81          55 :     NEXT_LINE();
      82             : 
      83             :   /* Okay, now we're pointed at the first line of the microdescriptor which is
      84             :      not an annotation or onion-key.  The next line that _is_ an annotation or
      85             :      onion-key is the start of the next microdescriptor. */
      86         468 :   while (eos - s > 32) {
      87         461 :     if (*s == '@' || !strcmpstart(s, "onion-key"))
      88          36 :       return s;
      89         425 :     NEXT_LINE();
      90             :   }
      91             :   return NULL;
      92             : 
      93             : #undef CHECK_LENGTH
      94             : #undef NEXT_LINE
      95             : }
      96             : 
      97             : static inline int
      98          70 : policy_is_reject_star_or_null(struct short_policy_t *policy)
      99             : {
     100          70 :   return !policy || short_policy_is_reject_star(policy);
     101             : }
     102             : 
     103             : /**
     104             :  * Return a human-readable description of a given saved_location_t.
     105             :  * Never returns NULL.
     106             :  **/
     107             : static const char *
     108           6 : saved_location_to_string(saved_location_t where)
     109             : {
     110           6 :   const char *location;
     111           6 :   switch (where) {
     112             :     case SAVED_NOWHERE:
     113             :       location = "download or generated string";
     114             :       break;
     115           1 :     case SAVED_IN_CACHE:
     116           1 :       location = "cache";
     117           1 :       break;
     118           1 :     case SAVED_IN_JOURNAL:
     119           1 :       location = "journal";
     120           1 :       break;
     121           0 :     default:
     122           0 :       location = "unknown location";
     123           0 :       break;
     124             :   }
     125           6 :   return location;
     126             : }
     127             : 
     128             : /**
     129             :  * Given a microdescriptor stored in <b>where</b> which starts at <b>s</b>,
     130             :  * which ends at <b>start_of_next_microdescriptor</b>, and which is located
     131             :  * within a larger document beginning at <b>start</b>: Fill in the body,
     132             :  * bodylen, bodylen, saved_location, off, and digest fields of <b>md</b> as
     133             :  * appropriate.
     134             :  *
     135             :  * The body field will be an alias within <b>s</b> if <b>saved_location</b>
     136             :  * is SAVED_IN_CACHE, and will be copied into body and nul-terminated
     137             :  * otherwise.
     138             :  **/
     139             : static int
     140          57 : microdesc_extract_body(microdesc_t *md,
     141             :                        const char *start,
     142             :                        const char *s, const char *start_of_next_microdesc,
     143             :                        saved_location_t where)
     144             : {
     145          57 :   const bool copy_body = (where != SAVED_IN_CACHE);
     146             : 
     147          57 :   const char *cp = tor_memstr(s, start_of_next_microdesc-s, "onion-key");
     148             : 
     149          57 :   const bool no_onion_key = (cp == NULL);
     150          57 :   if (no_onion_key) {
     151           2 :     cp = s; /* So that we have *some* junk to put in the body */
     152             :   }
     153             : 
     154          57 :   md->bodylen = start_of_next_microdesc - cp;
     155          57 :   md->saved_location = where;
     156          57 :   if (copy_body)
     157          53 :     md->body = tor_memdup_nulterm(cp, md->bodylen);
     158             :   else
     159           4 :     md->body = (char*)cp;
     160          57 :   md->off = cp - start;
     161             : 
     162          57 :   crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256);
     163             : 
     164          57 :   return no_onion_key ? -1 : 0;
     165             : }
     166             : 
     167             : /**
     168             :  * Parse a microdescriptor which begins at <b>s</b> and ends at
     169             :  * <b>start_of_next_microdesc</b>.  Store its fields into <b>md</b>.  Use
     170             :  * <b>where</b> for generating log information.  If <b>allow_annotations</b>
     171             :  * is true, then one or more annotations may precede the microdescriptor body
     172             :  * proper.  Use <b>area</b> for memory management, clearing it when done.
     173             :  *
     174             :  * On success, return 0; otherwise return -1.
     175             :  **/
     176             : static int
     177          55 : microdesc_parse_fields(microdesc_t *md,
     178             :                        memarea_t *area,
     179             :                        const char *s, const char *start_of_next_microdesc,
     180             :                        int allow_annotations,
     181             :                        saved_location_t where)
     182             : {
     183          55 :   smartlist_t *tokens = smartlist_new();
     184          55 :   int rv = -1;
     185          55 :   int flags = allow_annotations ? TS_ANNOTATIONS_OK : 0;
     186          55 :   directory_token_t *tok;
     187             : 
     188          55 :   if (tokenize_string(area, s, start_of_next_microdesc, tokens,
     189             :                       microdesc_token_table, flags)) {
     190           6 :     log_warn(LD_DIR, "Unparseable microdescriptor found in %s",
     191             :              saved_location_to_string(where));
     192           6 :     goto err;
     193             :   }
     194             : 
     195          49 :   if ((tok = find_opt_by_keyword(tokens, A_LAST_LISTED))) {
     196           4 :     if (parse_iso_time(tok->args[0], &md->last_listed)) {
     197           1 :       log_warn(LD_DIR, "Bad last-listed time in microdescriptor");
     198           1 :       goto err;
     199             :     }
     200             :   }
     201             : 
     202          48 :   tok = find_by_keyword(tokens, K_ONION_KEY);
     203          48 :   if (!crypto_pk_public_exponent_ok(tok->key)) {
     204           2 :     log_warn(LD_DIR,
     205             :              "Relay's onion key had invalid exponent.");
     206           2 :     goto err;
     207             :   }
     208          46 :   md->onion_pkey = tor_memdup(tok->object_body, tok->object_size);
     209          46 :   md->onion_pkey_len = tok->object_size;
     210          46 :   crypto_pk_free(tok->key);
     211             : 
     212          46 :   if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) {
     213          46 :     curve25519_public_key_t k;
     214          46 :     tor_assert(tok->n_args >= 1);
     215          46 :     if (curve25519_public_from_base64(&k, tok->args[0]) < 0) {
     216           2 :       log_warn(LD_DIR, "Bogus ntor-onion-key in microdesc");
     217           2 :       goto err;
     218             :     }
     219          44 :     md->onion_curve25519_pkey =
     220          44 :       tor_memdup(&k, sizeof(curve25519_public_key_t));
     221             :   }
     222             : 
     223          44 :   smartlist_t *id_lines = find_all_by_keyword(tokens, K_ID);
     224          44 :   if (id_lines) {
     225          70 :     SMARTLIST_FOREACH_BEGIN(id_lines, directory_token_t *, t) {
     226          38 :       tor_assert(t->n_args >= 2);
     227          38 :       if (!strcmp(t->args[0], "ed25519")) {
     228           7 :         if (md->ed25519_identity_pkey) {
     229           1 :           log_warn(LD_DIR, "Extra ed25519 key in microdesc");
     230           1 :           smartlist_free(id_lines);
     231           2 :           goto err;
     232             :         }
     233           6 :         ed25519_public_key_t k;
     234           6 :         if (ed25519_public_from_base64(&k, t->args[1])<0) {
     235           1 :           log_warn(LD_DIR, "Bogus ed25519 key in microdesc");
     236           1 :           smartlist_free(id_lines);
     237           1 :           goto err;
     238             :         }
     239           5 :         md->ed25519_identity_pkey = tor_memdup(&k, sizeof(k));
     240             :       }
     241          36 :     } SMARTLIST_FOREACH_END(t);
     242          32 :     smartlist_free(id_lines);
     243             :   }
     244             : 
     245             :   {
     246          42 :     smartlist_t *a_lines = find_all_by_keyword(tokens, K_A);
     247          42 :     if (a_lines) {
     248           8 :       find_single_ipv6_orport(a_lines, &md->ipv6_addr, &md->ipv6_orport);
     249           8 :       smartlist_free(a_lines);
     250             :     }
     251             :   }
     252             : 
     253          42 :   if ((tok = find_opt_by_keyword(tokens, K_FAMILY))) {
     254           7 :     md->family = nodefamily_parse(tok->args[0],
     255             :                                   NULL,
     256             :                                   NF_WARN_MALFORMED);
     257             :   }
     258             : 
     259          42 :   if ((tok = find_opt_by_keyword(tokens, K_P))) {
     260          18 :     md->exit_policy = parse_short_policy(tok->args[0]);
     261             :   }
     262          42 :   if ((tok = find_opt_by_keyword(tokens, K_P6))) {
     263           4 :     md->ipv6_exit_policy = parse_short_policy(tok->args[0]);
     264             :   }
     265             : 
     266          70 :   if (policy_is_reject_star_or_null(md->exit_policy) &&
     267          28 :       policy_is_reject_star_or_null(md->ipv6_exit_policy)) {
     268          26 :     md->policy_is_reject_star = 1;
     269             :   }
     270             : 
     271             :   rv = 0;
     272          55 :  err:
     273             : 
     274         267 :   SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
     275          55 :   memarea_clear(area);
     276          55 :   smartlist_free(tokens);
     277             : 
     278          55 :   return rv;
     279             : }
     280             : 
     281             : /** Parse as many microdescriptors as are found from the string starting at
     282             :  * <b>s</b> and ending at <b>eos</b>.  If allow_annotations is set, read any
     283             :  * annotations we recognize and ignore ones we don't.
     284             :  *
     285             :  * If <b>saved_location</b> isn't SAVED_IN_CACHE, make a local copy of each
     286             :  * descriptor in the body field of each microdesc_t.
     287             :  *
     288             :  * Return all newly parsed microdescriptors in a newly allocated
     289             :  * smartlist_t. If <b>invalid_disgests_out</b> is provided, add a SHA256
     290             :  * microdesc digest to it for every microdesc that we found to be badly
     291             :  * formed. (This may cause duplicates) */
     292             : smartlist_t *
     293          22 : microdescs_parse_from_string(const char *s, const char *eos,
     294             :                              int allow_annotations,
     295             :                              saved_location_t where,
     296             :                              smartlist_t *invalid_digests_out)
     297             : {
     298          22 :   smartlist_t *result;
     299          22 :   microdesc_t *md = NULL;
     300          22 :   memarea_t *area;
     301          22 :   const char *start = s;
     302          22 :   const char *start_of_next_microdesc;
     303             : 
     304          22 :   if (!eos)
     305          15 :     eos = s + strlen(s);
     306             : 
     307          22 :   s = eat_whitespace_eos(s, eos);
     308          22 :   area = memarea_new();
     309          22 :   result = smartlist_new();
     310             : 
     311          79 :   while (s < eos) {
     312          57 :    bool okay = false;
     313             : 
     314          57 :     start_of_next_microdesc = find_start_of_next_microdesc(s, eos);
     315          57 :     if (!start_of_next_microdesc)
     316          21 :       start_of_next_microdesc = eos;
     317             : 
     318          57 :     md = tor_malloc_zero(sizeof(microdesc_t));
     319          57 :     uint8_t md_digest[DIGEST256_LEN];
     320             :     {
     321         114 :       const bool body_not_found =
     322          57 :         microdesc_extract_body(md, start, s,
     323             :                                start_of_next_microdesc,
     324             :                                where) < 0;
     325             : 
     326          57 :       memcpy(md_digest, md->digest, DIGEST256_LEN);
     327          57 :       if (body_not_found) {
     328           2 :         log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Malformed or truncated descriptor");
     329           2 :         goto next;
     330             :       }
     331             :     }
     332             : 
     333          55 :     if (microdesc_parse_fields(md, area, s, start_of_next_microdesc,
     334             :                                allow_annotations, where) == 0) {
     335          42 :       smartlist_add(result, md);
     336          42 :       md = NULL; // prevent free
     337          42 :       okay = true;
     338             :     }
     339             : 
     340          13 :   next:
     341          57 :     if (! okay && invalid_digests_out) {
     342          13 :       smartlist_add(invalid_digests_out,
     343             :                     tor_memdup(md_digest, DIGEST256_LEN));
     344             :     }
     345          57 :     microdesc_free(md);
     346          57 :     md = NULL;
     347          57 :     s = start_of_next_microdesc;
     348             :   }
     349             : 
     350          22 :   memarea_drop_all(area);
     351             : 
     352          22 :   return result;
     353             : }

Generated by: LCOV version 1.14