# session key destruction and auditing # based on: # https://bugzilla.mindrot.org/show_bug.cgi?id=1402 # https://bugzilla.mindrot.org/attachment.cgi?id=2014 # by jchadima@redhat.com diff --git a/openssh-6.4p1/audit-bsm.c b/openssh-6.4p1/audit-bsm.c --- a/openssh-6.4p1/audit-bsm.c +++ b/openssh-6.4p1/audit-bsm.c @@ -480,9 +480,15 @@ audit_unsupported_body(int what) /* not implemented */ } void audit_kex_body(int ctos, char *enc, char *mac, char *compress, pid_t pid, uid_t uid) { /* not implemented */ } + +void +audit_session_key_free_body(int ctos, pid_t pid, uid_t uid) +{ + /* not implemented */ +} #endif /* BSM */ diff --git a/openssh-6.4p1/audit-linux.c b/openssh-6.4p1/audit-linux.c --- a/openssh-6.4p1/audit-linux.c +++ b/openssh-6.4p1/audit-linux.c @@ -289,24 +289,25 @@ audit_unsupported_body(int what) /* no problem, the next instruction will be fatal() */ return; audit_log_user_message(audit_fd, AUDIT_CRYPTO_SESSION, buf, NULL, get_remote_ipaddr(), NULL, 0); audit_close(audit_fd); #endif } +const static char *direction[] = { "from-server", "from-client", "both" }; + void audit_kex_body(int ctos, char *enc, char *mac, char *compress, pid_t pid, uid_t uid) { #ifdef AUDIT_CRYPTO_SESSION char buf[AUDIT_LOG_SIZE]; int audit_fd, audit_ok; - const static char *direction[] = { "from-server", "from-client", "both" }; Cipher *cipher = cipher_by_name(enc); char *s; snprintf(buf, sizeof(buf), "op=start direction=%s cipher=%s ksize=%d spid=%jd suid=%jd rport=%d laddr=%s lport=%d ", direction[ctos], enc, cipher ? 8 * cipher->key_len : 0, (intmax_t)pid, (intmax_t)uid, get_remote_port(), (s = get_local_ipaddr(packet_get_connection_in())), get_local_port()); free(s); @@ -322,9 +323,37 @@ audit_kex_body(int ctos, char *enc, char buf, NULL, get_remote_ipaddr(), NULL, 1); audit_close(audit_fd); /* do not abort if the error is EPERM and sshd is run as non root user */ if ((audit_ok < 0) && ((audit_ok != -1) || (getuid() == 0))) fatal("cannot write into audit"); /* Must prevent login */ #endif } +void +audit_session_key_free_body(int ctos, pid_t pid, uid_t uid) +{ + char buf[AUDIT_LOG_SIZE]; + int audit_fd, audit_ok; + char *s; + + snprintf(buf, sizeof(buf), "op=destroy kind=session fp=? direction=%s spid=%jd suid=%jd rport=%d laddr=%s lport=%d ", + direction[ctos], (intmax_t)pid, (intmax_t)uid, + get_remote_port(), + (s = get_local_ipaddr(packet_get_connection_in())), + get_local_port()); + free(s); + audit_fd = audit_open(); + if (audit_fd < 0) { + if (errno != EINVAL && errno != EPROTONOSUPPORT && + errno != EAFNOSUPPORT) + error("cannot open audit"); + return; + } + audit_ok = audit_log_user_message(audit_fd, AUDIT_CRYPTO_KEY_USER, + buf, NULL, get_remote_ipaddr(), NULL, 1); + audit_close(audit_fd); + /* do not abort if the error is EPERM and sshd is run as non root user */ + if ((audit_ok < 0) && ((audit_ok != -1) || (getuid() == 0))) + error("cannot write into audit"); +} + #endif /* USE_LINUX_AUDIT */ diff --git a/openssh-6.4p1/audit.c b/openssh-6.4p1/audit.c --- a/openssh-6.4p1/audit.c +++ b/openssh-6.4p1/audit.c @@ -138,16 +138,22 @@ audit_unsupported(int what) } void audit_kex(int ctos, char *enc, char *mac, char *comp) { PRIVSEP(audit_kex_body(ctos, enc, mac, comp, getpid(), getuid())); } +void +audit_session_key_free(int ctos) +{ + PRIVSEP(audit_session_key_free_body(ctos, getpid(), getuid())); +} + # ifndef CUSTOM_SSH_AUDIT_EVENTS /* * Null implementations of audit functions. * These get used if SSH_AUDIT_EVENTS is defined but no audit module is enabled. */ /* * Called after a connection has been accepted but before any authentication @@ -269,10 +275,20 @@ audit_unsupported_body(int what) void audit_kex_body(int ctos, char *enc, char *mac, char *compress, pid_t pid, uid_t uid) { debug("audit protocol negotiation euid %d direction %d cipher %s mac %s compresion %s from pid %ld uid %u", (unsigned)geteuid(), ctos, enc, mac, compress, (long)pid, (unsigned)uid); } + +/* + * This will be called on succesfull session key discard + */ +void +audit_session_key_free_body(int ctos, pid_t pid, uid_t uid) +{ + debug("audit session key discard euid %u direction %d from pid %ld uid %u", + (unsigned)geteuid(), ctos, (long)pid, (unsigned)uid); +} # endif /* !defined CUSTOM_SSH_AUDIT_EVENTS */ #endif /* SSH_AUDIT_EVENTS */ diff --git a/openssh-6.4p1/audit.h b/openssh-6.4p1/audit.h --- a/openssh-6.4p1/audit.h +++ b/openssh-6.4p1/audit.h @@ -57,10 +57,12 @@ int audit_run_command(const char *); void audit_end_command(int, const char *); ssh_audit_event_t audit_classify_auth(const char *); int audit_keyusage(int, const char *, unsigned, char *, int); void audit_key(int, int *, const Key *); void audit_unsupported(int); void audit_kex(int, char *, char *, char *); void audit_unsupported_body(int); void audit_kex_body(int, char *, char *, char *, pid_t, uid_t); +void audit_session_key_free(int ctos); +void audit_session_key_free_body(int ctos, pid_t, uid_t); #endif /* _SSH_AUDIT_H */ diff --git a/openssh-6.4p1/auditstub.c b/openssh-6.4p1/auditstub.c --- a/openssh-6.4p1/auditstub.c +++ b/openssh-6.4p1/auditstub.c @@ -22,18 +22,29 @@ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Red Hat author: Jan F. Chadima */ +#include + void audit_unsupported(int n) { } void audit_kex(int ctos, char *enc, char *mac, char *comp) { } +void +audit_session_key_free(int ctos) +{ +} + +void +audit_session_key_free_body(int ctos, pid_t pid, uid_t uid) +{ +} diff --git a/openssh-6.4p1/kex.c b/openssh-6.4p1/kex.c --- a/openssh-6.4p1/kex.c +++ b/openssh-6.4p1/kex.c @@ -667,8 +667,39 @@ dump_digest(char *msg, u_char *digest, i if (i%32 == 31) fprintf(stderr, "\n"); else if (i%8 == 7) fprintf(stderr, " "); } fprintf(stderr, "\n"); } #endif + +static void +enc_destroy(Enc *enc) +{ + if (enc == NULL) + return; + + if (enc->key) { + memset(enc->key, 0, enc->key_len); + free(enc->key); + } + + if (enc->iv) { + memset(enc->iv, 0, enc->block_size); + free(enc->iv); + } + + memset(enc, 0, sizeof(*enc)); +} + +void +newkeys_destroy(Newkeys *newkeys) +{ + if (newkeys == NULL) + return; + + enc_destroy(&newkeys->enc); + mac_destroy(&newkeys->mac); + memset(&newkeys->comp, 0, sizeof(newkeys->comp)); +} + diff --git a/openssh-6.4p1/kex.h b/openssh-6.4p1/kex.h --- a/openssh-6.4p1/kex.h +++ b/openssh-6.4p1/kex.h @@ -157,16 +157,18 @@ Newkeys *kex_get_newkeys(int); void kexdh_client(Kex *); void kexdh_server(Kex *); void kexgex_client(Kex *); void kexgex_server(Kex *); void kexecdh_client(Kex *); void kexecdh_server(Kex *); +void newkeys_destroy(Newkeys *newkeys); + void kex_dh_hash(char *, char *, char *, int, char *, int, u_char *, int, BIGNUM *, BIGNUM *, BIGNUM *, u_char **, u_int *); void kexgex_hash(const EVP_MD *, char *, char *, char *, int, char *, int, u_char *, int, int, int, int, BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *, BIGNUM *, u_char **, u_int *); #ifdef OPENSSL_HAS_ECC diff --git a/openssh-6.4p1/mac.c b/openssh-6.4p1/mac.c --- a/openssh-6.4p1/mac.c +++ b/openssh-6.4p1/mac.c @@ -219,16 +219,30 @@ mac_clear(Mac *mac) if (mac->umac_ctx != NULL) umac128_delete(mac->umac_ctx); } else if (mac->evp_md != NULL) HMAC_cleanup(&mac->evp_ctx); mac->evp_md = NULL; mac->umac_ctx = NULL; } +void +mac_destroy(Mac *mac) +{ + if (mac == NULL) + return; + + if (mac->key) { + memset(mac->key, 0, mac->key_len); + free(mac->key); + } + + memset(mac, 0, sizeof(*mac)); +} + /* XXX copied from ciphers_valid */ #define MAC_SEP "," int mac_valid(const char *names) { char *maclist, *cp, *p; if (names == NULL || strcmp(names, "") == 0) diff --git a/openssh-6.4p1/mac.h b/openssh-6.4p1/mac.h --- a/openssh-6.4p1/mac.h +++ b/openssh-6.4p1/mac.h @@ -24,8 +24,9 @@ */ int mac_valid(const char *); char *mac_alg_list(void); int mac_setup(Mac *, char *); int mac_init(Mac *); u_char *mac_compute(Mac *, u_int32_t, u_char *, int); void mac_clear(Mac *); +void mac_destroy(Mac *); diff --git a/openssh-6.4p1/monitor.c b/openssh-6.4p1/monitor.c --- a/openssh-6.4p1/monitor.c +++ b/openssh-6.4p1/monitor.c @@ -185,16 +185,17 @@ int mm_answer_gss_checkmic(int, Buffer * #endif #ifdef SSH_AUDIT_EVENTS int mm_answer_audit_event(int, Buffer *); int mm_answer_audit_command(int, Buffer *); int mm_answer_audit_end_command(int, Buffer *); int mm_answer_audit_unsupported_body(int, Buffer *); int mm_answer_audit_kex_body(int, Buffer *); +int mm_answer_audit_session_key_free_body(int, Buffer *); #endif static int monitor_read_log(struct monitor *); static Authctxt *authctxt; static BIGNUM *ssh1_challenge = NULL; /* used for ssh1 rsa auth */ /* local state for key verify */ @@ -238,16 +239,17 @@ struct mon_table mon_dispatch_proto20[] {MONITOR_REQ_PAM_QUERY, MON_ISAUTH, mm_answer_pam_query}, {MONITOR_REQ_PAM_RESPOND, MON_ISAUTH, mm_answer_pam_respond}, {MONITOR_REQ_PAM_FREE_CTX, MON_ONCE|MON_AUTHDECIDE, mm_answer_pam_free_ctx}, #endif #ifdef SSH_AUDIT_EVENTS {MONITOR_REQ_AUDIT_EVENT, MON_PERMIT, mm_answer_audit_event}, {MONITOR_REQ_AUDIT_UNSUPPORTED, MON_PERMIT, mm_answer_audit_unsupported_body}, {MONITOR_REQ_AUDIT_KEX, MON_PERMIT, mm_answer_audit_kex_body}, + {MONITOR_REQ_AUDIT_SESSION_KEY_FREE, MON_PERMIT, mm_answer_audit_session_key_free_body}, #endif #ifdef BSD_AUTH {MONITOR_REQ_BSDAUTHQUERY, MON_ISAUTH, mm_answer_bsdauthquery}, {MONITOR_REQ_BSDAUTHRESPOND, MON_AUTH, mm_answer_bsdauthrespond}, #endif #ifdef SKEY {MONITOR_REQ_SKEYQUERY, MON_ISAUTH, mm_answer_skeyquery}, {MONITOR_REQ_SKEYRESPOND, MON_AUTH, mm_answer_skeyrespond}, @@ -277,16 +279,17 @@ struct mon_table mon_dispatch_postauth20 {MONITOR_REQ_PTYCLEANUP, 0, mm_answer_pty_cleanup}, {MONITOR_REQ_TERM, 0, mm_answer_term}, #ifdef SSH_AUDIT_EVENTS {MONITOR_REQ_AUDIT_EVENT, MON_PERMIT, mm_answer_audit_event}, {MONITOR_REQ_AUDIT_COMMAND, MON_PERMIT, mm_answer_audit_command}, {MONITOR_REQ_AUDIT_END_COMMAND, MON_PERMIT, mm_answer_audit_end_command}, {MONITOR_REQ_AUDIT_UNSUPPORTED, MON_PERMIT, mm_answer_audit_unsupported_body}, {MONITOR_REQ_AUDIT_KEX, MON_PERMIT, mm_answer_audit_kex_body}, + {MONITOR_REQ_AUDIT_SESSION_KEY_FREE, MON_PERMIT, mm_answer_audit_session_key_free_body}, #endif {0, 0, NULL} }; struct mon_table mon_dispatch_proto15[] = { {MONITOR_REQ_PWNAM, MON_ONCE, mm_answer_pwnamallow}, {MONITOR_REQ_SESSKEY, MON_ONCE, mm_answer_sesskey}, {MONITOR_REQ_SESSID, MON_ONCE, mm_answer_sessid}, @@ -310,30 +313,32 @@ struct mon_table mon_dispatch_proto15[] {MONITOR_REQ_PAM_QUERY, MON_ISAUTH, mm_answer_pam_query}, {MONITOR_REQ_PAM_RESPOND, MON_ISAUTH, mm_answer_pam_respond}, {MONITOR_REQ_PAM_FREE_CTX, MON_ONCE|MON_AUTHDECIDE, mm_answer_pam_free_ctx}, #endif #ifdef SSH_AUDIT_EVENTS {MONITOR_REQ_AUDIT_EVENT, MON_PERMIT, mm_answer_audit_event}, {MONITOR_REQ_AUDIT_UNSUPPORTED, MON_PERMIT, mm_answer_audit_unsupported_body}, {MONITOR_REQ_AUDIT_KEX, MON_PERMIT, mm_answer_audit_kex_body}, + {MONITOR_REQ_AUDIT_SESSION_KEY_FREE, MON_PERMIT, mm_answer_audit_session_key_free_body}, #endif {0, 0, NULL} }; struct mon_table mon_dispatch_postauth15[] = { {MONITOR_REQ_PTY, MON_ONCE, mm_answer_pty}, {MONITOR_REQ_PTYCLEANUP, MON_ONCE, mm_answer_pty_cleanup}, {MONITOR_REQ_TERM, 0, mm_answer_term}, #ifdef SSH_AUDIT_EVENTS {MONITOR_REQ_AUDIT_EVENT, MON_PERMIT, mm_answer_audit_event}, {MONITOR_REQ_AUDIT_COMMAND, MON_PERMIT|MON_ONCE, mm_answer_audit_command}, {MONITOR_REQ_AUDIT_END_COMMAND, MON_PERMIT, mm_answer_audit_end_command}, {MONITOR_REQ_AUDIT_UNSUPPORTED, MON_PERMIT, mm_answer_audit_unsupported_body}, {MONITOR_REQ_AUDIT_KEX, MON_PERMIT, mm_answer_audit_kex_body}, + {MONITOR_REQ_AUDIT_SESSION_KEY_FREE, MON_PERMIT, mm_answer_audit_session_key_free_body}, #endif {0, 0, NULL} }; struct mon_table *mon_dispatch; /* Specifies if a certain message is allowed at the moment */ @@ -1970,21 +1975,23 @@ mm_get_keystate(struct monitor *pmonitor goto skip; } else { /* Get the Kex for rekeying */ *pmonitor->m_pkex = mm_get_kex(&m); } blob = buffer_get_string(&m, &bloblen); current_keys[MODE_OUT] = mm_newkeys_from_blob(blob, bloblen); + memset(blob, 0, bloblen); free(blob); debug3("%s: Waiting for second key", __func__); blob = buffer_get_string(&m, &bloblen); current_keys[MODE_IN] = mm_newkeys_from_blob(blob, bloblen); + memset(blob, 0, bloblen); free(blob); /* Now get sequence numbers for the packets */ seqnr = buffer_get_int(&m); blocks = buffer_get_int64(&m); packets = buffer_get_int(&m); bytes = buffer_get_int64(&m); packet_set_state(MODE_OUT, seqnr, blocks, packets, bytes); @@ -2020,16 +2027,31 @@ mm_get_keystate(struct monitor *pmonitor /* Roaming */ if (compat20) { child_state.sent_bytes = buffer_get_int64(&m); child_state.recv_bytes = buffer_get_int64(&m); } buffer_free(&m); + +#ifdef SSH_AUDIT_EVENTS + if (compat20) { + buffer_init(&m); + mm_request_receive_expect(pmonitor->m_sendfd, + MONITOR_REQ_AUDIT_SESSION_KEY_FREE, &m); + mm_answer_audit_session_key_free_body(pmonitor->m_sendfd, &m); + buffer_free(&m); + } +#endif + + /* Drain any buffered messages from the child */ + while (pmonitor->m_log_recvfd >= 0 && monitor_read_log(pmonitor) == 0) + ; + } /* Allocation functions for zlib */ void * mm_zalloc(struct mm_master *mm, u_int ncount, u_int size) { size_t len = (size_t) size * ncount; @@ -2464,9 +2486,27 @@ mm_answer_audit_kex_body(int sock, Buffe free(mac); free(compress); buffer_clear(m); mm_request_send(sock, MONITOR_ANS_AUDIT_KEX, m); return 0; } +int +mm_answer_audit_session_key_free_body(int sock, Buffer *m) +{ + int ctos; + pid_t pid; + uid_t uid; + + ctos = buffer_get_int(m); + pid = buffer_get_int64(m); + uid = buffer_get_int64(m); + + audit_session_key_free_body(ctos, pid, uid); + + buffer_clear(m); + + mm_request_send(sock, MONITOR_ANS_AUDIT_SESSION_KEY_FREE, m); + return 0; +} #endif /* SSH_AUDIT_EVENTS */ diff --git a/openssh-6.4p1/monitor.h b/openssh-6.4p1/monitor.h --- a/openssh-6.4p1/monitor.h +++ b/openssh-6.4p1/monitor.h @@ -67,16 +67,17 @@ enum monitor_reqtype { MONITOR_REQ_PAM_INIT_CTX = 104, MONITOR_ANS_PAM_INIT_CTX = 105, MONITOR_REQ_PAM_QUERY = 106, MONITOR_ANS_PAM_QUERY = 107, MONITOR_REQ_PAM_RESPOND = 108, MONITOR_ANS_PAM_RESPOND = 109, MONITOR_REQ_PAM_FREE_CTX = 110, MONITOR_ANS_PAM_FREE_CTX = 111, MONITOR_REQ_AUDIT_EVENT = 112, MONITOR_REQ_AUDIT_COMMAND = 113, MONITOR_ANS_AUDIT_COMMAND = 114, MONITOR_REQ_AUDIT_END_COMMAND = 115, MONITOR_REQ_AUDIT_UNSUPPORTED = 116, MONITOR_ANS_AUDIT_UNSUPPORTED = 117, MONITOR_REQ_AUDIT_KEX = 118, MONITOR_ANS_AUDIT_KEX = 119, + MONITOR_REQ_AUDIT_SESSION_KEY_FREE = 120, MONITOR_ANS_AUDIT_SESSION_KEY_FREE = 121, }; struct mm_master; struct monitor { int m_recvfd; int m_sendfd; int m_log_recvfd; diff --git a/openssh-6.4p1/monitor_wrap.c b/openssh-6.4p1/monitor_wrap.c --- a/openssh-6.4p1/monitor_wrap.c +++ b/openssh-6.4p1/monitor_wrap.c @@ -651,22 +651,24 @@ mm_send_keystate(struct monitor *monitor __func__, packet_get_newkeys(MODE_OUT), packet_get_newkeys(MODE_IN)); /* Keys from Kex */ if (!mm_newkeys_to_blob(MODE_OUT, &blob, &bloblen)) fatal("%s: conversion of newkeys failed", __func__); buffer_put_string(&m, blob, bloblen); + memset(blob, 0, bloblen); free(blob); if (!mm_newkeys_to_blob(MODE_IN, &blob, &bloblen)) fatal("%s: conversion of newkeys failed", __func__); buffer_put_string(&m, blob, bloblen); + memset(blob, 0, bloblen); free(blob); packet_get_state(MODE_OUT, &seqnr, &blocks, &packets, &bytes); buffer_put_int(&m, seqnr); buffer_put_int64(&m, blocks); buffer_put_int(&m, packets); buffer_put_int64(&m, bytes); packet_get_state(MODE_IN, &seqnr, &blocks, &packets, &bytes); @@ -1520,9 +1522,24 @@ mm_audit_kex_body(int ctos, char *cipher buffer_put_int64(&m, uid); mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_KEX, &m); mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_AUDIT_KEX, &m); buffer_free(&m); } + +void +mm_audit_session_key_free_body(int ctos, pid_t pid, uid_t uid) +{ + Buffer m; + + buffer_init(&m); + buffer_put_int(&m, ctos); + buffer_put_int64(&m, pid); + buffer_put_int64(&m, uid); + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_SESSION_KEY_FREE, &m); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_AUDIT_SESSION_KEY_FREE, + &m); + buffer_free(&m); +} #endif /* SSH_AUDIT_EVENTS */ diff --git a/openssh-6.4p1/monitor_wrap.h b/openssh-6.4p1/monitor_wrap.h --- a/openssh-6.4p1/monitor_wrap.h +++ b/openssh-6.4p1/monitor_wrap.h @@ -74,16 +74,17 @@ void mm_sshpam_free_ctx(void *); #ifdef SSH_AUDIT_EVENTS #include "audit.h" void mm_audit_event(ssh_audit_event_t); int mm_audit_run_command(const char *); void mm_audit_end_command(int, const char *); void mm_audit_unsupported_body(int); void mm_audit_kex_body(int, char *, char *, char *, pid_t, uid_t); +void mm_audit_session_key_free_body(int, pid_t, uid_t); #endif struct Session; void mm_terminate(void); int mm_pty_allocate(int *, int *, char *, size_t); void mm_session_pty_cleanup2(struct Session *); /* SSHv1 interfaces */ diff --git a/openssh-6.4p1/packet.c b/openssh-6.4p1/packet.c --- a/openssh-6.4p1/packet.c +++ b/openssh-6.4p1/packet.c @@ -56,16 +56,17 @@ #include #include #include #include #include #include #include "xmalloc.h" +#include "audit.h" #include "buffer.h" #include "packet.h" #include "crc32.h" #include "compress.h" #include "deattack.h" #include "channels.h" #include "compat.h" #include "ssh1.h" @@ -469,41 +470,51 @@ packet_get_connection_in(void) /* Returns the descriptor used for writing. */ int packet_get_connection_out(void) { return active_state->connection_out; } +static int +packet_state_has_keys (const struct session_state *state) +{ + return state != NULL && + (state->newkeys[MODE_IN] != NULL || state->newkeys[MODE_OUT] != NULL); +} + /* Closes the connection and clears and frees internal data structures. */ void packet_close(void) { if (!active_state->initialized) return; active_state->initialized = 0; - if (active_state->connection_in == active_state->connection_out) { - shutdown(active_state->connection_out, SHUT_RDWR); - close(active_state->connection_out); - } else { - close(active_state->connection_in); - close(active_state->connection_out); - } buffer_free(&active_state->input); buffer_free(&active_state->output); buffer_free(&active_state->outgoing_packet); buffer_free(&active_state->incoming_packet); if (active_state->compression_buffer_ready) { buffer_free(&active_state->compression_buffer); buffer_compress_uninit(); } - cipher_cleanup(&active_state->send_context); - cipher_cleanup(&active_state->receive_context); + if (packet_state_has_keys(active_state)) { + cipher_cleanup(&active_state->send_context); + cipher_cleanup(&active_state->receive_context); + audit_session_key_free(2); + } + if (active_state->connection_in == active_state->connection_out) { + shutdown(active_state->connection_out, SHUT_RDWR); + close(active_state->connection_out); + } else { + close(active_state->connection_in); + close(active_state->connection_out); + } } /* Sets remote side protocol flags. */ void packet_set_protocol_flags(u_int protocol_flags) { active_state->remote_protocol_flags = protocol_flags; @@ -728,16 +739,35 @@ packet_send1(void) /* * Note that the packet is now only buffered in output. It won't be * actually sent until packet_write_wait or packet_write_poll is * called. */ } +static void +newkeys_destroy_and_free(Newkeys *newkeys) +{ + if (newkeys == NULL) + return; + + free(newkeys->enc.name); + + mac_clear(&newkeys->mac); + /* MAC may happen to be empty - if the GCM mode of AES is used */ + if (newkeys->mac.name) + free(newkeys->mac.name); + + free(newkeys->comp.name); + + newkeys_destroy(newkeys); + free(newkeys); +} + void set_newkeys(int mode) { Enc *enc; Mac *mac; Comp *comp; CipherContext *cc; u_int64_t *max_blocks; @@ -753,31 +783,19 @@ set_newkeys(int mode) } else { cc = &active_state->receive_context; crypt_type = CIPHER_DECRYPT; active_state->p_read.packets = active_state->p_read.blocks = 0; max_blocks = &active_state->max_blocks_in; } if (active_state->newkeys[mode] != NULL) { debug("set_newkeys: rekeying"); + audit_session_key_free(mode); cipher_cleanup(cc); - enc = &active_state->newkeys[mode]->enc; - mac = &active_state->newkeys[mode]->mac; - comp = &active_state->newkeys[mode]->comp; - mac_clear(mac); - memset(enc->iv, 0, enc->iv_len); - memset(enc->key, 0, enc->key_len); - memset(mac->key, 0, mac->key_len); - free(enc->name); - free(enc->iv); - free(enc->key); - free(mac->name); - free(mac->key); - free(comp->name); - free(active_state->newkeys[mode]); + newkeys_destroy_and_free(active_state->newkeys[mode]); } active_state->newkeys[mode] = kex_get_newkeys(mode); if (active_state->newkeys[mode] == NULL) fatal("newkeys: no keys for mode %d", mode); enc = &active_state->newkeys[mode]->enc; mac = &active_state->newkeys[mode]->mac; comp = &active_state->newkeys[mode]->comp; if (cipher_authlen(enc->cipher) == 0 && mac_init(mac) == 0) @@ -1995,54 +2013,93 @@ packet_get_output(void) } void * packet_get_newkeys(int mode) { return (void *)active_state->newkeys[mode]; } +static void +packet_destroy_state(struct session_state *state) +{ + if (state == NULL) + return; + + cipher_cleanup(&state->receive_context); + cipher_cleanup(&state->send_context); + + buffer_free(&state->input); + buffer_free(&state->output); + buffer_free(&state->outgoing_packet); + buffer_free(&state->incoming_packet); + buffer_free(&state->compression_buffer); + newkeys_destroy_and_free(state->newkeys[MODE_IN]); + state->newkeys[MODE_IN] = NULL; + newkeys_destroy_and_free(state->newkeys[MODE_OUT]); + state->newkeys[MODE_OUT] = NULL; + mac_destroy(state->packet_discard_mac); +// TAILQ_HEAD(, packet) outgoing; +// memset(state, 0, sizeof(state)); +} + +void +packet_destroy_all(int audit_it, int privsep) +{ + if (audit_it) + audit_it = packet_state_has_keys (active_state) || + packet_state_has_keys (backup_state); + packet_destroy_state(active_state); + packet_destroy_state(backup_state); + if (audit_it) { +#ifdef SSH_AUDIT_EVENTS + if (privsep) + audit_session_key_free(2); + else + audit_session_key_free_body(2, getpid(), getuid()); +#endif + } +} + /* * Save the state for the real connection, and use a separate state when * resuming a suspended connection. */ void packet_backup_state(void) { - struct session_state *tmp; - close(active_state->connection_in); active_state->connection_in = -1; close(active_state->connection_out); active_state->connection_out = -1; - if (backup_state) - tmp = backup_state; - else - tmp = alloc_session_state(); backup_state = active_state; - active_state = tmp; + active_state = alloc_session_state(); } /* * Swap in the old state when resuming a connecion. */ void packet_restore_state(void) { struct session_state *tmp; void *buf; u_int len; tmp = backup_state; backup_state = active_state; active_state = tmp; active_state->connection_in = backup_state->connection_in; - backup_state->connection_in = -1; active_state->connection_out = backup_state->connection_out; - backup_state->connection_out = -1; len = buffer_len(&backup_state->input); if (len > 0) { buf = buffer_ptr(&backup_state->input); buffer_append(&active_state->input, buf, len); buffer_clear(&backup_state->input); add_recv_bytes(len); } + backup_state->connection_in = -1; + backup_state->connection_out = -1; + packet_destroy_state(backup_state); + free(backup_state); + backup_state = NULL; } + diff --git a/openssh-6.4p1/packet.h b/openssh-6.4p1/packet.h --- a/openssh-6.4p1/packet.h +++ b/openssh-6.4p1/packet.h @@ -119,9 +119,10 @@ void packet_set_rekey_limits(u_int32_t, time_t packet_get_rekey_timeout(void); void packet_backup_state(void); void packet_restore_state(void); void *packet_get_input(void); void *packet_get_output(void); +void packet_destroy_all(int, int); #endif /* PACKET_H */ diff --git a/openssh-6.4p1/session.c b/openssh-6.4p1/session.c --- a/openssh-6.4p1/session.c +++ b/openssh-6.4p1/session.c @@ -1661,16 +1661,19 @@ do_child(Session *s, const char *command int env_size; char *argv[ARGV_MAX]; const char *shell, *shell0, *hostname = NULL; struct passwd *pw = s->pw; int r = 0; /* remove hostkey from the child's memory */ destroy_sensitive_data(); + /* Don't audit this - both us and the parent would be talking to the + monitor over a single socket, with no synchronization. */ + packet_destroy_all(0, 1); /* Force a password change */ if (s->authctxt->force_pwchange) { do_setusercontext(pw); child_close_fds(); do_pwchange(s); exit(1); } diff --git a/openssh-6.4p1/sshd.c b/openssh-6.4p1/sshd.c --- a/openssh-6.4p1/sshd.c +++ b/openssh-6.4p1/sshd.c @@ -703,16 +703,18 @@ privsep_preauth(Authctxt *authctxt) setproctitle("%s", "[net]"); if (box != NULL) ssh_sandbox_child(box); return 0; } } +extern Newkeys *current_keys[]; + static void privsep_postauth(Authctxt *authctxt) { u_int32_t rnd[256]; #ifdef DISABLE_FD_PASSING if (1) { #else @@ -727,16 +729,20 @@ privsep_postauth(Authctxt *authctxt) monitor_reinit(pmonitor); pmonitor->m_pid = fork(); if (pmonitor->m_pid == -1) fatal("fork of unprivileged child failed"); else if (pmonitor->m_pid != 0) { verbose("User child is on pid %ld", (long)pmonitor->m_pid); buffer_clear(&loginmsg); + newkeys_destroy(current_keys[MODE_OUT]); + newkeys_destroy(current_keys[MODE_IN]); + audit_session_key_free_body(2, getpid(), getuid()); + packet_destroy_all(0, 0); monitor_child_postauth(pmonitor); /* NEVERREACHED */ exit(0); } /* child */ @@ -2089,16 +2095,17 @@ main(int ac, char **av) do_authentication(authctxt); } /* * If we use privilege separation, the unprivileged child transfers * the current keystate and exits */ if (use_privsep) { mm_send_keystate(pmonitor); + packet_destroy_all(1, 1); exit(0); } authenticated: /* * Cancel the alarm we set to limit the time taken for * authentication. */ @@ -2141,16 +2148,18 @@ main(int ac, char **av) packet_set_timeout(options.client_alive_interval, options.client_alive_count_max); /* Start session. */ do_authenticated(authctxt); /* The connection has been terminated. */ + packet_destroy_all(1, 1); + packet_get_state(MODE_IN, NULL, NULL, NULL, &ibytes); packet_get_state(MODE_OUT, NULL, NULL, NULL, &obytes); verbose("Transferred: sent %llu, received %llu bytes", (unsigned long long)obytes, (unsigned long long)ibytes); verbose("Closing connection to %.500s port %d", remote_ip, remote_port); #ifdef USE_PAM @@ -2480,26 +2489,38 @@ do_ssh2_kex(void) #endif debug("KEX done"); } /* server specific fatal cleanup */ void cleanup_exit(int i) { + static int in_cleanup = 0; + int is_privsep_child; + + /* cleanup_exit can be called at the very least from the privsep + wrappers used for auditing. Make sure we don't recurse + indefinitely. */ + if (in_cleanup) + _exit(i); + in_cleanup = 1; + if (the_authctxt) { do_cleanup(the_authctxt); if (use_privsep && privsep_is_preauth && pmonitor->m_pid > 1) { debug("Killing privsep child %d", pmonitor->m_pid); if (kill(pmonitor->m_pid, SIGKILL) != 0 && errno != ESRCH) error("%s: kill(%d): %s", __func__, pmonitor->m_pid, strerror(errno)); } } + is_privsep_child = use_privsep && (pmonitor != NULL) && !mm_is_monitor(); + packet_destroy_all(1, is_privsep_child); #ifdef SSH_AUDIT_EVENTS /* done after do_cleanup so it can cancel the PAM auth 'thread' */ if ((the_authctxt == NULL || !the_authctxt->authenticated) && (!use_privsep || mm_is_monitor())) audit_event(SSH_CONNECTION_ABANDON); #endif _exit(i); }