/* * ConnMan firewall unit tests * * Copyright (C) 2019 Jolla Ltd. All rights reserved. * Contact: jussi.laakkonen@jolla.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "src/connman.h" enum configtype { TEST_CONFIG_PASS = 0x0001, TEST_CONFIG_INIT_FAIL = 0x0002, TEST_CONFIG_FIND_MATCH_FAIL = 0x0004, TEST_CONFIG_FIND_TARGET_FAIL = 0x0008, TEST_CONFIG_PARSE_PROTOCOL_FAIL = 0x0010, TEST_CONFIG_MFCALL_FAIL = 0x0020, TEST_CONFIG_TFCALL_FAIL = 0x0040, TEST_CONFIG_MPCALL_FAIL = 0x0080, TEST_CONFIG_TPCALL_FAIL = 0x0100, TEST_CONFIG_INSMOD_FAIL = 0x0200, TEST_CONFIG_COMPATIBLE_REV_FAIL = 0x0400, TEST_CONFIG_OPTIONS_XFRM_FAIL = 0x0800, TEST_CONFIG_MERGE_OPTIONS_FAIL = 0x1000, }; enum configtype test_config_type = TEST_CONFIG_PASS; static void set_test_config(enum configtype type) { test_config_type = type; } /* Start of dummies */ /* xtables dummies */ /* From /usr/include/linux/netfilter_ipv4/ip_tables.h */ #define IPT_BASE_CTL 64 #define IPT_SO_SET_REPLACE (IPT_BASE_CTL) #define IPT_SO_SET_ADD_COUNTERS (IPT_BASE_CTL + 1) #define IPT_SO_GET_INFO (IPT_BASE_CTL) #define IPT_SO_GET_ENTRIES (IPT_BASE_CTL + 1) /* From /usr/include/linux/netfilter_ipv6/ip6_tables.h */ #define IP6T_BASE_CTL 64 #define IP6T_SO_SET_REPLACE (IP6T_BASE_CTL) #define IP6T_SO_SET_ADD_COUNTERS (IP6T_BASE_CTL + 1) #define IP6T_SO_GET_INFO (IP6T_BASE_CTL) #define IP6T_SO_GET_ENTRIES (IP6T_BASE_CTL + 1) int xt_match_parse(int c, char **argv, int invert, unsigned int *flags, const void *entry, struct xt_entry_match **match) { return 0; } int xt_target_parse(int c, char **argv, int invert, unsigned int *flags, const void *entry, struct xt_entry_target **targetinfo) { return 0; } static void xt_x6_parse(struct xt_option_call *opt) { return; } static void xt_x6_fcheck(struct xt_fcheck_call *call) { return; } static struct xtables_match xt_match = { .version = "1", .next = NULL, .name = "tcp", .real_name = "tcp", .revision = 1, .ext_flags = 0, .family = AF_INET, .size = XT_ALIGN(sizeof(struct xtables_match)), .userspacesize = XT_ALIGN(sizeof(struct xtables_match)), .parse = xt_match_parse, .extra_opts = NULL, .x6_parse = xt_x6_parse, .x6_fcheck = xt_x6_fcheck, .x6_options = NULL, .udata_size = XT_ALIGN(sizeof(struct xtables_match)), .udata = NULL, .option_offset = 32, .m = NULL, .mflags = 0, .loaded = 1, }; static struct xtables_target xt_target = { .version = "1", .next = NULL, .name = "ACCEPT", .real_name = "ACCEPT", .revision = 1, .ext_flags = 0, .family = AF_INET, .size = XT_ALIGN(sizeof(struct xtables_match)), .userspacesize = XT_ALIGN(sizeof(struct xtables_match)), .parse = xt_target_parse, .extra_opts = NULL, .x6_parse = xt_x6_parse, .x6_fcheck = xt_x6_fcheck, .x6_options = NULL, .udata_size = XT_ALIGN(sizeof(struct xtables_match)), .udata = NULL, .option_offset = 32, .t = NULL, .tflags = 0, .used = 0, .loaded = 1, }; struct xtables_globals *xt_params = NULL; struct xtables_match *xtables_matches = NULL; struct xtables_target *xtables_targets = NULL; static void call_error(const char *src) { g_assert(xt_params); DBG("%s", src); xt_params->exit_err(PARAMETER_PROBLEM, "longjmp() %s", src); } int xtables_init_all(struct xtables_globals *xtp, uint8_t nfproto) { DBG("%d", nfproto); if (test_config_type & TEST_CONFIG_INIT_FAIL) call_error("xtables_init_all"); xt_params = xtp; return 0; } struct xtables_match *xtables_find_match(const char *name, enum xtables_tryload tryload, struct xtables_rule_match **matches) { DBG("name %s type %d", name, tryload); if (test_config_type & TEST_CONFIG_FIND_MATCH_FAIL) call_error("xtables_find_match"); *matches = g_try_new0(struct xtables_rule_match, 1); (*matches)->next = NULL; (*matches)->match = &xt_match; (*matches)->completed = 0; return &xt_match; } struct xtables_target *xtables_find_target(const char *name, enum xtables_tryload tryload) { DBG("name %s type %d", name, tryload); if (test_config_type & TEST_CONFIG_FIND_TARGET_FAIL) call_error("xtables_find_target"); return &xt_target; } uint16_t xtables_parse_protocol(const char *s) { DBG("protocol %s", s); if (test_config_type & TEST_CONFIG_PARSE_PROTOCOL_FAIL) call_error("xtables_parse_protocol"); if (!g_strcmp0(s, "tcp")) return 6; return 0; } void xtables_option_mfcall(struct xtables_match *m) { DBG(""); if (test_config_type & TEST_CONFIG_MFCALL_FAIL) call_error("xtables_option_mfcall"); m = &xt_match; return; } void xtables_option_tfcall(struct xtables_target *t) { DBG(""); if (test_config_type & TEST_CONFIG_TFCALL_FAIL) call_error("xtables_option_tfcall"); t = &xt_target; return; } void xtables_option_mpcall(unsigned int c, char **argv, bool invert, struct xtables_match *m, void *fw) { DBG(""); if (test_config_type & TEST_CONFIG_MPCALL_FAIL) call_error("xtables_option_mpcall"); m = &xt_match; return; } void xtables_option_tpcall(unsigned int c, char **argv, bool invert, struct xtables_target *t, void *fw) { DBG(""); if (test_config_type & TEST_CONFIG_TPCALL_FAIL) call_error("xtables_option_tpcall"); t = &xt_target; return; } int xtables_insmod(const char *modname, const char *modprobe, bool quiet) { DBG("mod %s modprobe %s quiet %s", modname, modprobe, quiet ? "true" : "false"); if (test_config_type & TEST_CONFIG_INSMOD_FAIL) call_error("xtables_insmod"); return 0; } int xtables_compatible_revision(const char *name, uint8_t revision, int opt) { DBG("name %s rev %d opt %d", name, revision, opt); if (test_config_type & TEST_CONFIG_COMPATIBLE_REV_FAIL) call_error("xtables_compatible_revision"); return 1; } struct option *xtables_options_xfrm(struct option *opt1, struct option *opt2, const struct xt_option_entry *entry, unsigned int *dummy) { if (test_config_type & TEST_CONFIG_OPTIONS_XFRM_FAIL) call_error("xtables_options_xfrm"); return opt1; } struct option *xtables_merge_options(struct option *orig_opts, struct option *oldopts, const struct option *newopts, unsigned int *option_offset) { if (test_config_type & TEST_CONFIG_MERGE_OPTIONS_FAIL) call_error("xtables_merge_options"); return orig_opts; } /* End of xtables dummies */ /* socket dummies */ int global_sockfd = 1000; int socket(int domain, int type, int protocol) { DBG("domain %d type %d protocol %d", domain, type, protocol); return global_sockfd; } int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) { struct ipt_getinfo *info = NULL; struct ipt_get_entries *entries = NULL; struct ip6t_getinfo *info6 = NULL; struct ip6t_get_entries *entries6 = NULL; DBG(""); g_assert_cmpint(global_sockfd, ==, sockfd); switch (level) { case IPPROTO_IP: DBG("IPPROTO_IP"); switch (optname) { case IPT_SO_GET_ENTRIES: DBG("IPT_SO_GET_ENTRIES"); optval = entries; break; case IPT_SO_GET_INFO: DBG("IPT_SO_GET_INFO"); optval = info; break; default: DBG("optname %d", optname); return -1; } break; case IPPROTO_IPV6: DBG("IPPROTO_IPV6"); switch (optname) { case IP6T_SO_GET_ENTRIES: DBG("IP6T_SO_GET_ENTRIES"); optval = entries6; break; case IP6T_SO_GET_INFO: DBG("IP6T_SO_GET_INFO"); optval = info6; break; default: DBG("optname %d", optname); return -1; } break; default: return -1; } *optlen = 0; return 0; } int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) { DBG(""); g_assert_cmpint(global_sockfd, ==, sockfd); switch (level) { case IPPROTO_IP: DBG("IPPROTO_IP"); switch (optname) { case IPT_SO_SET_REPLACE: DBG("IPT_SO_SET_REPLACE"); return 0; case IPT_SO_SET_ADD_COUNTERS: DBG("IPT_SO_SET_ADD_COUNTERS"); return 0; default: DBG("optname %d", optname); return -1; } break; case IPPROTO_IPV6: DBG("IPPROTO_IPV6"); switch (optname) { case IP6T_SO_SET_REPLACE: DBG("IP6T_SO_SET_REPLACE"); return 0; case IP6T_SO_SET_ADD_COUNTERS: DBG("IP6T_SO_SET_ADD_COUNTERS"); return 0; default: DBG("optname %d", optname); return -1; } break; default: return -1; } } /* End of socket dummies */ /* End of dummies */ static void iptables_test_basic0() { set_test_config(TEST_CONFIG_PASS); __connman_iptables_init(); g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT")); g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT", "-p tcp -m tcp --dport 42 -j ACCEPT"), ==, 0); __connman_iptables_cleanup(); } /* * These ok0...ok6 tests test the error handling. The setjmp() position is set * properly for the functions that will trigger it and as a result, depending on * iptables.c, there will be an error or no error at all. Each of these should * return gracefully without calling exit(). */ static void iptables_test_jmp_ok0() { set_test_config(TEST_CONFIG_FIND_MATCH_FAIL); __connman_iptables_init(); g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT")); g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT", "-p tcp -m tcp -j ACCEPT"), ==, -EINVAL); __connman_iptables_cleanup(); } static void iptables_test_jmp_ok1() { set_test_config(TEST_CONFIG_FIND_TARGET_FAIL); __connman_iptables_init(); g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT")); g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT", "-p tcp -m tcp -j ACCEPT"), ==, -EINVAL); __connman_iptables_cleanup(); } static void iptables_test_jmp_ok2() { set_test_config(TEST_CONFIG_PARSE_PROTOCOL_FAIL); __connman_iptables_init(); g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT")); g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT", "-p tcp -m tcp --dport 42 -j ACCEPT"), ==, -EINVAL); __connman_iptables_cleanup(); } static void iptables_test_jmp_ok3() { set_test_config(TEST_CONFIG_TFCALL_FAIL); __connman_iptables_init(); g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT")); g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT", "-p tcp -m tcp --dport 42 -j ACCEPT"), ==, -EINVAL); __connman_iptables_cleanup(); } static void iptables_test_jmp_ok4() { set_test_config(TEST_CONFIG_MFCALL_FAIL); __connman_iptables_init(); g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT")); g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT", "-p tcp -m tcp --dport 42 -j ACCEPT"), ==, -EINVAL); __connman_iptables_cleanup(); } static void iptables_test_jmp_ok5() { set_test_config(TEST_CONFIG_TPCALL_FAIL); __connman_iptables_init(); g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT")); g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT", "-p tcp -m tcp --dport 42 -j ACCEPT " "--comment test"), ==, -EINVAL); __connman_iptables_cleanup(); } static void iptables_test_jmp_ok6() { set_test_config(TEST_CONFIG_MPCALL_FAIL); __connman_iptables_init(); g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT")); g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT", "-p tcp -m tcp --dport 42 -j ACCEPT"), ==, -EINVAL); __connman_iptables_cleanup(); } /* * These exit0...exit2 tests invoke longjmp() via xtables exit_err() without * having env saved with setjmp(). All of these will result calling exit(), thus * forking is required. */ static void iptables_test_jmp_exit0() { pid_t cpid = 0; int cstatus = 0; /* * Should work as normal but fork() is needed as exit() is called * when longjmp() is not allowed. At xtables_init_all() exit_err() is * not normally called. */ set_test_config(TEST_CONFIG_INIT_FAIL); /* Child, run iptables test */ if (fork() == 0) { __connman_iptables_init(); /* * Address family must be different from previous use because * otherwise xtables_init_all() is not called. */ g_assert(!__connman_iptables_new_chain(AF_INET6, "filter", "INPUT")); __connman_iptables_cleanup(); exit(0); } else { cpid = wait(&cstatus); /* Wait for child */ } DBG("parent %d child %d exit %d", getpid(), cpid, WEXITSTATUS(cstatus)); g_assert(WIFEXITED(cstatus)); g_assert_cmpint(WEXITSTATUS(cstatus), ==, PARAMETER_PROBLEM); } static void iptables_test_jmp_exit1() { pid_t cpid = 0; int cstatus = 0; /* * Should work as normal but fork() is needed as exit() is called * when longjmp() is not allowed. At xtables_insmod() exit_err() is not * normally called. */ set_test_config(TEST_CONFIG_INSMOD_FAIL); if (fork() == 0) { __connman_iptables_init(); g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT")); __connman_iptables_cleanup(); exit(0); } else { cpid = wait(&cstatus); } DBG("parent %d child %d exit %d", getpid(), cpid, WEXITSTATUS(cstatus)); g_assert(WIFEXITED(cstatus)); g_assert_cmpint(WEXITSTATUS(cstatus), ==, PARAMETER_PROBLEM); } static void iptables_test_jmp_exit2() { pid_t cpid = 0; int cstatus = 0; set_test_config(TEST_CONFIG_OPTIONS_XFRM_FAIL| TEST_CONFIG_MERGE_OPTIONS_FAIL| TEST_CONFIG_COMPATIBLE_REV_FAIL); if (fork() == 0) { __connman_iptables_init(); g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT")); g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT", "-p tcp -m tcp --dport 42 " "-j ACCEPT --comment test"), ==, 0); __connman_iptables_cleanup(); exit(0); } else { cpid = wait(&cstatus); } DBG("parent %d child %d exit %d", getpid(), cpid, WEXITSTATUS(cstatus)); g_assert(WIFEXITED(cstatus)); g_assert_cmpint(WEXITSTATUS(cstatus), ==, PARAMETER_PROBLEM); } static gchar *option_debug = NULL; static bool parse_debug(const char *key, const char *value, gpointer user_data, GError **error) { if (value) option_debug = g_strdup(value); else option_debug = g_strdup("*"); return true; } static GOptionEntry options[] = { { "debug", 'd', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, parse_debug, "Specify debug options to enable", "DEBUG" }, { NULL }, }; int main (int argc, char *argv[]) { GOptionContext *context; GError *error = NULL; g_test_init(&argc, &argv, NULL); context = g_option_context_new(NULL); g_option_context_add_main_entries(context, options, NULL); if (!g_option_context_parse(context, &argc, &argv, &error)) { if (error) { g_printerr("%s\n", error->message); g_error_free(error); } else g_printerr("An unknown error occurred\n"); return 1; } g_option_context_free(context); __connman_log_init(argv[0], option_debug, false, false, "Unit Tests Connection Manager", VERSION); g_test_add_func("/iptables/test_basic0", iptables_test_basic0); g_test_add_func("/iptables/test_jmp_ok0", iptables_test_jmp_ok0); g_test_add_func("/iptables/test_jmp_ok1", iptables_test_jmp_ok1); g_test_add_func("/iptables/test_jmp_ok2", iptables_test_jmp_ok2); g_test_add_func("/iptables/test_jmp_ok3", iptables_test_jmp_ok3); g_test_add_func("/iptables/test_jmp_ok4", iptables_test_jmp_ok4); g_test_add_func("/iptables/test_jmp_ok5", iptables_test_jmp_ok5); g_test_add_func("/iptables/test_jmp_ok6", iptables_test_jmp_ok6); g_test_add_func("/iptables/test_jmp_exit0", iptables_test_jmp_exit0); g_test_add_func("/iptables/test_jmp_exit1", iptables_test_jmp_exit1); g_test_add_func("/iptables/test_jmp_exit2", iptables_test_jmp_exit2); return g_test_run(); } /* * Local Variables: * mode: C * c-basic-offset: 8 * indent-tabs-mode: t * End: */