From d7d40e3420ad8c1d6fba1be6ef26705502a64a20 Mon Sep 17 00:00:00 2001 From: Tomasz bla Fortuna Date: Thu, 24 Dec 2009 00:12:48 +0100 Subject: [PATCH] UID mangling functions added. --- CMakeLists.txt | 11 ++- ChangeLog | 1 + tests/lock.c | 90 +++++++++++++++++++++++ tests/suid.c | 153 +++++++++++++++++++++++++++++++++++++++ utility/otpasswd.c | 55 ++++---------- utility/security.c | 176 +++++++++++++++++++++++++++++++++++++++++++++ utility/security.h | 34 +++++++++ 7 files changed, 479 insertions(+), 41 deletions(-) create mode 100644 tests/lock.c create mode 100644 tests/suid.c create mode 100644 utility/security.c create mode 100644 utility/security.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e8601b8..591a859 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,15 @@ SET(${PROJECT_NAME}_MINOR_VERSION 1) ADD_DEFINITIONS("-Wall -ggdb") +# Detect system +IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + ADD_DEFINITIONS("-DOS_LINUX") +ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + +IF(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + ADD_DEFINITIONS("-OS_FREEBSD") +ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + # Detect include dirs FIND_PATH(GMP_INCLUDE_DIR gmp.h /usr/include/gmp /usr/local/include/gmp) FIND_PATH(PAM_INCLUDE_DIR pam_modules.h /usr/include/security /usr/include/pam) @@ -60,7 +69,7 @@ ADD_LIBRARY(pam_otpasswd SHARED pam/pam_helpers.c pam/pam_otpasswd.c) TARGET_LINK_LIBRARIES(pam_otpasswd libotp gmp ssl pam) # Password management target -ADD_EXECUTABLE(otpasswd utility/otpasswd.c utility/otpasswd_actions.c utility/testcases.c) +ADD_EXECUTABLE(otpasswd utility/otpasswd.c utility/otpasswd_actions.c utility/testcases.c utility/security.c) TARGET_LINK_LIBRARIES(otpasswd libotp gmp ssl) ## diff --git a/ChangeLog b/ChangeLog index e6bef8e..e1426b5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -40,6 +40,7 @@ Trying to sort tasks according to their priority. to conversation function. If not, we must build a buffer (See for example how winscp shows that warning) * [?] Use PAM_SERVICE_ERR + * [?] Check if OOB script is not SUID? * [-] right trim values from config? Low-priority: diff --git a/tests/lock.c b/tests/lock.c new file mode 100644 index 0000000..2d75e9e --- /dev/null +++ b/tests/lock.c @@ -0,0 +1,90 @@ +#include +#include +#include +#include +#include +#include +#include + +#define LOCK_FILE ".otpasswd.lck" +int fd = -1; + +int lock() +{ + struct flock fl; + + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = fl.l_len = 0; + + fd = open(LOCK_FILE, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR); + + if (fd == -1) { + /* Unable to create file, therefore unable to obtain lock */ + perror("open"); + printf("Unable to open a state file\n"); + return 1; + } + + if (fcntl(fd, F_SETLK, &fl) == 0) { + printf("Locked\n"); + return 0; + } + + close(fd); + printf("Unable to lock\n"); + return 1; +} + +void overwrite() +{ + FILE *f = fopen(LOCK_FILE, "w"); + if (!f) { + printf("Unable to open for overwrite\n"); + return; + } + fprintf(f, "Dupablada\n"); + if (fflush(f) != 0) { + printf("Unable to fflush\n"); + } + fclose(f); +} + +int unlock() +{ + struct flock fl; + + if (fd < 0) { + printf("No lock to release!\n"); + return 1; + } + + fl.l_type = F_UNLCK; + fl.l_whence = SEEK_SET; + fl.l_start = fl.l_len = 0; + + int ret = fcntl(fd, F_SETLK, &fl); + + close(fd); + fd = -1; + + if (ret != 0) { + printf("Strange error while releasing lock\n"); + /* Strange error while releasing the lock */ + return 1; + } + + return 0; +} + +int main(int argc, char **argv) +{ + if (lock() != 0) { + return 1; + } + +// overwrite(); + printf("Waiting for keypress\n"); getchar(); + unlock(); + return 0; +} diff --git a/tests/suid.c b/tests/suid.c new file mode 100644 index 0000000..6d5ec16 --- /dev/null +++ b/tests/suid.c @@ -0,0 +1,153 @@ + +#define _GNU_SOURCE /* For setres* */ + +#include +#include + +#include + +static int real_uid; +static int set_uid; + +static int real_gid; +static int set_gid; + +void init() +{ + real_uid = getuid(); + set_uid = geteuid(); + + real_gid = getgid(); + set_gid = getegid(); +} + +void print(void) +{ + int uid, euid; + int gid, egid; + + uid = getuid(); + euid = geteuid(); + + gid = getgid(); + egid = getegid(); + + printf("UID/eUID: %d/%d GID/eGID: %d/%d\n", uid, euid, gid, egid); +} + + +void check_perms(void) +{ + printf("Checking root perms! "); + print(); + + FILE *f = fopen("/etc/shadow", "r"); + if (!f) { + printf("We do not have root access permissions\n"); + } else { + fclose(f); + printf("Root permissions\n"); + } +} + +void drop_temporarily(void) +{ + /* On systems without setres* use setre*. But make sure it works */ + const int gid = getgid(), uid = getuid(); + const int egid = getegid(), euid = geteuid(); + + if (setresuid(uid, uid, euid) != 0) + goto error; + if (setresgid(gid, gid, egid) != 0) + goto error; + + /* Paranoid check */ + if (geteuid() != getuid() || getegid() != getgid()) { + printf("d_t: fun\n"); + goto error; + } + return; +error: + printf("d_t: failure\n"); + exit(EXIT_FAILURE); +} + +void drop_pernamently(void) +{ + /* On systems without setres* use setre*. But make sure it works */ + const int gid = getgid(), uid = getuid(); + + if (setresuid(uid, uid, uid) != 0) + goto error; + if (setresgid(gid, gid, gid) != 0) + goto error; + + /* Paranoid check */ + if (geteuid() != getuid() || getegid() != getgid()) { + printf("d_t: fun\n"); + goto error; + } + + return; +error: + printf("d_p: failure\n"); + exit(EXIT_FAILURE); +} + +void restore(void) +{ + /* On systems without setres* use setre*. But make sure it works */ + + /* 0 should be remembered before! */ + if (setresuid(real_uid, set_uid, set_uid) != 0) + goto error; + if (setresgid(real_gid, set_gid, set_gid) != 0) + goto error; + + /* Paranoid check */ + if (geteuid() != set_uid || getegid() != set_gid) { + printf("d_t: fun\n"); + goto error; + } + + return; +error: + printf("d_p: failure\n"); + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) +{ + clearenv(); + + init(); + + printf("Initial: "); + print(); + + check_perms(); + + printf("* TEMPORARY DROP \n"); + drop_temporarily(); + + check_perms(); + + printf("* RESTORE \n"); + restore(); + + check_perms(); + + printf("* PERNAMENT DROP \n"); + drop_pernamently(); + + check_perms(); + + + printf("* RESTORE (we should fail now) \n"); + restore(); + + check_perms(); + + + return 0; +} diff --git a/utility/otpasswd.c b/utility/otpasswd.c index 05aa17d..f89d0e4 100644 --- a/utility/otpasswd.c +++ b/utility/otpasswd.c @@ -24,9 +24,7 @@ #include #include /* chdir, environ */ -/* umask */ -#include -#include +#include "security.h" #include "print.h" #include "crypto.h" @@ -398,37 +396,8 @@ int main(int argc, char **argv) { int ret; cfg_t *cfg = NULL; - int uid = getuid(), gid = getgid(); - /* As we might be SUID/SGID binary. Clear environment. */ - ret = clearenv(); - if (ret != 0) { - printf("Unable to clear environment\n"); - exit(EXIT_FAILURE); - } - - ret = chdir("/"); - if (ret != 0) { - printf("Unable to change directory to /\n"); - exit(EXIT_FAILURE); - } - - if (environ != NULL || (environ && *environ != NULL)) { - printf("Environment not clear!\n"); - exit(EXIT_FAILURE); - } - - putenv("PATH=/bin:/usr/bin"); - - /* Set umask so others won't read our files */ - if (gid != getegid()) { - /* We are SGID. Don't remove bits from group... */ - umask(S_IWOTH | S_IROTH | S_IXOTH); - } - else { - /* Normal or SUID */ - umask(S_IWOTH | S_IROTH | S_IXOTH | S_IWGRP | S_IRGRP | S_IXGRP); - } + security_init(); /* Bootstrap logging subsystem. */ if (print_init(PRINT_ERROR, 1, 0, NULL) != 0) { @@ -446,15 +415,21 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); } - /* If database is not global we can drop permissions now */ + /* If database is not global we can drop _pernamently_ permissions now */ if (cfg->db != CONFIG_DB_GLOBAL) { - ret = setgid(gid); - ret += setuid(uid); - if (ret != 0) { - printf("Strange error while dropping permissions\n"); - exit(EXIT_FAILURE); - } + security_permanent_drop(); + } else { + /* Otherwise - drop them temporarily */ + security_temporal_drop(); } + + /* TODO/FIXME: If we are SGID and not SUID is there a problem with + * receiving a signal at stupid point of time? */ + +#ifdef OS_LINUX + /* Drop all permissions except for fsuid? Is it possible? */ +#endif + ret = process_cmd_line(argc, argv); return ret; } diff --git a/utility/security.c b/utility/security.c new file mode 100644 index 0000000..70a838e --- /dev/null +++ b/utility/security.c @@ -0,0 +1,176 @@ +/********************************************************************** + * otpasswd -- One-time password manager and PAM module. + * Copyright (C) 2009 by Tomasz bla Fortuna + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with otpasswd. If not, see . + **********************************************************************/ + +/* On systems without setres* use setre* to drop pernamently. + * And sete* to drop temporarily. But make sure it works */ +#if OS_LINUX +/* for setresuid, setresgid */ +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +/* umask */ +#include +#include + +#include "security.h" + +/* Initial remembered values */ +static uid_t real_uid=-1, set_uid=-1; +static uid_t real_gid=-1, set_gid=-1; + +/* When dropping drop to this user */ +static const uid_t drop_to = -1; + +extern char **environ; + +void security_init(void) +{ + int ret; + + /* Store initial UIDs/GIDs */ + real_uid = getuid(); + set_uid = geteuid(); + + real_gid = getgid(); + set_gid = getegid(); + + /* As we might be SUID/SGID binary. Clear the environment. */ + ret = clearenv(); + if (ret != 0) { + printf("Unable to clear environment\n"); + exit(EXIT_FAILURE); + } + + ret = chdir("/"); + if (ret != 0) { + printf("Unable to change directory to /\n"); + exit(EXIT_FAILURE); + } + + if (environ != NULL || (environ && *environ != NULL)) { + printf("Environment not clear!\n"); + exit(EXIT_FAILURE); + } + + putenv("PATH=/bin:/usr/bin"); + + /* Set umask so others won't read our files */ + if (real_gid != set_gid) { + /* We are SGID. Don't remove bits from group... */ + umask(S_IWOTH | S_IROTH | S_IXOTH); + } + else { + /* Normal or SUID */ + umask(S_IWOTH | S_IROTH | S_IXOTH | S_IWGRP | S_IRGRP | S_IXGRP); + } +} + +static void _ensure_no_privileges() +{ + if ((real_gid != set_gid) && (setuid(set_gid) == 0)) + goto error; + + if ((real_uid != set_uid) && (setuid(set_uid) == 0)) + goto error; + + + return; +error: + printf("Privilege check failed. Dying.\n"); + exit(EXIT_FAILURE); +} + +void security_temporal_drop(void) +{ + assert(real_gid != -1); + + /* Draft of setre version: + * setreuid(real_uid, set_uid); - copy euid to suid + * seteuid(drop_to); - drop + * ensure correctness + */ + + if (setresgid(real_gid, drop_to, set_gid) != 0) + goto error; + if (setresuid(real_uid, drop_to, set_uid) != 0) + goto error; + + /* Paranoid check */ + if (geteuid() != drop_to || getegid() != drop_to) { + printf("d_t: fun\n"); + goto error; + } + + return; +error: + printf("Temporal privilege drop failed. Dying.\n"); + exit(EXIT_FAILURE); +} + +void security_permanent_drop(void) +{ + assert(real_gid != -1); + + /* Draft of setre version: + * setreuid(drop_to, drop_to); + * Ensure somehow the saved-UID is correct (/proc) + */ + + if (setresgid(drop_to, drop_to, drop_to) != 0) + goto error; + if (setresuid(drop_to, drop_to, drop_to) != 0) + goto error; + + /* Paranoid check */ + if (geteuid() != getuid() || getegid() != getgid()) { + goto error; + } + + _ensure_no_privileges(); + + return; +error: + printf("Permanent privilege drop failed. Dying.\n"); + exit(EXIT_FAILURE); +} + +void security_restore(void) +{ + assert(real_gid != -1); + + if (setresuid(real_uid, set_uid, set_uid) != 0) + goto error; + if (setresgid(real_gid, set_gid, set_gid) != 0) + goto error; + + /* Paranoid check */ + if (geteuid() != set_uid || getegid() != set_gid) { + goto error; + } + + return; +error: + printf("Privilege restore failed. Dying.\n"); + exit(EXIT_FAILURE); +} + diff --git a/utility/security.h b/utility/security.h new file mode 100644 index 0000000..c723cc6 --- /dev/null +++ b/utility/security.h @@ -0,0 +1,34 @@ +/********************************************************************** + * otpasswd -- One-time password manager and PAM module. + * Copyright (C) 2009 by Tomasz bla Fortuna + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with otpasswd. If not, see . + **********************************************************************/ + +#ifndef _SECURITY_H_ +#define _SECURITY_H_ + +/* Init environment for SUID/SGID program */ +extern void security_init(void); + +/* Temporary drop our effective rights */ +extern void security_temporal_drop(void); + +/* Pernamently drop rights */ +extern void security_permanent_drop(void); + +/* Restore rights which were dropped temporarily */ +extern void security_restore(void); + +#endif