/* Test that single onion poisoning works. */ static void test_single_onion_poisoning(void *arg) { or_options_t opt; mock_options = &opt; reset_options(mock_options, &mock_get_options_calls); MOCK(get_options, mock_get_options); int ret = -1; intptr_t create_dir_mask = (intptr_t)arg; /* Get directories with a random suffix so we can repeat the tests */ mock_options->DataDirectory = tor_strdup(get_fname_rnd("test_data_dir")); rend_service_t *service_1 = tor_malloc_zero(sizeof(rend_service_t)); char *dir1 = tor_strdup(get_fname_rnd("test_hs_dir1")); rend_service_t *service_2 = tor_malloc_zero(sizeof(rend_service_t)); char *dir2 = tor_strdup(get_fname_rnd("test_hs_dir2")); smartlist_t *services = smartlist_new(); char *poison_path = NULL; char *err_msg = NULL; mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; /* Create the data directory, and, if the correct bit in arg is set, * create a directory for that service. * The data directory is required for the lockfile, which is used when * loading keys. */ ret = check_private_dir(mock_options->DataDirectory, CPD_CREATE, NULL); tt_int_op(ret, OP_EQ, 0); if (create_dir_mask & CREATE_HS_DIR1) { ret = check_private_dir(dir1, CPD_CREATE, NULL); tt_int_op(ret, OP_EQ, 0); } if (create_dir_mask & CREATE_HS_DIR2) { ret = check_private_dir(dir2, CPD_CREATE, NULL); tt_int_op(ret, OP_EQ, 0); } service_1->directory = dir1; service_2->directory = dir2; /* The services own the directory pointers now */ dir1 = dir2 = NULL; /* Add port to service 1 */ service_1->ports = smartlist_new(); service_2->ports = smartlist_new(); rend_service_port_config_t *port1 = rend_service_parse_port_config("80", " ", &err_msg); tt_assert(port1); tt_ptr_op(err_msg, OP_EQ, NULL); smartlist_add(service_1->ports, port1); rend_service_port_config_t *port2 = rend_service_parse_port_config("90", " ", &err_msg); /* Add port to service 2 */ tt_assert(port2); tt_ptr_op(err_msg, OP_EQ, NULL); smartlist_add(service_2->ports, port2); /* No services, a service to verify, no problem! */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Either way, no problem. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Add the first service */ ret = hs_check_service_private_dir(mock_options->User, service_1->directory, service_1->dir_group_readable, 1); tt_int_op(ret, OP_EQ, 0); smartlist_add(services, service_1); /* But don't add the second service yet. */ /* Service directories, but no previous keys, no problem! */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Either way, no problem. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Poison! Poison! Poison! * This can only be done in HiddenServiceSingleHopMode. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); /* Poisoning twice is a no-op. */ ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); /* Poisoned service directories, but no previous keys, no problem! */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Either way, no problem. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Now add some keys, and we'll have a problem. */ ret = rend_service_load_all_keys(services); tt_int_op(ret, OP_EQ, 0); /* Poisoned service directories with previous keys are not allowed. */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* But they are allowed if we're in non-anonymous mode. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Re-poisoning directories with existing keys is a no-op, because * directories with existing keys are ignored. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); /* And it keeps the poison. */ ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Now add the second service: it has no key and no poison file */ ret = hs_check_service_private_dir(mock_options->User, service_2->directory, service_2->dir_group_readable, 1); tt_int_op(ret, OP_EQ, 0); smartlist_add(services, service_2); /* A new service, and an existing poisoned service. Not ok. */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* But ok to add in non-anonymous mode. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Now remove the poisoning from the first service, and we have the opposite * problem. */ poison_path = rend_service_sos_poison_path(service_1); tt_assert(poison_path); ret = unlink(poison_path); tt_int_op(ret, OP_EQ, 0); /* Unpoisoned service directories with previous keys are ok, as are empty * directories. */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* But the existing unpoisoned key is not ok in non-anonymous mode, even if * there is an empty service. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Poisoning directories with existing keys is a no-op, because directories * with existing keys are ignored. But the new directory should poison. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_poison_new_single_onion_dir(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* And the old directory remains unpoisoned. */ ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* And the new directory should be ignored, because it has no key. */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Re-poisoning directories without existing keys is a no-op. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_poison_new_single_onion_dir(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* And the old directory remains unpoisoned. */ ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); done: /* The test harness deletes the directories at exit */ tor_free(poison_path); tor_free(dir1); tor_free(dir2); smartlist_free(services); rend_service_free(service_1); rend_service_free(service_2); UNMOCK(get_options); tor_free(mock_options->DataDirectory); tor_free(err_msg); }
/* Configure a service using the given line and options. This function will * call the corresponding configuration function for a specific service * version and validate the service against the other ones. On success, add * the service to the given list and return 0. On error, nothing is added to * the list and a negative value is returned. */ static int config_service(const config_line_t *line, const or_options_t *options, smartlist_t *service_list) { int ret; hs_service_t *service = NULL; tor_assert(line); tor_assert(options); tor_assert(service_list); /* We have a new hidden service. */ service = hs_service_new(options); /* We'll configure that service as a generic one and then pass it to a * specific function according to the configured version number. */ if (config_generic_service(line, options, service) < 0) { goto err; } tor_assert(service->config.version <= HS_VERSION_MAX); /* Check permission on service directory that was just parsed. And this must * be done regardless of the service version. Do not ask for the directory * to be created, this is done when the keys are loaded because we could be * in validation mode right now. */ if (hs_check_service_private_dir(options->User, service->config.directory_path, service->config.dir_group_readable, 0) < 0) { goto err; } /* We'll try to learn the service version here by loading the key(s) if * present and we did not set HiddenServiceVersion. Depending on the key * format, we can figure out the service version. */ if (!service->config.hs_version_explicitly_set) { service->config.version = config_learn_service_version(service); } /* We make sure that this set of options for a service are valid that is for * instance an option only for v2 is not used for v3. */ if (config_has_invalid_options(line->next, service)) { goto err; } /* Different functions are in charge of specific options for a version. We * start just after the service directory line so once we hit another * directory line, the function knows that it has to stop parsing. */ switch (service->config.version) { case HS_VERSION_TWO: ret = rend_config_service(line->next, options, &service->config); break; case HS_VERSION_THREE: ret = config_service_v3(line->next, &service->config); break; default: /* We do validate before if we support the parsed version. */ tor_assert_nonfatal_unreached(); goto err; } if (ret < 0) { goto err; } /* We'll check if this service can be kept depending on the others * configured previously. */ if (service_is_duplicate_in_list(service_list, service)) { goto err; } /* Passes, add it to the given list. */ smartlist_add(service_list, service); return 0; err: hs_service_free(service); return -1; }