1262566Sdes/* $OpenBSD: uidswap.c,v 1.36 2013/11/08 11:15:19 dtucker Exp $ */ 257429Smarkm/* 357429Smarkm * Author: Tatu Ylonen <ylo@cs.hut.fi> 457429Smarkm * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 557429Smarkm * All rights reserved 657429Smarkm * Code for uid-swapping. 765668Skris * 865668Skris * As far as I am concerned, the code I have written for this software 965668Skris * can be used freely for any purpose. Any derived versions of this 1065668Skris * software must be clearly marked as such, and if the derived work is 1165668Skris * incompatible with the protocol description in the RFC file, it must be 1265668Skris * called by a name other than "ssh" or "Secure Shell". 1357429Smarkm */ 1457429Smarkm 1557429Smarkm#include "includes.h" 1657429Smarkm 17162852Sdes#include <sys/param.h> 18162852Sdes#include <errno.h> 19162852Sdes#include <pwd.h> 20162852Sdes#include <string.h> 21162852Sdes#include <unistd.h> 22162852Sdes#include <stdarg.h> 23262566Sdes#include <stdlib.h> 24162852Sdes 25162852Sdes#include <grp.h> 26162852Sdes 2776259Sgreen#include "log.h" 2857429Smarkm#include "uidswap.h" 29126274Sdes#include "xmalloc.h" 3057429Smarkm 3157429Smarkm/* 3257429Smarkm * Note: all these functions must work in all of the following cases: 3357429Smarkm * 1. euid=0, ruid=0 3457429Smarkm * 2. euid=0, ruid!=0 3557429Smarkm * 3. euid!=0, ruid!=0 3657429Smarkm * Additionally, they must work regardless of whether the system has 3757429Smarkm * POSIX saved uids or not. 3857429Smarkm */ 3957429Smarkm 4098937Sdes#if defined(_POSIX_SAVED_IDS) && !defined(BROKEN_SAVED_UIDS) 4157429Smarkm/* Lets assume that posix saved ids also work with seteuid, even though that 4257429Smarkm is not part of the posix specification. */ 4398937Sdes#define SAVED_IDS_WORK_WITH_SETEUID 4498937Sdes/* Saved effective uid. */ 4598937Sdesstatic uid_t saved_euid = 0; 4698937Sdesstatic gid_t saved_egid = 0; 4798937Sdes#endif 4857429Smarkm 4957429Smarkm/* Saved effective uid. */ 5076259Sgreenstatic int privileged = 0; 5176259Sgreenstatic int temporarily_use_uid_effective = 0; 52126274Sdesstatic gid_t *saved_egroups = NULL, *user_groups = NULL; 5376259Sgreenstatic int saved_egroupslen = -1, user_groupslen = -1; 5457429Smarkm 5557429Smarkm/* 5657429Smarkm * Temporarily changes to the given uid. If the effective user 5757429Smarkm * id is not root, this does nothing. This call cannot be nested. 5857429Smarkm */ 5960573Skrisvoid 6076259Sgreentemporarily_use_uid(struct passwd *pw) 6157429Smarkm{ 6276259Sgreen /* Save the current euid, and egroups. */ 6398937Sdes#ifdef SAVED_IDS_WORK_WITH_SETEUID 6457429Smarkm saved_euid = geteuid(); 6598937Sdes saved_egid = getegid(); 66106121Sdes debug("temporarily_use_uid: %u/%u (e=%u/%u)", 67106121Sdes (u_int)pw->pw_uid, (u_int)pw->pw_gid, 68106121Sdes (u_int)saved_euid, (u_int)saved_egid); 69146998Sdes#ifndef HAVE_CYGWIN 7076259Sgreen if (saved_euid != 0) { 7176259Sgreen privileged = 0; 7276259Sgreen return; 7376259Sgreen } 74146998Sdes#endif 7598937Sdes#else 7698937Sdes if (geteuid() != 0) { 7798937Sdes privileged = 0; 7898937Sdes return; 7998937Sdes } 8098937Sdes#endif /* SAVED_IDS_WORK_WITH_SETEUID */ 8198937Sdes 8276259Sgreen privileged = 1; 8376259Sgreen temporarily_use_uid_effective = 1; 84126274Sdes 85126274Sdes saved_egroupslen = getgroups(0, NULL); 8676259Sgreen if (saved_egroupslen < 0) 8776259Sgreen fatal("getgroups: %.100s", strerror(errno)); 88126274Sdes if (saved_egroupslen > 0) { 89126274Sdes saved_egroups = xrealloc(saved_egroups, 90162852Sdes saved_egroupslen, sizeof(gid_t)); 91126274Sdes if (getgroups(saved_egroupslen, saved_egroups) < 0) 92126274Sdes fatal("getgroups: %.100s", strerror(errno)); 93126274Sdes } else { /* saved_egroupslen == 0 */ 94255767Sdes free(saved_egroups); 95126274Sdes } 9657429Smarkm 9776259Sgreen /* set and save the user's groups */ 9876259Sgreen if (user_groupslen == -1) { 9976259Sgreen if (initgroups(pw->pw_name, pw->pw_gid) < 0) 10076259Sgreen fatal("initgroups: %s: %.100s", pw->pw_name, 10176259Sgreen strerror(errno)); 102126274Sdes 103126274Sdes user_groupslen = getgroups(0, NULL); 10476259Sgreen if (user_groupslen < 0) 10576259Sgreen fatal("getgroups: %.100s", strerror(errno)); 106126274Sdes if (user_groupslen > 0) { 107126274Sdes user_groups = xrealloc(user_groups, 108162852Sdes user_groupslen, sizeof(gid_t)); 109126274Sdes if (getgroups(user_groupslen, user_groups) < 0) 110126274Sdes fatal("getgroups: %.100s", strerror(errno)); 111126274Sdes } else { /* user_groupslen == 0 */ 112255767Sdes free(user_groups); 113126274Sdes } 11476259Sgreen } 11557429Smarkm /* Set the effective uid to the given (unprivileged) uid. */ 11676259Sgreen if (setgroups(user_groupslen, user_groups) < 0) 11776259Sgreen fatal("setgroups: %.100s", strerror(errno)); 11898937Sdes#ifndef SAVED_IDS_WORK_WITH_SETEUID 11998937Sdes /* Propagate the privileged gid to all of our gids. */ 12098937Sdes if (setgid(getegid()) < 0) 12198937Sdes debug("setgid %u: %.100s", (u_int) getegid(), strerror(errno)); 12298937Sdes /* Propagate the privileged uid to all of our uids. */ 12398937Sdes if (setuid(geteuid()) < 0) 12498937Sdes debug("setuid %u: %.100s", (u_int) geteuid(), strerror(errno)); 12598937Sdes#endif /* SAVED_IDS_WORK_WITH_SETEUID */ 12676259Sgreen if (setegid(pw->pw_gid) < 0) 12798675Sdes fatal("setegid %u: %.100s", (u_int)pw->pw_gid, 12876259Sgreen strerror(errno)); 12976259Sgreen if (seteuid(pw->pw_uid) == -1) 13098675Sdes fatal("seteuid %u: %.100s", (u_int)pw->pw_uid, 13176259Sgreen strerror(errno)); 13257429Smarkm} 13357429Smarkm 134162852Sdesvoid 135162852Sdespermanently_drop_suid(uid_t uid) 136162852Sdes{ 137262566Sdes#ifndef HAVE_CYGWIN 138162852Sdes uid_t old_uid = getuid(); 139262566Sdes#endif 140162852Sdes 141162852Sdes debug("permanently_drop_suid: %u", (u_int)uid); 142162852Sdes if (setresuid(uid, uid, uid) < 0) 143162852Sdes fatal("setresuid %u: %.100s", (u_int)uid, strerror(errno)); 144162852Sdes 145162852Sdes#ifndef HAVE_CYGWIN 146162852Sdes /* Try restoration of UID if changed (test clearing of saved uid) */ 147162852Sdes if (old_uid != uid && 148162852Sdes (setuid(old_uid) != -1 || seteuid(old_uid) != -1)) 149162852Sdes fatal("%s: was able to restore old [e]uid", __func__); 150162852Sdes#endif 151162852Sdes 152162852Sdes /* Verify UID drop was successful */ 153162852Sdes if (getuid() != uid || geteuid() != uid) { 154162852Sdes fatal("%s: euid incorrect uid:%u euid:%u (should be %u)", 155162852Sdes __func__, (u_int)getuid(), (u_int)geteuid(), (u_int)uid); 156162852Sdes } 157162852Sdes} 158162852Sdes 15957429Smarkm/* 16076259Sgreen * Restores to the original (privileged) uid. 16157429Smarkm */ 16260573Skrisvoid 16376259Sgreenrestore_uid(void) 16457429Smarkm{ 16576259Sgreen /* it's a no-op unless privileged */ 166106121Sdes if (!privileged) { 167106121Sdes debug("restore_uid: (unprivileged)"); 16876259Sgreen return; 169106121Sdes } 17076259Sgreen if (!temporarily_use_uid_effective) 17176259Sgreen fatal("restore_uid: temporarily_use_uid not effective"); 17298937Sdes 17398937Sdes#ifdef SAVED_IDS_WORK_WITH_SETEUID 174106121Sdes debug("restore_uid: %u/%u", (u_int)saved_euid, (u_int)saved_egid); 17576259Sgreen /* Set the effective uid back to the saved privileged uid. */ 17657429Smarkm if (seteuid(saved_euid) < 0) 17798675Sdes fatal("seteuid %u: %.100s", (u_int)saved_euid, strerror(errno)); 17898937Sdes if (setegid(saved_egid) < 0) 17998937Sdes fatal("setegid %u: %.100s", (u_int)saved_egid, strerror(errno)); 18098937Sdes#else /* SAVED_IDS_WORK_WITH_SETEUID */ 18198937Sdes /* 18298937Sdes * We are unable to restore the real uid to its unprivileged value. 18398937Sdes * Propagate the real uid (usually more privileged) to effective uid 18498937Sdes * as well. 18598937Sdes */ 18698937Sdes setuid(getuid()); 18798937Sdes setgid(getgid()); 18898937Sdes#endif /* SAVED_IDS_WORK_WITH_SETEUID */ 18998937Sdes 19076259Sgreen if (setgroups(saved_egroupslen, saved_egroups) < 0) 19176259Sgreen fatal("setgroups: %.100s", strerror(errno)); 19276259Sgreen temporarily_use_uid_effective = 0; 19357429Smarkm} 19457429Smarkm 19557429Smarkm/* 19657429Smarkm * Permanently sets all uids to the given uid. This cannot be 19757429Smarkm * called while temporarily_use_uid is effective. 19857429Smarkm */ 19960573Skrisvoid 20076259Sgreenpermanently_set_uid(struct passwd *pw) 20157429Smarkm{ 202262566Sdes#ifndef HAVE_CYGWIN 203124208Sdes uid_t old_uid = getuid(); 204124208Sdes gid_t old_gid = getgid(); 205262566Sdes#endif 206124208Sdes 207162852Sdes if (pw == NULL) 208162852Sdes fatal("permanently_set_uid: no user given"); 20976259Sgreen if (temporarily_use_uid_effective) 21098675Sdes fatal("permanently_set_uid: temporarily_use_uid effective"); 211106121Sdes debug("permanently_set_uid: %u/%u", (u_int)pw->pw_uid, 212106121Sdes (u_int)pw->pw_gid); 213124208Sdes 214124208Sdes if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) < 0) 215124208Sdes fatal("setresgid %u: %.100s", (u_int)pw->pw_gid, strerror(errno)); 216124208Sdes 217192595Sdes#ifdef __APPLE__ 218192595Sdes /* 219192595Sdes * OS X requires initgroups after setgid to opt back into 220192595Sdes * memberd support for >16 supplemental groups. 221192595Sdes */ 222192595Sdes if (initgroups(pw->pw_name, pw->pw_gid) < 0) 223192595Sdes fatal("initgroups %.100s %u: %.100s", 224192595Sdes pw->pw_name, (u_int)pw->pw_gid, strerror(errno)); 225192595Sdes#endif 226192595Sdes 227124208Sdes if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) < 0) 228124208Sdes fatal("setresuid %u: %.100s", (u_int)pw->pw_uid, strerror(errno)); 229124208Sdes 230146998Sdes#ifndef HAVE_CYGWIN 231124208Sdes /* Try restoration of GID if changed (test clearing of saved gid) */ 232146998Sdes if (old_gid != pw->pw_gid && pw->pw_uid != 0 && 233124208Sdes (setgid(old_gid) != -1 || setegid(old_gid) != -1)) 234124208Sdes fatal("%s: was able to restore old [e]gid", __func__); 235146998Sdes#endif 236124208Sdes 237124208Sdes /* Verify GID drop was successful */ 238124208Sdes if (getgid() != pw->pw_gid || getegid() != pw->pw_gid) { 239126274Sdes fatal("%s: egid incorrect gid:%u egid:%u (should be %u)", 240126274Sdes __func__, (u_int)getgid(), (u_int)getegid(), 241124208Sdes (u_int)pw->pw_gid); 242124208Sdes } 243124208Sdes 244124208Sdes#ifndef HAVE_CYGWIN 245124208Sdes /* Try restoration of UID if changed (test clearing of saved uid) */ 246126274Sdes if (old_uid != pw->pw_uid && 247124208Sdes (setuid(old_uid) != -1 || seteuid(old_uid) != -1)) 248124208Sdes fatal("%s: was able to restore old [e]uid", __func__); 249124208Sdes#endif 250124208Sdes 251124208Sdes /* Verify UID drop was successful */ 252124208Sdes if (getuid() != pw->pw_uid || geteuid() != pw->pw_uid) { 253126274Sdes fatal("%s: euid incorrect uid:%u euid:%u (should be %u)", 254126274Sdes __func__, (u_int)getuid(), (u_int)geteuid(), 255124208Sdes (u_int)pw->pw_uid); 256124208Sdes } 25757429Smarkm} 258