diff --git a/src/a12/a12.c b/src/a12/a12.c index c2603cda3..4c256e200 100644 --- a/src/a12/a12.c +++ b/src/a12/a12.c @@ -1300,6 +1300,13 @@ static struct blob_out** alloc_attach_blob(struct a12_state* S) return parent; } +void a12_set_session( + struct pk_response* dst, uint8_t pubk[static 32], uint8_t privk[static 32]) +{ + x25519_public_key(privk, dst->key_pub); + x25519_shared_secret(dst->key_session, privk, pubk); +} + /* * Simplified form of enqueue bstream below, we already have the buffer * in memory so just build a different blob-out node with a copy @@ -1487,8 +1494,12 @@ static bool authdec_buffer(const char* src, struct a12_state* S, size_t block_sz static void hello_auth_server_hello(struct a12_state* S) { uint8_t pubk[32]; + uint8_t remote_pubk[32]; + uint8_t nonce[8]; int cfl = S->decode[20]; + + memcpy(remote_pubk, &S->decode[21], 32); a12int_trace(A12_TRACE_CRYPTO, "state=complete:method=%d", cfl); /* here is a spot for having more authentication modes if needed (version bump) */ @@ -1510,7 +1521,7 @@ static void hello_auth_server_hello(struct a12_state* S) arcan_random(nonce, 8); send_hello_packet(S, HELLO_MODE_EPHEMPK, pubk, nonce); - x25519_shared_secret((uint8_t*)S->opts->secret, ek, &S->decode[21]); + x25519_shared_secret((uint8_t*)S->opts->secret, ek, remote_pubk); trace_crypto_key(S->server, "ephem_pub", pubk, 32); update_keymaterial(S, S->opts->secret, 32, nonce); S->authentic = AUTH_EPHEMERAL_PK; @@ -1527,17 +1538,19 @@ static void hello_auth_server_hello(struct a12_state* S) /* the lookup function returns the key that should be used in the reply * and to calculate the shared secret */ - trace_crypto_key(S->server, "state=client_pk", &S->decode[21], 32); - struct pk_response res = S->opts->pk_lookup(&S->decode[21]); + trace_crypto_key(S->server, "state=client_pk", remote_pubk, 32); + struct pk_response res = S->opts->pk_lookup(remote_pubk); if (!res.authentic){ a12int_trace(A12_TRACE_CRYPTO, "state=eperm:kind=x25519-pk-fail"); fail_state(S); return; } + memcpy((uint8_t*)S->opts->secret, res.key_session, 32); + memcpy(pubk, res.key_pub, 32); + /* hello packet here will still use the keystate from the process_srvfirst * which will use the client provided nonce, KDF on preshare-pw */ - x25519_public_key(res.key, pubk); arcan_random(nonce, 8); send_hello_packet(S, HELLO_MODE_REALPK, pubk, nonce); memcpy(S->keys.remote_pub, &S->decode[21], 32); @@ -1545,7 +1558,6 @@ static void hello_auth_server_hello(struct a12_state* S) /* now we can switch keys, note that the new nonce applies for both enc and dec * states regardless of the nonce the client provided in the first message */ - x25519_shared_secret((uint8_t*)S->opts->secret, res.key, &S->decode[21]); trace_crypto_key(S->server, "state=server_ssecret", (uint8_t*)S->opts->secret, 32); update_keymaterial(S, S->opts->secret, 32, nonce); diff --git a/src/a12/a12.h b/src/a12/a12.h index c45811b8a..0c0e1a829 100644 --- a/src/a12/a12.h +++ b/src/a12/a12.h @@ -42,14 +42,14 @@ struct a12_state; * and client- side or the communication will fail regardless of key validity. * * return the private key to use with the public key received (server only) + * OR (migration to better process- separation friendly interface), got_session + * set and key_pub + derived session will be provided rather than the key_priv. */ struct pk_response { bool authentic; -/* For server-side, reply with the key that should be used with the specific - * client if there is differentiation, for client-side, the private key in the - * structure to a12_client will be used */ - uint8_t key[32]; + uint8_t key_pub[32]; + uint8_t key_session[32]; /* If state store is provided / permitted for the key, return a lookup function * for creating or reading named resources from it */ @@ -60,7 +60,9 @@ struct pk_response { struct a12_context_options { /* Provide to enable asymetric key authentication, set valid in the return to * allow the key, otherwise the session may be continued for a random number of - * time or bytes before being terminated. */ + * time or bytes before being terminated. If want_session is requested, the + * lookup function, if it is able to (legacy) should set got_session in the + * reply and calculate the x25519v shared secret itself. */ struct pk_response (*pk_lookup)(uint8_t pub[static 32]); /* Client only, provide the private key to use with the connection. All [0] @@ -127,6 +129,14 @@ struct a12_state* a12_server(struct a12_context_options*); bool a12_free(struct a12_state*); +/* + * Used by the canonical key lookup function that provides the pk_response. + * Provide the remote public key in pubk, and the local private key in privk. + * Calculate the session key and local public key and set in [dst]. + */ +void a12_set_session( + struct pk_response* dst, uint8_t pubk[static 32], uint8_t privk[static 32]); + /* * Take an incoming byte buffer and append to the current state of * the channel. Any received events will be pushed via the callback. diff --git a/src/a12/net/dir_srv.c b/src/a12/net/dir_srv.c index 201e52f7a..6956e8de8 100644 --- a/src/a12/net/dir_srv.c +++ b/src/a12/net/dir_srv.c @@ -43,6 +43,9 @@ struct dircl { arcan_event petname; + uint8_t pubk[32]; + bool authenticated; + struct shmifsrv_client* C; struct dircl* next; struct dircl* prev; @@ -58,6 +61,14 @@ static struct { .sync = PTHREAD_MUTEX_INITIALIZER }; +#define A12INT_DIRTRACE(...) do { \ + if (!(a12_trace_targets & A12_TRACE_DIRECTORY))\ + break;\ + pthread_mutex_lock(&active_clients.sync);\ + a12int_trace(A12_TRACE_DIRECTORY, __VA_ARGS__);\ + pthread_mutex_unlock(&active_clients.sync);\ + } while (0); + /* Check for petname collision among existing instances, this is another of * those policy decisions that should be moved to a scripting layer. */ static bool gotname(struct dircl* source, struct arcan_event ev) @@ -108,7 +119,7 @@ static int buf_memfd(const char* buf, size_t buf_sz) if (errno == EINTR || errno == EAGAIN) continue; close(out); - a12int_trace(A12_TRACE_DIRECTORY, "dirsv:kind=tmpfile:error=%d", errno); + A12INT_DIRTRACE("dirsv:kind=tmpfile:error=%d", errno); return -1; } @@ -140,36 +151,132 @@ static void dirlist_to_worker(struct dircl* C) close(fd); } -volatile struct appl_meta* identifier_to_appl(char* id, int* mtype) +/* + * split a worker provided resource identifier id[.resource] into its components + * and return the matching appl_meta pairing with [id] (if any) + */ +enum { + IDTYPE_APPL = 0, + IDTYPE_STATE = 1, + IDTYPE_DEBUG = 2, + IDTYPE_RAW = 3 +}; + +volatile struct appl_meta* identifier_to_appl(char* id, int* mtype, uint16_t* mid) { *mtype = 0; char* sep = strrchr(id, '.'); - bool state = false; - bool debug = false; - bool rawfile = false; /* are we looking for a subresource? */ if (sep){ *sep = '\0'; sep++; if (strcmp(sep, "state") == 0) - state = true; + *mtype = IDTYPE_STATE; else if (strcmp(sep, "debug") == 0) - debug = true; + *mtype = IDTYPE_DEBUG; else if (strlen(sep) > 0) - rawfile = true; + *mtype = IDTYPE_RAW; + } + + char* err = NULL; + *mid = strtoul(id, &err, 10); + if (!err || *err != '\0'){ + A12INT_DIRTRACE("dirsv:kind=einval:id=%s", id); + return NULL; } + pthread_mutex_lock(&active_clients.sync); volatile struct appl_meta* cur = &active_clients.opts->dir; - while (cur){ + while (cur){ + if (cur->identifier == *mid){ + pthread_mutex_unlock(&active_clients.sync); + A12INT_DIRTRACE("dirsv:resolve_id:id=%s:applname=%s", id, cur->applname); + return cur; + } cur = cur->next; } + pthread_mutex_unlock(&active_clients.sync); + A12INT_DIRTRACE("dirsv:kind=missing_id:id=%s", id); return NULL; } +static void handle_bchunk_req(struct dircl* C, char* ext, bool input) +{ + int mtype; + uint16_t mid = 0; + + volatile struct appl_meta* meta = identifier_to_appl(ext, &mtype, &mid); + + if (!meta) + goto fail; + + int resfd = -1; + size_t ressz = 0; + + if (input){ + switch (mtype){ + case IDTYPE_APPL: + pthread_mutex_lock(&active_clients.sync); + resfd = buf_memfd(meta->buf, meta->buf_sz); + ressz = meta->buf_sz; + pthread_mutex_unlock(&active_clients.sync); + break; + case IDTYPE_STATE: + goto fail; + break; + case IDTYPE_DEBUG: + goto fail; + break; + case IDTYPE_RAW: + goto fail; + break; + } + } + else { + switch (mtype){ + case IDTYPE_APPL: + goto fail; + break; + case IDTYPE_STATE: + goto fail; + break; + case IDTYPE_DEBUG: + goto fail; + break; + case IDTYPE_RAW: + goto fail; + break; + } + } + + if (-1 != resfd){ + struct arcan_event ev = (struct arcan_event){ + .category = EVENT_TARGET, + .tgt.kind = TARGET_COMMAND_BCHUNK_IN, + .tgt.ioevs[1].iv = ressz + }; + snprintf(ev.tgt.message, COUNT_OF(ev.tgt.message), "%"PRIu16, mid); + + shmifsrv_enqueue_event(C->C, &ev, resfd); + close(resfd); + } + else { + goto fail; + } + return; + +fail: + shmifsrv_enqueue_event(C->C, &(struct arcan_event){ + .category = EVENT_TARGET, + .tgt.kind = TARGET_COMMAND_REQFAIL, + .tgt.ioevs[0].uiv = mid + }, -1); +} + static void* dircl_process(void* P) { struct dircl* C = P; @@ -191,7 +298,7 @@ static void* dircl_process(void* P) if (poll(&pfd, 1, pv) > 0){ if (pfd.revents){ if (pfd.revents != POLLIN){ - a12int_trace(A12_TRACE_DIRECTORY, "dirsv:kind=worker:epipe"); + A12INT_DIRTRACE("dirsv:kind=worker:epipe"); break; } pv = 25; @@ -200,13 +307,13 @@ static void* dircl_process(void* P) int sv; if ((sv = shmifsrv_poll(C->C)) == CLIENT_DEAD){ - a12int_trace(A12_TRACE_DIRECTORY, "dirsv:kind=worker:dead"); + A12INT_DIRTRACE("dirsv:kind=worker:dead"); dead = true; continue; } /* send the directory index as a bchunkstate, this lets us avoid abusing the - * MESSAGE event as well as re-using the same codepaths for dynamically + *MESSAGE event as well as re-using the same codepaths for dynamically * updating the index later. */ if (!activated && shmifsrv_poll(C->C) == CLIENT_IDLE){ dirlist_to_worker(C); @@ -221,74 +328,17 @@ static void* dircl_process(void* P) while (1 == shmifsrv_dequeue_events(C->C, &ev, 1)){ /* petName for a source or for joining an appl */ if (ev.ext.kind == EVENT_EXTERNAL_IDENT){ - a12int_trace( - A12_TRACE_DIRECTORY, "dirsv:kind=worker:cl_join=%s", ev.ext.message); + A12INT_DIRTRACE("dirsv:kind=worker:cl_join=%s", ev.ext.message); } /* right now we permit the worker to fetch / update their state store of any - * appl - the other option is to use IDENT to explicitly enter an appl signalling - * that participation in networked activity is desired. */ - if (ev.ext.kind == EVENT_EXTERNAL_BCHUNKSTATE){ - int mtype; - struct appl_meta* meta = - identifier_to_appl(ev.ext.bchunk.extensions, &mtype); - - if (!meta){ - shmifsrv_enqueue_event(C->C, - &(struct arcan_event){ - .category = EVENT_TARGET, - .tgt.kind = TARGET_COMMAND_REQFAIL, - .tgt.ioevs[0].uiv = id - }, -1); - continue; - } - - if (ev.ext.bchunk.input){ - /* mtype == state ? ask statestore */ - - /* 1. mtype == debug ? ask statestore for .debug) - * 2. separate daemon does triage, merge into some other statestore for analytics - * 3. worker with permissions can query / flush the triage - */ - - /* 1. check for permission to update - * 2. if granted, create temp descriptor, wait for it to be closed - * 3. swap out the previous app (while possibly keeping it around if .debug starts growing) - * 4. broadcast the update to any listeners */ - } - else { - /* mtype == state ? ask statestore, otherwise: - * 1. worker participating in an active appl and no ID? - * 2. lookup the dynamic (appl_temp) store for the resource, send that */ - } -/* -* int fd = a12_access_state(cbt->S, meta->applname, "r", 0); - if (-1 != fd){ - a12int_trace(A12_TRACE_DIRECTORY, - "event=bchunkstate:send_state=%s", meta->applname); - a12_enqueue_bstream( - cbt->S, fd, A12_BTYPE_STATE, meta->identifier, false, 0); - close(fd); - } - - a12int_trace(A12_TRACE_DIRECTORY, - "event=bchunkstate:send=%s", meta->applname); - a12_enqueue_blob(cbt->S, meta->buf, meta->buf_sz, meta->identifier); - return; - } - meta = meta->next; - } - a12int_trace(A12_TRACE_DIRECTORY, - "event=bchunkstate:error=no_match:id=%"PRIu16, extid); - } - else - a12int_trace(A12_TRACE_DIRECTORY, - "event=%s", arcan_shmif_eventstr(ev, NULL, 0)); -*/ - a12int_trace( - A12_TRACE_DIRECTORY, "dirsv:kind=worker:get=%s", ev.ext.bchunk.extensions); - } + * appl as the format is id[.resource]. The other option is to use IDENT to + * explicitly enter an appl signalling that participation in networked activity + * is desired. */ + else if (ev.ext.kind == EVENT_EXTERNAL_BCHUNKSTATE){ + handle_bchunk_req(C, (char*) ev.ext.bchunk.extensions, ev.ext.bchunk.input); } + /* registering as a source / directory? */ else if (ev.ext.kind == EVENT_EXTERNAL_NETSTATE){ if (ev.ext.netstate.state == 0){ /* lost */ @@ -296,9 +346,67 @@ static void* dircl_process(void* P) /* this is cheating a bit, SHMIF splits TARGET and EXTERNAL for (srv->cl), (cl->srv) * but by replaying like this we use EXTERNAL as (cl->srv->cl) */ } -/* this takes forwarding to the server-side appl execution (if there is one) or - * a message broadcast to all clients connected to the same appl */ + +/* the generic message passing is first used for sending and authenticating the + * keys on the initial connection. If the authentication goes through and IDENT + * is used to 'join' an appl the MESSAGE facility should (TOFIX) become a broadcast + * domain or wrapped through a Lua VM instance as the server end of the appl. */ else if (ev.ext.kind == EVENT_EXTERNAL_MESSAGE){ + struct arg_arr* entry = arg_unpack((char*)ev.ext.message.data); + if (!entry){ + A12INT_DIRTRACE("dirsv:kind=worker:bad_msg:%s=", ev.ext.message.data); + continue; + } + +/* just route authentication through the regular function, caching the reply */ + const char* pubk; + if (!C->authenticated){ + bool send_fail = false; + + if (!arg_lookup(entry, "a12", 0, NULL) || !arg_lookup(entry, "pubk", 0, &pubk)){ + send_fail = true; + } + else { + uint8_t pubk_dec[32]; + if (!a12helper_fromb64((const uint8_t*) pubk, 32, pubk_dec)){ + send_fail = true; + } + else { + pthread_mutex_lock(&active_clients.sync); + struct pk_response rep = active_clients.opts->a12_cfg->pk_lookup(pubk_dec); + pthread_mutex_unlock(&active_clients.sync); + if (rep.authentic){ + struct arcan_event ev = { + .category = EVENT_TARGET, + .tgt.kind = TARGET_COMMAND_MESSAGE + }; + unsigned char* b64 = a12helper_tob64(rep.key_pub, 32, &(size_t){0}); + snprintf((char*)&ev.tgt.message, COUNT_OF(ev.tgt.message), "a12:pub=%s", b64); + free(b64); + shmifsrv_enqueue_event(C->C, &ev, -1); + b64 = a12helper_tob64(rep.key_session, 32, &(size_t){0}); + snprintf((char*)&ev.tgt.message, COUNT_OF(ev.tgt.message), "a12:ss=%s", b64); + free(b64); + shmifsrv_enqueue_event(C->C, &ev, -1); + } + else + send_fail = true; + } + } + + if (send_fail){ + shmifsrv_enqueue_event(C->C, &(struct arcan_event){ + .category = EVENT_TARGET, + .tgt.kind = TARGET_COMMAND_MESSAGE, + .tgt.message = "a12:fail" + }, -1); + } + } + else { + /* TOFIX: MESSAGE into broadcast or route through server-side APPL */ + } + + arg_cleanup(entry); } } @@ -370,6 +478,9 @@ void anet_directory_shmifsrv_set(struct anet_dirsrv_opts* opts) if (opts->dir.handle || opts->dir.buf){ rebuild_index(); +/* Note that DIRTRACE macro isn't used here as it locks the mutex. + * Setting the directory again after the initial time (vs. individual + * entry updates with broadcast) should be rare to never). */ if (!first){ a12int_trace(A12_TRACE_DIRECTORY, "list_updated"); struct dircl* cur = &active_clients.root; @@ -448,6 +559,8 @@ static FILE* cmd_to_membuf(const char* cmd, char** out, size_t* out_sz) * have to redownload if nothing's changed. See the tar comments further below. */ static size_t scan_appdir(int fd, struct appl_meta* dst) { + int old = open(".", O_RDONLY, O_DIRECTORY); + lseek(fd, 0, SEEK_SET); DIR* dir = fdopendir(fd); struct dirent* ent; @@ -507,6 +620,12 @@ static size_t scan_appdir(int fd, struct appl_meta* dst) a12int_trace(A12_TRACE_DIRECTORY, "scan_over:count=%zu", count); closedir(dir); + + if (-1 != old){ + fchdir(old); + close(old); + } + return count; } diff --git a/src/a12/net/dir_srv_worker.c b/src/a12/net/dir_srv_worker.c index ce2fca42c..fdd3516e6 100644 --- a/src/a12/net/dir_srv_worker.c +++ b/src/a12/net/dir_srv_worker.c @@ -28,6 +28,9 @@ #include #include +static struct arcan_shmif_cont shmif_parent_process; +static struct a12_state* active_client_state; + static void do_event( struct a12_state* S, struct arcan_shmif_cont* C, struct arcan_event* ev); @@ -309,9 +312,99 @@ static void on_shmif(struct a12_state* S, void* tag) } } +/* We come in here after wait_for_activation is complete but don't have a good + * mechanism for sending the actual key. Encode it as base64 in a regular message + * with kpub=%s and wait for kpriv or fail. Re-keying doesn't need this as we + * just generate new keys locally. + */ +static struct pk_response key_auth_worker(uint8_t pk[static 32]) +{ + struct pk_response reply = {0}; + struct arcan_event req = { + .category = EVENT_EXTERNAL, + .ext.kind = EVENT_EXTERNAL_MESSAGE + }; + +/* package the pubk as base-64 in shmif-pack format, set the a12 key + * as an indicator (blocked from other MESSAGE forwarding) and wait + * for the two keys in return (or a fail). */ + size_t outl; + unsigned char* b64 = a12helper_tob64(pk, 32, &outl); + snprintf((char*)req.ext.message.data, + COUNT_OF(req.ext.message.data), "a12:pubk=%s", b64); + free(b64); + +/* Can't do anything else before authentication so blocking here is fine. + * the keys are larger than what fits in one message so we need to split, + * one for session, one for pubk. */ + arcan_shmif_enqueue(&shmif_parent_process, &req); + struct arcan_event rep; + size_t count = 2; + + while (count && arcan_shmif_wait(&shmif_parent_process, &rep) > 0){ + if (rep.category != EVENT_TARGET || rep.tgt.kind != TARGET_COMMAND_MESSAGE){ + a12int_trace( + A12_TRACE_DIRECTORY, + "kind=auth:status=unexpected_event:message=%s", + arcan_shmif_eventstr(&rep, NULL, 0) + ); + continue; + } + + struct arg_arr* stat = arg_unpack(rep.tgt.message); + if (!stat){ + arg_cleanup(stat); + a12int_trace( + A12_TRACE_DIRECTORY, + "kind=auth:status=broken:message=%s", rep.tgt.message); + break; + } + + b64 = NULL; + if (!arg_lookup(stat, "a12", 0, NULL)){ + arg_cleanup(stat); + a12int_trace( + A12_TRACE_DIRECTORY, + "kind=auth:status=broken:message=missing a12 key"); + break; + } + + const char* inkey; + if (arg_lookup(stat, "fail", 0, NULL)){ + a12int_trace(A12_TRACE_DIRECTORY, "kind=auth:status=rejected"); + arg_cleanup(stat); + break; + } + + if (arg_lookup(stat, "pub", 0, &inkey)){ + a12helper_fromb64((uint8_t*) inkey, 32, reply.key_pub); + count--; + } + else if (arg_lookup(stat, "ss", 0, &inkey)){ + a12helper_fromb64((uint8_t*) inkey, 32, reply.key_session); + count--; + } + + arg_cleanup(stat); + } + reply.authentic = count == 0; + + return reply; +} + void anet_directory_srv( - struct a12_state* S, struct anet_dirsrv_opts opts, int fdin, int fdout) + struct a12_context_options* netopts, struct anet_dirsrv_opts opts, int fdin, int fdout) { +/* Swap out authenticator for one that forwards pubkey to parent and waits for + * the derived session key back. This also lets the parent process worker + * tracking thread track pubkey identity for state store. */ + + netopts->pk_lookup = key_auth_worker; + struct a12_state* S = a12_server(netopts); + active_client_state = S; + + struct anet_dirsrv_opts diropts = {}; + struct directory_meta cbt = { .dir = &opts.dir, .S = S @@ -320,34 +413,52 @@ void anet_directory_srv( struct arg_arr* args; a12int_trace(A12_TRACE_DIRECTORY, "notice:directory-ready:pid=%d", getpid()); - struct arcan_shmif_cont C = + shmif_parent_process = arcan_shmif_open( SEGID_NETWORK_SERVER, SHMIF_ACQUIRE_FATALFAIL | - SHMIF_DISABLE_GUARD | SHMIF_NOACTIVATE | SHMIF_NOREGISTER, &args ); - C.user = &cbt; + shmif_parent_process.user = &cbt; -/* now that we have the shmif context, all we need is stdio and descriptor - passing */ +/* Now that we have the shmif context, all we need is stdio and descriptor + passing. The rest - keystore, state access, everything is done elsewhere. - cbt.C = &C; + For meaningful access the attacker would have to get local code-exec, + infoleak the shmpage, find a vuln in either the BCHUNKSTATE event handling + code or the simplified use of shmif in the parent process with + stdio/fdpassing level of syscalls. +*/ + struct shmif_privsep_node* paths[] = {NULL}; + arcan_shmif_privsep(&shmif_parent_process, "minimal", paths, 0); + + cbt.C = &shmif_parent_process; /* flush out the event loop before starting as that is likely to update our * list of active directory entries - this will also block until ACTIVATE is * received due to the NOACTIVATE _open */ - if (!wait_for_activation(S, &C)){ + if (!wait_for_activation(S, &shmif_parent_process)){ a12int_trace(A12_TRACE_DIRECTORY, "error=control_channel"); return; } + + char* msg = NULL; + if (!anet_authenticate(S, fdin, fdout, &msg)){ + a12int_trace(A12_TRACE_SYSTEM, "authentication failed: %s", msg); + } + else { + } + a12_set_bhandler(S, srv_bevent, &cbt); /* this will loop until client shutdown */ - anet_directory_ioloop(S, &C, fdin, fdout, C.epipe, on_srv_event, NULL, on_shmif); - arcan_shmif_drop(&C); + anet_directory_ioloop(S, + &shmif_parent_process, fdin, fdout, + shmif_parent_process.epipe, on_srv_event, NULL, on_shmif); + + arcan_shmif_drop(&shmif_parent_process); } static struct appl_meta* find_identifier(struct appl_meta* base, unsigned id) @@ -374,7 +485,7 @@ static int request_parent_resource( ev.ext.bchunk.input = false; } else { - kind = TARAGET_COMMAND_BCHUNK_IN; /* we want input from .. */ + kind = TARGET_COMMAND_BCHUNK_IN; /* we want input from .. */ ev.ext.bchunk.input = true; } diff --git a/src/a12/net/directory.h b/src/a12/net/directory.h index 1ed99bccc..974c570a0 100644 --- a/src/a12/net/directory.h +++ b/src/a12/net/directory.h @@ -5,6 +5,7 @@ * where is the basedir */ struct anet_dirsrv_opts { + struct a12_context_options* a12_cfg; int basedir; struct appl_meta dir; size_t dir_count; @@ -67,7 +68,7 @@ struct directory_meta { void anet_directory_srv_rescan(struct anet_dirsrv_opts* opts); void anet_directory_srv( - struct a12_state* S, struct anet_dirsrv_opts opts, int fdin, int fdout); + struct a12_context_options*, struct anet_dirsrv_opts, int fdin, int fdout); /* * shmif connection to map to a thread for coordination diff --git a/src/a12/net/net.c b/src/a12/net/net.c index 87c7812fb..b6a12c4e7 100644 --- a/src/a12/net/net.c +++ b/src/a12/net/net.c @@ -187,7 +187,7 @@ static int get_bcache_dir() if (!base) return -1; - return open(base, O_DIRECTORY); + return open(base, O_DIRECTORY | O_CLOEXEC); } static void set_log_trace() @@ -212,6 +212,7 @@ static void fork_a12srv(struct a12_state* S, int fd, void* tag) * and inherit shmif into the forked child that is a re-execution of ourselves. */ int clsock = -1; struct shmifsrv_client* cl = NULL; + struct arcan_net_meta* ameta = tag; if (global.directory > 0){ char tmpfd[32], tmptrace[32]; @@ -220,17 +221,12 @@ static void fork_a12srv(struct a12_state* S, int fd, void* tag) char* argv[] = {global.path_self, "-d", tmptrace, "-S", tmpfd, NULL, NULL}; -/* Any authentication parameters need to be provided as that will happen in the - * forked child for now. The safer option is to also have the key derivation in - * parent. */ - if (global.soft_auth) - argv[5] = "--soft-auth"; - + /* shmif-server lib will get to waitpid / kill so we don't need to care here */ struct shmifsrv_envp env = { .path = global.path_self, .envv = NULL, .argv = argv, - .detach = 1 | 2 | 4 + .detach = 2 | 4 | 8 }; cl = shmifsrv_spawn_client(env, &clsock, NULL, 0); @@ -912,7 +908,9 @@ static int apply_commandline(int argc, char** argv, struct arcan_net_meta* meta) return show_usage("--directory without ARCAN_APPLBASEPATH set"); } - global.directory = open(getenv("ARCAN_APPLBASEPATH"), O_DIRECTORY); + global.directory = open( + getenv("ARCAN_APPLBASEPATH"), O_DIRECTORY | O_CLOEXEC); + if (-1 == global.directory){ return show_usage("--directory ARCAN_APPLBASEPATH couldn't be opened"); } @@ -1055,22 +1053,27 @@ static int apply_keystore_command(int argc, char** argv) static struct pk_response key_auth_local(uint8_t pk[static 32]) { struct pk_response auth = {}; + uint8_t my_private_key[32]; char* tmp; uint16_t tmpport; size_t outl; unsigned char* out = a12helper_tob64(pk, 32, &outl); -/* is the key in our trusted set? */ +/* the trust domain (accepted return value) are ignored here, a separate + * request will check if a certain domain is trusted for the kpub when/if a + * request arrives that mandates it */ if (a12helper_keystore_accepted(pk, global.trust_domain)){ auth.authentic = true; a12int_trace(A12_TRACE_SECURITY, "accept=%s", out); - a12helper_keystore_hostkey("default", 0, auth.key, &tmp, &tmpport); + a12helper_keystore_hostkey("default", 0, my_private_key, &tmp, &tmpport); + a12_set_session(&auth, pk, my_private_key); } /* or do we not care about pk authenticity - password in first HMAC only */ - if (global.soft_auth){ + else if (global.soft_auth){ auth.authentic = true; - a12helper_keystore_hostkey("default", 0, auth.key, &tmp, &tmpport); + a12helper_keystore_hostkey("default", 0, my_private_key, &tmp, &tmpport); + a12_set_session(&auth, pk, my_private_key); a12int_trace(A12_TRACE_SECURITY, "soft-auth-trust=%s", out); } /* or should we add the first n unknown as implicitly trusted through pass */ @@ -1082,7 +1085,8 @@ static struct pk_response key_auth_local(uint8_t pk[static 32]) /* trust-domain covers both case 3 and 4. */ a12helper_keystore_accept(pk, global.trust_domain); - a12helper_keystore_hostkey("default", 0, auth.key, &tmp, &tmpport); + a12helper_keystore_hostkey("default", 0, my_private_key, &tmp, &tmpport); + a12_set_session(&auth, pk, my_private_key); } /* Since SSH has trained people on this behaviour, allow interactive override. @@ -1112,14 +1116,16 @@ static struct pk_response key_auth_local(uint8_t pk[static 32]) fgets(buf, 16, stdin); if (strcmp(buf, "yes\n") == 0){ auth.authentic = true; - a12helper_keystore_hostkey("default", 0, auth.key, &tmp, &tmpport); + a12helper_keystore_hostkey("default", 0, my_private_key, &tmp, &tmpport); + a12_set_session(&auth, pk, my_private_key); a12int_trace(A12_TRACE_SECURITY, "interactive-soft-auth=%s", out); } else if (strcmp(buf, "remember\n") == 0){ auth.authentic = true; a12helper_keystore_accept(pk, global.trust_domain); a12int_trace(A12_TRACE_SECURITY, "interactive-add-trust=%s", out); - a12helper_keystore_hostkey("default", 0, auth.key, &tmp, &tmpport); + a12helper_keystore_hostkey("default", 0, my_private_key, &tmp, &tmpport); + a12_set_session(&auth, pk, my_private_key); } else a12int_trace(A12_TRACE_SECURITY, "rejected-interactive"); @@ -1150,6 +1156,7 @@ int main(int argc, char** argv) anet.opts = a12_sensitive_alloc(sizeof(struct a12_context_options)); anet.opts->pk_lookup = key_auth_local; + global.dirsrv.a12_cfg = anet.opts; /* set this as default, so the remote side can't actually close */ anet.redirect_exit = getenv("ARCAN_CONNPATH"); @@ -1329,21 +1336,9 @@ int main(int argc, char** argv) return a12_preauth(&anet, a12cl_dispatch); } else if (anet.mode == ANET_SHMIF_DIRSRV_INHERIT){ - struct a12_state* S = a12_server(anet.opts); - struct anet_dirsrv_opts diropts = {}; set_log_trace(); - -/* this is where we can swap in keymanagement to keep the keystore entirely - * outside of the directory client process */ - char* msg = NULL; - if (!anet_authenticate(S, anet.sockfd, anet.sockfd, &msg)){ - a12int_trace(A12_TRACE_SYSTEM, "authentication failed: %s", msg); - } - else { - anet_directory_srv(S, diropts, anet.sockfd, anet.sockfd); - } - - free(msg); + struct anet_dirsrv_opts diropts = {0}; + anet_directory_srv(anet.opts, diropts, anet.sockfd, anet.sockfd); shutdown(anet.sockfd, SHUT_RDWR); close(anet.sockfd); return EXIT_SUCCESS; diff --git a/src/frameserver/net/default/net.c b/src/frameserver/net/default/net.c index 3f67620b1..5c90ce4ad 100644 --- a/src/frameserver/net/default/net.c +++ b/src/frameserver/net/default/net.c @@ -64,8 +64,10 @@ static struct pk_response key_auth_local(uint8_t pk[static 32]) * message onwards and wait for an accept or reject event before moving on. */ if (a12helper_keystore_accepted(pk, global.trust_domain) || global.soft_auth){ + uint8_t key_priv[32]; auth.authentic = true; - a12helper_keystore_hostkey("default", 0, auth.key, &tmp, &tmpport); + a12helper_keystore_hostkey("default", 0, key_priv, &tmp, &tmpport); + a12_set_session(&auth, pk, key_priv); } return auth; diff --git a/src/frameserver/util/anet_helper.h b/src/frameserver/util/anet_helper.h index cda94dd72..1f1ff5864 100644 --- a/src/frameserver/util/anet_helper.h +++ b/src/frameserver/util/anet_helper.h @@ -99,6 +99,8 @@ bool a12helper_keystore_release(); uint8_t* a12helper_tob64(const uint8_t* data, size_t inl, size_t* outl); +bool a12helper_fromb64(const uint8_t* instr, size_t lim, uint8_t outb[static 32]); + /* retrieve key and connect properties for a user-defined tag, * increment index to fetch the next possible host. * diff --git a/src/frameserver/util/anet_keystore_naive.c b/src/frameserver/util/anet_keystore_naive.c index f966ef3cb..135ce3bf8 100644 --- a/src/frameserver/util/anet_keystore_naive.c +++ b/src/frameserver/util/anet_keystore_naive.c @@ -113,7 +113,7 @@ uint8_t* a12helper_tob64(const uint8_t* data, size_t inl, size_t* outl) return res; } -static bool from_b64(const uint8_t* instr, size_t lim, uint8_t outb[static 32]) +bool a12helper_fromb64(const uint8_t* instr, size_t lim, uint8_t outb[static 32]) { size_t inlen = strlen((char*)instr); @@ -191,7 +191,7 @@ static bool decode_hostline(char* buf, *outhost = buf; /* decode keypart */ - return from_b64((uint8_t*) cur, 32, key); + return a12helper_fromb64((uint8_t*) cur, 32, key); } static void flush_accepted_keys() @@ -716,16 +716,16 @@ int a12helper_keystore_dirfd(const char** err) return -1; } - int dir = open(basedir, O_DIRECTORY); + int dir = open(basedir, O_DIRECTORY | O_CLOEXEC); if (-1 == dir){ *err = "Error opening basedir, check permissions and type"; return -1; } - int keydir = openat(dir, "a12", O_DIRECTORY); + int keydir = openat(dir, "a12", O_DIRECTORY | O_CLOEXEC); if (-1 == keydir){ mkdirat(dir, "a12", S_IRWXU); - keydir = openat(dir, "a12", O_DIRECTORY); + keydir = openat(dir, "a12", O_DIRECTORY | O_CLOEXEC); } return keydir; diff --git a/src/shmif/arcan_shmif_server.c b/src/shmif/arcan_shmif_server.c index 32207697a..5f45d7489 100644 --- a/src/shmif/arcan_shmif_server.c +++ b/src/shmif/arcan_shmif_server.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include /* * This is needed in order to re-use some of the platform layer functions that @@ -44,6 +46,7 @@ struct shmifsrv_client { /* need a 'per client' eventqueue */ struct arcan_frameserver* con; enum connstatus status; + pid_t pid; size_t errors; uint64_t cookie; }; @@ -222,10 +225,7 @@ struct shmifsrv_client* shmifsrv_spawn_client( shmifsrv_free(res, SHMIFSRV_FREE_FULL); return NULL; } - -/* there is no API for returning / binding the pid here, the use for that seems - * rather fringe (possibly for kill like mechanics), if needed we should tie it - * to the context and add an accessor */ + res->pid = rpid; } return res; @@ -368,6 +368,26 @@ int shmifsrv_poll(struct shmifsrv_client* cl) return CLIENT_NOT_READY; } +static void* nanny_thread(void* arg) +{ + pid_t* pid = (pid_t*) arg; + int counter = 10; + while (counter--){ + int statusfl; + int rv = waitpid(*pid, &statusfl, WNOHANG); + if (rv > 0) + break; + else if (counter == 0){ + kill(*pid, SIGKILL); + waitpid(*pid, &statusfl, 0); + break; + } + sleep(1); + } + free(pid); + return NULL; +} + void shmifsrv_free(struct shmifsrv_client* cl, int mode) { if (!cl) @@ -387,6 +407,20 @@ void shmifsrv_free(struct shmifsrv_client* cl, int mode) break; } +/* the same nanny-kill thread approach as used in platform-posix-frameserver */ + if (cl->pid){ + pid_t* pidptr = malloc(sizeof(pid_t)); + pthread_attr_t nanny_attr; + pthread_attr_init(&nanny_attr); + pthread_attr_setdetachstate(&nanny_attr, PTHREAD_CREATE_DETACHED); + *pidptr = cl->pid; + + pthread_t nanny; + if (0 != pthread_create(&nanny, &nanny_attr, nanny_thread, (void*) pidptr)) + kill(cl->pid, SIGKILL); + pthread_attr_destroy(&nanny_attr); + } + cl->status = DEAD; free(cl); } diff --git a/src/shmif/platform/exec.c b/src/shmif/platform/exec.c index 093e0a7e4..1d24bbfc8 100644 --- a/src/shmif/platform/exec.c +++ b/src/shmif/platform/exec.c @@ -145,7 +145,7 @@ pid_t shmif_platform_execve(int fd, const char* shmif_key, *fds[2] = perr[0]; close_err = true; } - else stdout_src = *fds[2]; + else stderr_src = *fds[2]; } /* null- terminate or we have an invalid address on our hands */ diff --git a/src/shmif/platform/shmif_platform.h b/src/shmif/platform/shmif_platform.h index c23f0ea6f..509c5b9e6 100644 --- a/src/shmif/platform/shmif_platform.h +++ b/src/shmif/platform/shmif_platform.h @@ -11,7 +11,10 @@ enum platform_execve_opts { EXECVE_NONE = 0, - EXECVE_DETACH_PROCESS = 1 + EXECVE_DETACH_PROCESS = 1, + EXECVE_DETACH_STDIN = 2, + EXECVE_DETACH_STDOUT = 4, + EXECVE_DETACH_STDERR = 8 }; /* Take a file descriptor (shmif_fd) that corresponds to the control socket