diff options
Diffstat (limited to 'src/irmd/reg')
| -rw-r--r-- | src/irmd/reg/flow.c | 7 | ||||
| -rw-r--r-- | src/irmd/reg/flow.h | 7 | ||||
| -rw-r--r-- | src/irmd/reg/reg.c | 389 | ||||
| -rw-r--r-- | src/irmd/reg/reg.h | 43 | ||||
| -rw-r--r-- | src/irmd/reg/tests/reg_test.c | 165 |
5 files changed, 576 insertions, 35 deletions
diff --git a/src/irmd/reg/flow.c b/src/irmd/reg/flow.c index ccb2562d..8be2dfc7 100644 --- a/src/irmd/reg/flow.c +++ b/src/irmd/reg/flow.c @@ -70,10 +70,12 @@ static void destroy_rbuffs(struct reg_flow * flow) { if (flow->n_rb != NULL) ssm_rbuff_destroy(flow->n_rb); + flow->n_rb = NULL; if (flow->n_1_rb != NULL) ssm_rbuff_destroy(flow->n_1_rb); + flow->n_1_rb = NULL; } @@ -81,7 +83,10 @@ void reg_flow_destroy(struct reg_flow * flow) { assert(flow != NULL); - crypt_secure_clear(flow->rk.pending_seed, SYMMKEYSZ); + if (flow->rk.pending_seed != NULL) + crypt_secure_free(flow->rk.pending_seed, SYMMKEYSZ); + + freebuf(flow->rk.peer_crt); switch(flow->info.state) { case FLOW_ACCEPT_PENDING: diff --git a/src/irmd/reg/flow.h b/src/irmd/reg/flow.h index 15fc7b8f..166bed61 100644 --- a/src/irmd/reg/flow.h +++ b/src/irmd/reg/flow.h @@ -55,9 +55,14 @@ struct reg_flow { uint8_t epoch; /* last epoch installed by app */ bool initiator; /* OAP initiator (role 0) */ bool in_flight; /* a re-key is in progress */ - uint8_t pending_seed[SYMMKEYSZ]; + bool req_queued; /* a peer REQ is in the inbox */ + bool resp_queued; /* a peer RESP is in the inbox */ + uint8_t * pending_seed; /* secure heap; NULL until set */ uint8_t pending_epoch; + bool pending_initiator; /* pending seed: oap_cli side */ bool has_pending; /* new seed awaits app pull */ + uint8_t pulled; /* direct: per-app pull mask */ + buffer_t peer_crt; /* peer cert DER, cached at HS */ } rk; struct ssm_rbuff * n_rb; diff --git a/src/irmd/reg/reg.c b/src/irmd/reg/reg.c index 70baf64e..ebf3959d 100644 --- a/src/irmd/reg/reg.c +++ b/src/irmd/reg/reg.c @@ -872,6 +872,7 @@ int reg_list_ipcps(ipcp_list_msg_t *** ipcps) fail: while (i-- > 0) ipcp_list_msg__free_unpacked((*ipcps)[i], NULL); + free(*ipcps); fail_malloc: pthread_mutex_unlock(®.mtx); @@ -1033,6 +1034,20 @@ int reg_get_name_for_flow_id(char * buf, return f == NULL ? -ENOENT : 0; } +void reg_set_name_for_flow_id(const char * name, + int flow_id) +{ + struct reg_flow * f; + + pthread_mutex_lock(®.mtx); + + f = __reg_get_flow(flow_id); + if (f != NULL) + strcpy(f->name, name); + + pthread_mutex_unlock(®.mtx); +} + int reg_list_names(name_info_msg_t *** names) { struct list_head * p; @@ -1077,6 +1092,7 @@ int reg_list_names(name_info_msg_t *** names) fail: while (i-- > 0) name_info_msg__free_unpacked((*names)[i], NULL); + free(*names); fail_malloc: pthread_mutex_unlock(®.mtx); @@ -2103,10 +2119,21 @@ bool reg_flow_is_direct(int flow_id) return ret; } -void reg_flow_set_rekey(int flow_id, - bool initiator) +void reg_flow_set_rekey(int flow_id, + bool initiator, + buffer_t peer_crt) { struct reg_flow * flow; + uint8_t * crt = NULL; + + /* Copy the cert outside the lock; publish it with rk.encrypted. */ + if (peer_crt.len > 0) { + crt = malloc(peer_crt.len); + if (crt != NULL) + memcpy(crt, peer_crt.data, peer_crt.len); + else + log_warn("Failed to cache peer cert for re-key."); + } pthread_mutex_lock(®.mtx); @@ -2115,9 +2142,47 @@ void reg_flow_set_rekey(int flow_id, flow->rk.encrypted = true; flow->rk.initiator = initiator; flow->rk.epoch = 0; + if (crt != NULL) { + freebuf(flow->rk.peer_crt); + flow->rk.peer_crt.data = crt; + flow->rk.peer_crt.len = peer_crt.len; + crt = NULL; + } } pthread_mutex_unlock(®.mtx); + + free(crt); +} + +int reg_flow_get_peer_crt(int flow_id, + buffer_t * crt) +{ + struct reg_flow * flow; + int ret = -ENOENT; + + assert(crt != NULL); + + clrbuf(*crt); + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(flow_id); + if (flow != NULL && flow->rk.peer_crt.len > 0) { + crt->data = malloc(flow->rk.peer_crt.len); + if (crt->data == NULL) { + ret = -ENOMEM; + } else { + memcpy(crt->data, flow->rk.peer_crt.data, + flow->rk.peer_crt.len); + crt->len = flow->rk.peer_crt.len; + ret = 0; + } + } + + pthread_mutex_unlock(®.mtx); + + return ret; } int reg_flow_get_epoch(int flow_id) @@ -2184,10 +2249,14 @@ int reg_flow_snapshot_rekey_due(struct rekey_info * snap, f = list_entry(p, struct reg_flow, next); - if (f->info.state != FLOW_ALLOCATED || f->direct) + if (f->info.state != FLOW_ALLOCATED) + continue; + + if (!f->rk.encrypted) continue; - if (!f->rk.encrypted || !f->rk.initiator) + /* Direct flows have no IPCP initiator; either side drives. */ + if (!f->direct && !f->rk.initiator) continue; if (f->rk.in_flight || f->rk.has_pending) @@ -2199,6 +2268,7 @@ int reg_flow_snapshot_rekey_due(struct rekey_info * snap, snap[n].n_pid = f->info.n_pid; snap[n].n_1_pid = f->info.n_1_pid; snap[n].epoch = f->rk.epoch; + snap[n].direct = f->direct; strcpy(snap[n].name, f->name); ++n; } @@ -2221,9 +2291,48 @@ void reg_flow_clear_in_flight(int flow_id) pthread_mutex_unlock(®.mtx); } +/* Test-and-set the in-flight latch; refuse if a re-key is already active. */ +bool reg_flow_rekey_begin(int flow_id) +{ + struct reg_flow * flow; + bool ret = false; + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(flow_id); + if (flow != NULL && flow->rk.encrypted) { + if (!flow->rk.in_flight && !flow->rk.has_pending) { + flow->rk.in_flight = true; + ret = true; + } + } + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +/* Initiator yields the responder role while driving its own exchange. */ +bool reg_flow_rekey_should_yield(int flow_id) +{ + struct reg_flow * flow; + bool ret = false; + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(flow_id); + if (flow != NULL) + ret = flow->rk.initiator && flow->rk.in_flight; + + pthread_mutex_unlock(®.mtx); + + return ret; +} + int reg_flow_store_pending(int flow_id, const uint8_t * seed, - uint8_t epoch) + uint8_t epoch, + bool initiator) { struct reg_flow * flow; int ret = -ENOENT; @@ -2232,14 +2341,24 @@ int reg_flow_store_pending(int flow_id, flow = __reg_get_flow(flow_id); if (flow != NULL) { - memcpy(flow->rk.pending_seed, seed, SYMMKEYSZ); - flow->rk.pending_epoch = epoch; - flow->rk.has_pending = true; - flow->rk.in_flight = false; - /* Doorbell raised only after the seed is parked. */ - if (flow->n_rb != NULL) - ssm_rbuff_set_bits(flow->n_rb, RB_REKEY); - ret = 0; + /* Exchange done: release the latch regardless of parking. */ + flow->rk.in_flight = false; + + if (flow->rk.pending_seed == NULL) + flow->rk.pending_seed = crypt_secure_malloc(SYMMKEYSZ); + + if (flow->rk.pending_seed != NULL) { + memcpy(flow->rk.pending_seed, seed, SYMMKEYSZ); + flow->rk.pending_epoch = epoch; + flow->rk.pending_initiator = initiator; + flow->rk.has_pending = true; + /* Doorbell raised only after the seed is parked. */ + if (flow->n_rb != NULL) + ssm_rbuff_set_bits(flow->n_rb, RB_REKEY); + ret = 0; + } else { + ret = -ENOMEM; + } } pthread_mutex_unlock(®.mtx); @@ -2247,45 +2366,259 @@ int reg_flow_store_pending(int flow_id, return ret; } -bool reg_flow_take_pending(int flow_id, - uint8_t * seed, - uint8_t * epoch) +/* Direct re-key: which of the two local apps has pulled the seed. */ +#define RK_N_PID 0x1 /* acceptor (n_pid) pulled the seed */ +#define RK_N_1_PID 0x2 /* allocator (n_1_pid) pulled the seed */ +#define RK_PID_MASK (RK_N_PID | RK_N_1_PID) + +/* + * Park a single re-key seed for a direct flow and ring BOTH apps' + * doorbells. The seed is the one shared secret; each app pulls it once + * (reg_flow_take_pending), so it is held until both have taken it. + */ +int reg_flow_store_pending_direct(int flow_id, + const uint8_t * seed, + uint8_t epoch) { struct reg_flow * flow; - bool ret = false; + int ret = -ENOENT; pthread_mutex_lock(®.mtx); flow = __reg_get_flow(flow_id); - if (flow != NULL && flow->rk.has_pending) { - memcpy(seed, flow->rk.pending_seed, SYMMKEYSZ); - *epoch = flow->rk.pending_epoch; - flow->rk.epoch = flow->rk.pending_epoch; /* app installed it */ - flow->rk.has_pending = false; - crypt_secure_clear(flow->rk.pending_seed, SYMMKEYSZ); + if (flow == NULL) + goto out; + + /* Exchange done: release the latch regardless of parking. */ + flow->rk.in_flight = false; + + if (flow->rk.pending_seed == NULL) + flow->rk.pending_seed = crypt_secure_malloc(SYMMKEYSZ); + + if (flow->rk.pending_seed == NULL) { + ret = -ENOMEM; + goto out; + } + + memcpy(flow->rk.pending_seed, seed, SYMMKEYSZ); + flow->rk.pending_epoch = epoch; + flow->rk.has_pending = true; + flow->rk.pulled = 0; + + /* A departed peer never pulls; treat its side as already done. */ + if (flow->info.n_pid <= 0) + flow->rk.pulled |= RK_N_PID; + + if (flow->info.n_1_pid <= 0) + flow->rk.pulled |= RK_N_1_PID; + + if (flow->n_rb != NULL && !(flow->rk.pulled & RK_N_PID)) + ssm_rbuff_set_bits(flow->n_rb, RB_REKEY); + + if (flow->n_1_rb != NULL && !(flow->rk.pulled & RK_N_1_PID)) + ssm_rbuff_set_bits(flow->n_1_rb, RB_REKEY); + + ret = 0; + out: + pthread_mutex_unlock(®.mtx); + + return ret; +} + +/* A caller may act on a flow if it is privileged or owns the flow. */ +static bool uid_may_access(uid_t caller, + uid_t owner) +{ + return is_ouroboros_member_uid(caller) || caller == owner; +} + +/* + * Caller holds reg.mtx. The direct seed is shared by both apps, so the + * per-app initiator role is resolved from the verified caller pid (the + * allocator is n_1_pid), and the seed is held until both have pulled. + */ +static void __take_pending_direct(struct reg_flow * flow, + pid_t cpid, + uint8_t * seed, + uint8_t * epoch, + bool * initiator) +{ + bool allocator; + + allocator = cpid == flow->info.n_1_pid; + + memcpy(seed, flow->rk.pending_seed, SYMMKEYSZ); + *epoch = flow->rk.pending_epoch; + *initiator = allocator; + flow->rk.epoch = flow->rk.pending_epoch; + + if (allocator) { + flow->rk.pulled |= RK_N_1_PID; + if (flow->n_1_rb != NULL) + ssm_rbuff_clr_bits(flow->n_1_rb, RB_REKEY); + } else { + flow->rk.pulled |= RK_N_PID; if (flow->n_rb != NULL) ssm_rbuff_clr_bits(flow->n_rb, RB_REKEY); - ret = true; } + if ((flow->rk.pulled & RK_PID_MASK) != RK_PID_MASK) + return; + + flow->rk.has_pending = false; + flow->rk.pulled = 0; + crypt_secure_clear(flow->rk.pending_seed, SYMMKEYSZ); +} + +int reg_flow_take_pending(int flow_id, + uid_t uid, + pid_t cpid, + uint8_t * seed, + uint8_t * epoch, + bool * initiator) +{ + struct reg_flow * flow; + int ret = -ENOENT; + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(flow_id); + if (flow == NULL || !flow->rk.has_pending) + goto out; + + if (!uid_may_access(uid, flow->info.uid)) { + ret = -EPERM; + goto out; + } + + if (flow->direct) { + __take_pending_direct(flow, cpid, seed, epoch, initiator); + ret = 0; + goto out; + } + + memcpy(seed, flow->rk.pending_seed, SYMMKEYSZ); + *epoch = flow->rk.pending_epoch; + *initiator = flow->rk.pending_initiator; + flow->rk.epoch = flow->rk.pending_epoch; + flow->rk.has_pending = false; + crypt_secure_clear(flow->rk.pending_seed, SYMMKEYSZ); + if (flow->n_rb != NULL) + ssm_rbuff_clr_bits(flow->n_rb, RB_REKEY); + + ret = 0; + out: pthread_mutex_unlock(®.mtx); return ret; } +/* + * Admit a peer-driven re-key arrival before a worker event is allocated: + * the flow must exist, carry a cipher, and the update must come from its + * own lower IPCP. Coalesces to one queued REQ and one queued RESP per flow + * so a flooding peer cannot grow the inbox without bound. + */ +bool reg_flow_rekey_arr_admit(int flow_id, + pid_t n_1_pid, + bool is_req) +{ + struct reg_flow * flow; + bool admit = false; + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(flow_id); + if (flow != NULL && flow->rk.encrypted + && flow->info.n_1_pid == n_1_pid) { + if (is_req && !flow->rk.req_queued) { + flow->rk.req_queued = true; + admit = true; + } else if (!is_req && flow->rk.in_flight + && !flow->rk.resp_queued) { + flow->rk.resp_queued = true; + admit = true; + } + } + + pthread_mutex_unlock(®.mtx); + + return admit; +} + +void reg_flow_rekey_arr_done(int flow_id, + bool is_req) +{ + struct reg_flow * flow; + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(flow_id); + if (flow != NULL) { + if (is_req) + flow->rk.req_queued = false; + else + flow->rk.resp_queued = false; + } + + pthread_mutex_unlock(®.mtx); +} + +bool reg_flow_owned_by(int flow_id, + uid_t uid) +{ + struct reg_flow * flow; + bool ret = false; + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(flow_id); + if (flow != NULL) + ret = uid_may_access(uid, flow->info.uid); + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +/* Caller holds reg.mtx. */ +static void __notify_proc(pid_t pid, + int flow_id, + int event) +{ + struct reg_proc * proc; + + proc = __reg_get_proc(pid); + if (proc != NULL) + ssm_flow_set_notify(proc->set, flow_id, event); +} + void reg_notify_flow(int flow_id, int event) { struct reg_flow * flow; - struct reg_proc * proc; + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(flow_id); + if (flow != NULL) + __notify_proc(flow->info.n_pid, flow_id, event); + + pthread_mutex_unlock(®.mtx); +} + +/* Wake both endpoints of a direct flow (acceptor and allocator). */ +void reg_notify_flow_peers(int flow_id, + int event) +{ + struct reg_flow * flow; pthread_mutex_lock(®.mtx); flow = __reg_get_flow(flow_id); if (flow != NULL) { - proc = __reg_get_proc(flow->info.n_pid); - if (proc != NULL) - ssm_flow_set_notify(proc->set, flow_id, event); + __notify_proc(flow->info.n_pid, flow_id, event); + __notify_proc(flow->info.n_1_pid, flow_id, event); } pthread_mutex_unlock(®.mtx); diff --git a/src/irmd/reg/reg.h b/src/irmd/reg/reg.h index e0c64fed..8a313d46 100644 --- a/src/irmd/reg/reg.h +++ b/src/irmd/reg/reg.h @@ -109,6 +109,9 @@ int reg_get_name_for_hash(char * buf, int reg_get_name_for_flow_id(char * buf, int flow_id); +void reg_set_name_for_flow_id(const char * name, + int flow_id); + /* TODO don't rely on protobuf here */ int reg_list_names(name_info_msg_t *** names); @@ -170,10 +173,15 @@ struct rekey_info { pid_t n_1_pid; char name[NAME_SIZE + 1]; uint8_t epoch; + bool direct; }; -void reg_flow_set_rekey(int flow_id, - bool initiator); +void reg_flow_set_rekey(int flow_id, + bool initiator, + buffer_t peer_crt); + +int reg_flow_get_peer_crt(int flow_id, + buffer_t * crt); int reg_flow_get_epoch(int flow_id); @@ -186,17 +194,42 @@ int reg_flow_snapshot_rekey_due(struct rekey_info * snap, void reg_flow_clear_in_flight(int flow_id); +bool reg_flow_rekey_begin(int flow_id); + +bool reg_flow_rekey_should_yield(int flow_id); + int reg_flow_store_pending(int flow_id, const uint8_t * seed, - uint8_t epoch); + uint8_t epoch, + bool initiator); + +int reg_flow_store_pending_direct(int flow_id, + const uint8_t * seed, + uint8_t epoch); -bool reg_flow_take_pending(int flow_id, +int reg_flow_take_pending(int flow_id, + uid_t uid, + pid_t cpid, uint8_t * seed, - uint8_t * epoch); + uint8_t * epoch, + bool * initiator); + +bool reg_flow_rekey_arr_admit(int flow_id, + pid_t n_1_pid, + bool is_req); + +void reg_flow_rekey_arr_done(int flow_id, + bool is_req); + +bool reg_flow_owned_by(int flow_id, + uid_t uid); void reg_notify_flow(int flow_id, int event); +void reg_notify_flow_peers(int flow_id, + int event); + void reg_dealloc_flow(struct flow_info * info); void reg_dealloc_flow_resp(struct flow_info * info); diff --git a/src/irmd/reg/tests/reg_test.c b/src/irmd/reg/tests/reg_test.c index 0b1014f9..a8c1b1fa 100644 --- a/src/irmd/reg/tests/reg_test.c +++ b/src/irmd/reg/tests/reg_test.c @@ -771,6 +771,167 @@ static int test_reg_direct_flow_success(void) return TEST_RC_FAIL; } +/* + * Direct-flow re-key: one shared seed is parked for both local apps. The + * per-app initiator role is resolved from the verified caller pid (the + * allocator is n_1_pid), and the seed is held until both have pulled it. + */ +static int test_reg_direct_flow_rekey(void) +{ + pthread_t thr; + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_S(1); + buffer_t rbuf = BUF_INIT; + buffer_t rsp; + buffer_t no_crt = BUF_INIT; + struct direct_alloc_info dai; + uint8_t seed[SYMMKEYSZ]; + uint8_t out[SYMMKEYSZ]; + uint8_t epoch; + bool initiator; + size_t i; + + struct flow_info info = { + .n_pid = TEST_PID, + .qs = qos_raw + }; + + TEST_START(); + + for (i = 0; i < SYMMKEYSZ; ++i) + seed[i] = (uint8_t) i; + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + + ts_add(&abstime, &timeo, &abstime); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_flow(&info) < 0) { + printf("Failed to add flow.\n"); + goto fail; + } + + if (reg_prepare_flow_accept(&info) < 0) { + printf("Failed to prepare for accept.\n"); + goto fail; + } + + dai.info.id = info.id; + dai.info.n_1_pid = TEST_N_1_PID; + dai.info.mpl = TEST_MPL; + dai.info.qs = qos_msg; + dai.info.state = FLOW_ALLOCATED; + dai.rsp.len = 0; + dai.rsp.data = NULL; + dai.abstime = abstime; + + pthread_create(&thr, NULL, test_flow_alloc_direct, &dai); + + if (reg_wait_flow_accepted(&info, &rbuf, &abstime) < 0) { + printf("Flow accept failed.\n"); + pthread_join(thr, NULL); + goto fail; + } + + freebuf(rbuf); + + rsp.data = (uint8_t *) strdup(TEST_DATA2); + if (rsp.data == NULL) { + printf("Failed to strdup rsp data.\n"); + pthread_join(thr, NULL); + goto fail; + } + rsp.len = strlen(TEST_DATA2) + 1; + + if (reg_respond_flow_direct(info.id, &rsp) < 0) { + printf("Failed to respond direct.\n"); + freebuf(rsp); + pthread_join(thr, NULL); + goto fail; + } + + pthread_join(thr, NULL); + + freebuf(dai.rsp); + + if (!reg_flow_is_direct(info.id)) { + printf("Flow not marked direct.\n"); + goto fail; + } + + reg_flow_set_rekey(info.id, false, no_crt); + + if (reg_flow_store_pending_direct(info.id, seed, 5) < 0) { + printf("Failed to store pending direct seed.\n"); + goto fail; + } + + if (!reg_flow_rekey_pending(info.id)) { + printf("Seed not pending after store.\n"); + goto fail; + } + + /* Allocator (n_1_pid) pulls: initiator role, seed still held. */ + if (reg_flow_take_pending(info.id, 0, TEST_N_1_PID, out, + &epoch, &initiator) != 0) { + printf("Allocator failed to take pending seed.\n"); + goto fail; + } + + if (!initiator || epoch != 5 || memcmp(out, seed, SYMMKEYSZ) != 0) { + printf("Allocator got wrong seed/role/epoch.\n"); + goto fail; + } + + if (!reg_flow_rekey_pending(info.id)) { + printf("Seed cleared before both apps pulled.\n"); + goto fail; + } + + /* Acceptor (n_pid) pulls: responder role, seed now released. */ + if (reg_flow_take_pending(info.id, 0, TEST_PID, out, + &epoch, &initiator) != 0) { + printf("Acceptor failed to take pending seed.\n"); + goto fail; + } + + if (initiator || epoch != 5 || memcmp(out, seed, SYMMKEYSZ) != 0) { + printf("Acceptor got wrong seed/role/epoch.\n"); + goto fail; + } + + if (reg_flow_rekey_pending(info.id)) { + printf("Seed still pending after both pulled.\n"); + goto fail; + } + + if (reg_flow_get_epoch(info.id) != 5) { + printf("Flow epoch not advanced.\n"); + goto fail; + } + + info.n_pid = TEST_PID; + reg_dealloc_flow(&info); + + info.n_pid = TEST_N_1_PID; + reg_dealloc_flow(&info); + + reg_destroy_flow(info.id); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + static int test_reg_flow(void) { int rc = 0; @@ -781,6 +942,7 @@ static int test_reg_flow(void) { rc |= test_reg_allocate_flow_fail(); rc |= test_reg_respond_alloc_duplicate(); rc |= test_reg_direct_flow_success(); + rc |= test_reg_direct_flow_rekey(); return rc; } @@ -875,6 +1037,7 @@ static int test_reg_list_ipcps(void) while (len-- > 0) ipcp_list_msg__free_unpacked(ipcps[len], NULL); + free(ipcps); for (i = 0; i < 10; i++) @@ -941,6 +1104,7 @@ static int test_insert_ipcps(void) while (len-- > 0) ipcp_list_msg__free_unpacked(ipcps[len], NULL); + free(ipcps); reg_clear(); @@ -1118,6 +1282,7 @@ static int test_reg_list_names(void) for (i = 0; i < len; i++) name_info_msg__free_unpacked(names[i], NULL); + free(names); for (i = 0; i < 10; i++) { |
