X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/5e8948af46276093320c3e713c809957b512d082..01dafcb504c448f0d4d5834c21b99e025f492550:/sshd.c diff --git a/sshd.c b/sshd.c index 91376de2..589a1160 100644 --- a/sshd.c +++ b/sshd.c @@ -15,8 +15,10 @@ * called by a name other than "ssh" or "Secure Shell". * * SSH2 implementation: + * Privilege Separation: * - * Copyright (c) 2000 Markus Friedl. All rights reserved. + * Copyright (c) 2000, 2001, 2002 Markus Friedl. All rights reserved. + * Copyright (c) 2002 Niels Provos. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -40,11 +42,16 @@ */ #include "includes.h" -RCSID("$OpenBSD: sshd.c,v 1.216 2001/12/10 16:45:04 stevesk Exp $"); +RCSID("$OpenBSD: sshd.c,v 1.240 2002/04/23 22:16:29 djm Exp $"); #include #include -#include +#include +#include +#ifdef HAVE_SECUREWARE +#include +#include +#endif #include "ssh.h" #include "ssh1.h" @@ -72,6 +79,11 @@ RCSID("$OpenBSD: sshd.c,v 1.216 2001/12/10 16:45:04 stevesk Exp $"); #include "misc.h" #include "dispatch.h" #include "channels.h" +#include "session.h" +#include "monitor_mm.h" +#include "monitor.h" +#include "monitor_wrap.h" +#include "monitor_fdpass.h" #ifdef LIBWRAP #include @@ -189,8 +201,13 @@ u_int utmp_len = MAXHOSTNAMELEN; int *startup_pipes = NULL; int startup_pipe; /* in child */ +/* variables used for privilege separation */ +extern struct monitor *monitor; +extern int use_privsep; + /* Prototypes for various functions defined later in this file. */ void destroy_sensitive_data(void); +void demote_sensitive_data(void); static void do_ssh1_kex(void); static void do_ssh2_kex(void); @@ -263,10 +280,12 @@ sigterm_handler(int sig) static void main_sigchld_handler(int sig) { + pid_t pid; int save_errno = errno; int status; - while (waitpid(-1, &status, WNOHANG) > 0) + while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || + (pid < 0 && errno == EINTR)) ; signal(SIGCHLD, main_sigchld_handler); @@ -399,7 +418,7 @@ sshd_exchange_identification(int sock_in, int sock_out) fatal_cleanup(); } debug("Client protocol version %d.%d; client software version %.100s", - remote_major, remote_minor, remote_version); + remote_major, remote_minor, remote_version); compat_datafellows(remote_version); @@ -467,7 +486,7 @@ destroy_sensitive_data(void) key_free(sensitive_data.server_key); sensitive_data.server_key = NULL; } - for(i = 0; i < options.num_host_key_files; i++) { + for (i = 0; i < options.num_host_key_files; i++) { if (sensitive_data.host_keys[i]) { key_free(sensitive_data.host_keys[i]); sensitive_data.host_keys[i] = NULL; @@ -477,36 +496,194 @@ destroy_sensitive_data(void) memset(sensitive_data.ssh1_cookie, 0, SSH_SESSION_KEY_LENGTH); } +/* Demote private to public keys for network child */ +void +demote_sensitive_data(void) +{ + Key *tmp; + int i; + + if (sensitive_data.server_key) { + tmp = key_demote(sensitive_data.server_key); + key_free(sensitive_data.server_key); + sensitive_data.server_key = tmp; + } + + for (i = 0; i < options.num_host_key_files; i++) { + if (sensitive_data.host_keys[i]) { + tmp = key_demote(sensitive_data.host_keys[i]); + key_free(sensitive_data.host_keys[i]); + sensitive_data.host_keys[i] = tmp; + if (tmp->type == KEY_RSA1) + sensitive_data.ssh1_host_key = tmp; + } + } + + /* We do not clear ssh1_host key and cookie. XXX - Okay Niels? */ +} + +static void +privsep_preauth_child(void) +{ + u_int32_t rand[256]; + int i; + struct passwd *pw; + + /* Enable challenge-response authentication for privilege separation */ + privsep_challenge_enable(); + + for (i = 0; i < 256; i++) + rand[i] = arc4random(); + RAND_seed(rand, sizeof(rand)); + + /* Demote the private keys to public keys. */ + demote_sensitive_data(); + + if ((pw = getpwnam(SSH_PRIVSEP_USER)) == NULL) + fatal("Privilege separation user %s does not exist", + SSH_PRIVSEP_USER); + memset(pw->pw_passwd, 0, strlen(pw->pw_passwd)); + endpwent(); + + /* Change our root directory*/ + if (chroot(_PATH_PRIVSEP_CHROOT_DIR) == -1) + fatal("chroot(\"%s\"): %s", _PATH_PRIVSEP_CHROOT_DIR, + strerror(errno)); + if (chdir("/") == -1) + fatal("chdir(\"/\"): %s", strerror(errno)); + + /* Drop our privileges */ + debug3("privsep user:group %u:%u", (u_int)pw->pw_uid, + (u_int)pw->pw_gid); + do_setusercontext(pw); +} + +static Authctxt* +privsep_preauth(void) +{ + Authctxt *authctxt = NULL; + int status; + pid_t pid; + + /* Set up unprivileged child process to deal with network data */ + monitor = monitor_init(); + /* Store a pointer to the kex for later rekeying */ + monitor->m_pkex = &xxx_kex; + + pid = fork(); + if (pid == -1) { + fatal("fork of unprivileged child failed"); + } else if (pid != 0) { + debug2("Network child is on pid %d", pid); + + close(monitor->m_recvfd); + authctxt = monitor_child_preauth(monitor); + close(monitor->m_sendfd); + + /* Sync memory */ + monitor_sync(monitor); + + /* Wait for the child's exit status */ + while (waitpid(pid, &status, 0) < 0) + if (errno != EINTR) + break; + return (authctxt); + } else { + /* child */ + + close(monitor->m_sendfd); + + /* Demote the child */ + if (getuid() == 0 || geteuid() == 0) + privsep_preauth_child(); + setproctitle("%s", "[net]"); + } + return (NULL); +} + +static void +privsep_postauth(Authctxt *authctxt) +{ + extern Authctxt *x_authctxt; + + /* XXX - Remote port forwarding */ + x_authctxt = authctxt; + + if (authctxt->pw->pw_uid == 0 || options.use_login) { + /* File descriptor passing is broken or root login */ + monitor_apply_keystate(monitor); + use_privsep = 0; + return; + } + + /* Authentication complete */ + alarm(0); + if (startup_pipe != -1) { + close(startup_pipe); + startup_pipe = -1; + } + + /* New socket pair */ + monitor_reinit(monitor); + + monitor->m_pid = fork(); + if (monitor->m_pid == -1) + fatal("fork of unprivileged child failed"); + else if (monitor->m_pid != 0) { + debug2("User child is on pid %d", monitor->m_pid); + close(monitor->m_recvfd); + monitor_child_postauth(monitor); + + /* NEVERREACHED */ + exit(0); + } + + close(monitor->m_sendfd); + + /* Demote the private keys to public keys. */ + demote_sensitive_data(); + + /* Drop privileges */ + do_setusercontext(authctxt->pw); + + /* It is safe now to apply the key state */ + monitor_apply_keystate(monitor); +} + static char * list_hostkey_types(void) { - static char buf[1024]; + Buffer b; + char *p; int i; - buf[0] = '\0'; - for(i = 0; i < options.num_host_key_files; i++) { + + buffer_init(&b); + for (i = 0; i < options.num_host_key_files; i++) { Key *key = sensitive_data.host_keys[i]; if (key == NULL) continue; switch (key->type) { case KEY_RSA: case KEY_DSA: - strlcat(buf, key_ssh_name(key), sizeof buf); - strlcat(buf, ",", sizeof buf); + if (buffer_len(&b) > 0) + buffer_append(&b, ",", 1); + p = key_ssh_name(key); + buffer_append(&b, p, strlen(p)); break; } } - i = strlen(buf); - if (i > 0 && buf[i-1] == ',') - buf[i-1] = '\0'; - debug("list_hostkey_types: %s", buf); - return buf; + buffer_append(&b, "\0", 1); + p = xstrdup(buffer_ptr(&b)); + buffer_free(&b); + debug("list_hostkey_types: %s", p); + return p; } -static Key * +Key * get_hostkey_by_type(int type) { int i; - for(i = 0; i < options.num_host_key_files; i++) { + for (i = 0; i < options.num_host_key_files; i++) { Key *key = sensitive_data.host_keys[i]; if (key != NULL && key->type == type) return key; @@ -514,6 +691,25 @@ get_hostkey_by_type(int type) return NULL; } +Key * +get_hostkey_by_index(int ind) +{ + if (ind < 0 || ind >= options.num_host_key_files) + return (NULL); + return (sensitive_data.host_keys[ind]); +} + +int +get_hostkey_index(Key *key) +{ + int i; + for (i = 0; i < options.num_host_key_files; i++) { + if (key == sensitive_data.host_keys[i]) + return (i); + } + return (-1); +} + /* * returns 1 if connection should be dropped, 0 otherwise. * dropping starts at connection #max_startups_begin with a probability @@ -590,9 +786,13 @@ main(int ac, char **av) int listen_sock, maxfd; int startup_p[2]; int startups = 0; + Authctxt *authctxt; Key *key; int ret, key_used = 0; +#ifdef HAVE_SECUREWARE + (void)set_auth_parameters(ac, av); +#endif __progname = get_progname(av[0]); init_rng(); @@ -687,9 +887,9 @@ main(int ac, char **av) utmp_len = atoi(optarg); break; case 'o': - if (process_server_config_line(&options, optarg, + if (process_server_config_line(&options, optarg, "command-line", 0) != 0) - exit(1); + exit(1); break; case '?': default: @@ -705,8 +905,10 @@ main(int ac, char **av) * key (unless started from inetd) */ log_init(__progname, - options.log_level == -1 ? SYSLOG_LEVEL_INFO : options.log_level, - options.log_facility == -1 ? SYSLOG_FACILITY_AUTH : options.log_facility, + options.log_level == SYSLOG_LEVEL_NOT_SET ? + SYSLOG_LEVEL_INFO : options.log_level, + options.log_facility == SYSLOG_FACILITY_NOT_SET ? + SYSLOG_FACILITY_AUTH : options.log_facility, !inetd_flag); #ifdef _CRAY @@ -734,14 +936,14 @@ main(int ac, char **av) /* load private host keys */ sensitive_data.host_keys = xmalloc(options.num_host_key_files*sizeof(Key*)); - for(i = 0; i < options.num_host_key_files; i++) + for (i = 0; i < options.num_host_key_files; i++) sensitive_data.host_keys[i] = NULL; sensitive_data.server_key = NULL; sensitive_data.ssh1_host_key = NULL; sensitive_data.have_ssh1_key = 0; sensitive_data.have_ssh2_key = 0; - for(i = 0; i < options.num_host_key_files; i++) { + for (i = 0; i < options.num_host_key_files; i++) { key = key_load_private(options.host_key_files[i], "", NULL); sensitive_data.host_keys[i] = key; if (key == NULL) { @@ -803,9 +1005,15 @@ main(int ac, char **av) if (test_flag) exit(0); -#ifdef HAVE_SCO_PROTECTED_PW - (void) set_auth_parameters(ac, av); -#endif + /* + * Clear out any supplemental groups we may have inherited. This + * prevents inadvertent creation of files with bad modes (in the + * portable version at least, it's certainly possible for PAM + * to create a file, and we can't control the code in every + * module which might be used). + */ + if (setgroups(0, NULL) < 0) + debug("setgroups() failed: %.200s", strerror(errno)); /* Initialize the log (it is reinitialized below in case we forked). */ if (debug_flag && !inetd_flag) @@ -842,7 +1050,7 @@ main(int ac, char **av) /* Chdir to the root directory so that the current disk can be unmounted if desired. */ chdir("/"); - + /* ignore SIGPIPE */ signal(SIGPIPE, SIG_IGN); @@ -894,11 +1102,11 @@ main(int ac, char **av) * close. */ setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, - (void *) &on, sizeof(on)); + &on, sizeof(on)); linger.l_onoff = 1; linger.l_linger = 5; setsockopt(listen_sock, SOL_SOCKET, SO_LINGER, - (void *) &linger, sizeof(linger)); + &linger, sizeof(linger)); debug("Bind to port %s on %s.", strport, ntop); @@ -1144,11 +1352,11 @@ main(int ac, char **av) /* setsockopt(sock_in, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)); */ linger.l_onoff = 1; linger.l_linger = 5; - setsockopt(sock_in, SOL_SOCKET, SO_LINGER, (void *) &linger, sizeof(linger)); + setsockopt(sock_in, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)); /* Set keepalives if requested. */ if (options.keepalives && - setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, + setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) < 0) error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); @@ -1201,8 +1409,9 @@ main(int ac, char **av) * machine, he can connect from any port. So do not use these * authentication methods from machines that you do not trust. */ - if (remote_port >= IPPORT_RESERVED || - remote_port < IPPORT_RESERVED / 2) { + if (options.rhosts_authentication && + (remote_port >= IPPORT_RESERVED || + remote_port < IPPORT_RESERVED / 2)) { debug("Rhosts Authentication disabled, " "originating port %d not trusted.", remote_port); options.rhosts_authentication = 0; @@ -1224,15 +1433,43 @@ main(int ac, char **av) packet_set_nonblocking(); + if (use_privsep) + if ((authctxt = privsep_preauth()) != NULL) + goto authenticated; + /* perform the key exchange */ /* authenticate user and start session */ if (compat20) { do_ssh2_kex(); - do_authentication2(); + authctxt = do_authentication2(); } else { do_ssh1_kex(); - do_authentication(); + authctxt = do_authentication(); + } + /* + * If we use privilege separation, the unprivileged child transfers + * the current keystate and exits + */ + if (use_privsep) { + mm_send_keystate(monitor); + exit(0); } + + authenticated: + /* + * In privilege separation, we fork another child and prepare + * file descriptor passing. + */ + if (use_privsep) { + privsep_postauth(authctxt); + /* the monitor process [priv] will not return */ + if (!compat20) + destroy_sensitive_data(); + } + + /* Perform session preparation. */ + do_authenticated(authctxt); + /* The connection has been terminated. */ verbose("Closing connection to %.100s", remote_ip); @@ -1241,9 +1478,57 @@ main(int ac, char **av) #endif /* USE_PAM */ packet_close(); + + if (use_privsep) + mm_terminate(); + exit(0); } +/* + * Decrypt session_key_int using our private server key and private host key + * (key with larger modulus first). + */ +int +ssh1_session_key(BIGNUM *session_key_int) +{ + int rsafail = 0; + + if (BN_cmp(sensitive_data.server_key->rsa->n, sensitive_data.ssh1_host_key->rsa->n) > 0) { + /* Server key has bigger modulus. */ + if (BN_num_bits(sensitive_data.server_key->rsa->n) < + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + SSH_KEY_BITS_RESERVED) { + fatal("do_connection: %s: server_key %d < host_key %d + SSH_KEY_BITS_RESERVED %d", + get_remote_ipaddr(), + BN_num_bits(sensitive_data.server_key->rsa->n), + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), + SSH_KEY_BITS_RESERVED); + } + if (rsa_private_decrypt(session_key_int, session_key_int, + sensitive_data.server_key->rsa) <= 0) + rsafail++; + if (rsa_private_decrypt(session_key_int, session_key_int, + sensitive_data.ssh1_host_key->rsa) <= 0) + rsafail++; + } else { + /* Host key has bigger modulus (or they are equal). */ + if (BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) < + BN_num_bits(sensitive_data.server_key->rsa->n) + SSH_KEY_BITS_RESERVED) { + fatal("do_connection: %s: host_key %d < server_key %d + SSH_KEY_BITS_RESERVED %d", + get_remote_ipaddr(), + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), + BN_num_bits(sensitive_data.server_key->rsa->n), + SSH_KEY_BITS_RESERVED); + } + if (rsa_private_decrypt(session_key_int, session_key_int, + sensitive_data.ssh1_host_key->rsa) < 0) + rsafail++; + if (rsa_private_decrypt(session_key_int, session_key_int, + sensitive_data.server_key->rsa) < 0) + rsafail++; + } + return (rsafail); +} /* * SSH1 key exchange */ @@ -1251,7 +1536,6 @@ static void do_ssh1_kex(void) { int i, len; - int plen, slen; int rsafail = 0; BIGNUM *session_key_int; u_char session_key[SSH_SESSION_KEY_LENGTH]; @@ -1335,7 +1619,7 @@ do_ssh1_kex(void) BN_num_bits(sensitive_data.ssh1_host_key->rsa->n)); /* Read clients reply (cipher type and session key). */ - packet_read_expect(&plen, SSH_CMSG_SESSION_KEY); + packet_read_expect(SSH_CMSG_SESSION_KEY); /* Get cipher type and check whether we accept this. */ cipher_type = packet_get_char(); @@ -1352,51 +1636,17 @@ do_ssh1_kex(void) debug("Encryption type: %.200s", cipher_name(cipher_type)); /* Get the encrypted integer. */ - session_key_int = BN_new(); - packet_get_bignum(session_key_int, &slen); + if ((session_key_int = BN_new()) == NULL) + fatal("do_ssh1_kex: BN_new failed"); + packet_get_bignum(session_key_int); protocol_flags = packet_get_int(); packet_set_protocol_flags(protocol_flags); + packet_check_eom(); - packet_integrity_check(plen, 1 + 8 + slen + 4, SSH_CMSG_SESSION_KEY); + /* Decrypt session_key_int using host/server keys */ + rsafail = PRIVSEP(ssh1_session_key(session_key_int)); - /* - * Decrypt it using our private server key and private host key (key - * with larger modulus first). - */ - if (BN_cmp(sensitive_data.server_key->rsa->n, sensitive_data.ssh1_host_key->rsa->n) > 0) { - /* Server key has bigger modulus. */ - if (BN_num_bits(sensitive_data.server_key->rsa->n) < - BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + SSH_KEY_BITS_RESERVED) { - fatal("do_connection: %s: server_key %d < host_key %d + SSH_KEY_BITS_RESERVED %d", - get_remote_ipaddr(), - BN_num_bits(sensitive_data.server_key->rsa->n), - BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), - SSH_KEY_BITS_RESERVED); - } - if (rsa_private_decrypt(session_key_int, session_key_int, - sensitive_data.server_key->rsa) <= 0) - rsafail++; - if (rsa_private_decrypt(session_key_int, session_key_int, - sensitive_data.ssh1_host_key->rsa) <= 0) - rsafail++; - } else { - /* Host key has bigger modulus (or they are equal). */ - if (BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) < - BN_num_bits(sensitive_data.server_key->rsa->n) + SSH_KEY_BITS_RESERVED) { - fatal("do_connection: %s: host_key %d < server_key %d + SSH_KEY_BITS_RESERVED %d", - get_remote_ipaddr(), - BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), - BN_num_bits(sensitive_data.server_key->rsa->n), - SSH_KEY_BITS_RESERVED); - } - if (rsa_private_decrypt(session_key_int, session_key_int, - sensitive_data.ssh1_host_key->rsa) < 0) - rsafail++; - if (rsa_private_decrypt(session_key_int, session_key_int, - sensitive_data.server_key->rsa) < 0) - rsafail++; - } /* * Extract session key from the decrypted integer. The key is in the * least significant 256 bits of the integer; the first byte of the @@ -1428,7 +1678,7 @@ do_ssh1_kex(void) } if (rsafail) { int bytes = BN_num_bytes(session_key_int); - char *buf = xmalloc(bytes); + u_char *buf = xmalloc(bytes); MD5_CTX md; log("do_connection: generating a fake encryption key"); @@ -1447,9 +1697,12 @@ do_ssh1_kex(void) for (i = 0; i < 16; i++) session_id[i] = session_key[i] ^ session_key[i + 16]; } - /* Destroy the private and public keys. They will no longer be needed. */ + /* Destroy the private and public keys. No longer. */ destroy_sensitive_data(); + if (use_privsep) + mm_ssh1_session_id(session_id); + /* Destroy the decrypted integer. It is no longer needed. */ BN_clear_free(session_key_int); @@ -1496,6 +1749,7 @@ do_ssh2_kex(void) kex->client_version_string=client_version_string; kex->server_version_string=server_version_string; kex->load_host_key=&get_hostkey_by_type; + kex->host_key_index=&get_hostkey_index; xxx_kex = kex;