1 /* 2 * Copyright (c) 2005 Daniel Walsh <dwalsh@redhat.com> 3 * Copyright (c) 2006 Damien Miller <djm@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 /* 19 * Linux-specific portability code - just SELinux support at present 20 */ 21 22 #include "includes.h" 23 24 #if defined(WITH_SELINUX) || defined(LINUX_OOM_ADJUST) || \ 25 defined(SYSTEMD_NOTIFY) 26 #include <sys/socket.h> 27 #include <sys/un.h> 28 29 #include <errno.h> 30 #include <inttypes.h> 31 #include <stdarg.h> 32 #include <string.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <time.h> 36 #include <unistd.h> 37 38 #include "log.h" 39 #include "xmalloc.h" 40 #include "port-linux.h" 41 #include "misc.h" 42 43 #ifdef WITH_SELINUX 44 #include <selinux/selinux.h> 45 #include <selinux/label.h> 46 #include <selinux/get_context_list.h> 47 48 #ifndef SSH_SELINUX_UNCONFINED_TYPE 49 # define SSH_SELINUX_UNCONFINED_TYPE ":unconfined_t:" 50 #endif 51 52 /* Wrapper around is_selinux_enabled() to log its return value once only */ 53 int 54 ssh_selinux_enabled(void) 55 { 56 static int enabled = -1; 57 58 if (enabled == -1) { 59 enabled = (is_selinux_enabled() == 1); 60 debug("SELinux support %s", enabled ? "enabled" : "disabled"); 61 } 62 63 return (enabled); 64 } 65 66 /* Return the default security context for the given username */ 67 static char * 68 ssh_selinux_getctxbyname(char *pwname) 69 { 70 char *sc = NULL, *sename = NULL, *lvl = NULL; 71 int r; 72 73 #ifdef HAVE_GETSEUSERBYNAME 74 if (getseuserbyname(pwname, &sename, &lvl) != 0) 75 return NULL; 76 #else 77 sename = pwname; 78 lvl = NULL; 79 #endif 80 81 #ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL 82 r = get_default_context_with_level(sename, lvl, NULL, &sc); 83 #else 84 r = get_default_context(sename, NULL, &sc); 85 #endif 86 87 if (r != 0) { 88 switch (security_getenforce()) { 89 case -1: 90 fatal("%s: ssh_selinux_getctxbyname: " 91 "security_getenforce() failed", __func__); 92 case 0: 93 error("%s: Failed to get default SELinux security " 94 "context for %s", __func__, pwname); 95 sc = NULL; 96 break; 97 default: 98 fatal("%s: Failed to get default SELinux security " 99 "context for %s (in enforcing mode)", 100 __func__, pwname); 101 } 102 } 103 104 #ifdef HAVE_GETSEUSERBYNAME 105 free(sename); 106 free(lvl); 107 #endif 108 109 return sc; 110 } 111 112 /* Set the execution context to the default for the specified user */ 113 void 114 ssh_selinux_setup_exec_context(char *pwname) 115 { 116 char *user_ctx = NULL; 117 118 if (!ssh_selinux_enabled()) 119 return; 120 121 debug3("%s: setting execution context", __func__); 122 123 user_ctx = ssh_selinux_getctxbyname(pwname); 124 if (setexeccon(user_ctx) != 0) { 125 switch (security_getenforce()) { 126 case -1: 127 fatal("%s: security_getenforce() failed", __func__); 128 case 0: 129 error("%s: Failed to set SELinux execution " 130 "context for %s", __func__, pwname); 131 break; 132 default: 133 fatal("%s: Failed to set SELinux execution context " 134 "for %s (in enforcing mode)", __func__, pwname); 135 } 136 } 137 if (user_ctx != NULL) 138 freecon(user_ctx); 139 140 debug3("%s: done", __func__); 141 } 142 143 /* Set the TTY context for the specified user */ 144 void 145 ssh_selinux_setup_pty(char *pwname, const char *tty) 146 { 147 char *new_tty_ctx = NULL, *user_ctx = NULL, *old_tty_ctx = NULL; 148 security_class_t chrclass; 149 150 if (!ssh_selinux_enabled()) 151 return; 152 153 debug3("%s: setting TTY context on %s", __func__, tty); 154 155 user_ctx = ssh_selinux_getctxbyname(pwname); 156 157 /* XXX: should these calls fatal() upon failure in enforcing mode? */ 158 159 if (getfilecon(tty, &old_tty_ctx) == -1) { 160 error("%s: getfilecon: %s", __func__, strerror(errno)); 161 goto out; 162 } 163 if ((chrclass = string_to_security_class("chr_file")) == 0) { 164 error("%s: couldn't get security class for chr_file", __func__); 165 goto out; 166 } 167 if (security_compute_relabel(user_ctx, old_tty_ctx, 168 chrclass, &new_tty_ctx) != 0) { 169 error("%s: security_compute_relabel: %s", 170 __func__, strerror(errno)); 171 goto out; 172 } 173 174 if (setfilecon(tty, new_tty_ctx) != 0) 175 error("%s: setfilecon: %s", __func__, strerror(errno)); 176 out: 177 if (new_tty_ctx != NULL) 178 freecon(new_tty_ctx); 179 if (old_tty_ctx != NULL) 180 freecon(old_tty_ctx); 181 if (user_ctx != NULL) 182 freecon(user_ctx); 183 debug3("%s: done", __func__); 184 } 185 186 void 187 ssh_selinux_change_context(const char *newname) 188 { 189 char *oldctx, *newctx, *cx, *cx2; 190 LogLevel log_level = SYSLOG_LEVEL_INFO; 191 192 if (!ssh_selinux_enabled()) 193 return; 194 195 if (getcon(&oldctx) < 0) { 196 logit_f("getcon failed with %s", strerror(errno)); 197 return; 198 } 199 if ((cx = strchr(oldctx, ':')) == NULL || 200 (cx = strchr(cx + 1, ':')) == NULL || 201 (cx - oldctx) >= INT_MAX) { 202 logit_f("unparsable context %s", oldctx); 203 return; 204 } 205 206 /* 207 * Check whether we are attempting to switch away from an unconfined 208 * security context. 209 */ 210 if (strncmp(cx, SSH_SELINUX_UNCONFINED_TYPE, 211 sizeof(SSH_SELINUX_UNCONFINED_TYPE) - 1) == 0) 212 log_level = SYSLOG_LEVEL_DEBUG3; 213 214 cx2 = strchr(cx + 1, ':'); 215 xasprintf(&newctx, "%.*s%s%s", (int)(cx - oldctx + 1), oldctx, 216 newname, cx2 == NULL ? "" : cx2); 217 218 debug3_f("setting context from '%s' to '%s'", oldctx, newctx); 219 if (setcon(newctx) < 0) 220 do_log2_f(log_level, "setcon %s from %s failed with %s", 221 newctx, oldctx, strerror(errno)); 222 free(oldctx); 223 free(newctx); 224 } 225 226 void 227 ssh_selinux_setfscreatecon(const char *path) 228 { 229 char *context; 230 struct selabel_handle *shandle = NULL; 231 232 if (!ssh_selinux_enabled()) 233 return; 234 if (path == NULL) { 235 setfscreatecon(NULL); 236 return; 237 } 238 if ((shandle = selabel_open(SELABEL_CTX_FILE, NULL, 0)) == NULL) { 239 debug_f("selabel_open failed"); 240 return; 241 } 242 if (selabel_lookup(shandle, &context, path, 0700) == 0) 243 setfscreatecon(context); 244 selabel_close(shandle); 245 } 246 247 #endif /* WITH_SELINUX */ 248 249 #ifdef LINUX_OOM_ADJUST 250 /* 251 * The magic "don't kill me" values, old and new, as documented in eg: 252 * http://lxr.linux.no/#linux+v2.6.32/Documentation/filesystems/proc.txt 253 * http://lxr.linux.no/#linux+v2.6.36/Documentation/filesystems/proc.txt 254 */ 255 256 static int oom_adj_save = INT_MIN; 257 static char *oom_adj_path = NULL; 258 struct { 259 char *path; 260 int value; 261 } oom_adjust[] = { 262 {"/proc/self/oom_score_adj", -1000}, /* kernels >= 2.6.36 */ 263 {"/proc/self/oom_adj", -17}, /* kernels <= 2.6.35 */ 264 {NULL, 0}, 265 }; 266 267 /* 268 * Tell the kernel's out-of-memory killer to avoid sshd. 269 * Returns the previous oom_adj value or zero. 270 */ 271 void 272 oom_adjust_setup(void) 273 { 274 int i, value; 275 FILE *fp; 276 277 debug3("%s", __func__); 278 for (i = 0; oom_adjust[i].path != NULL; i++) { 279 oom_adj_path = oom_adjust[i].path; 280 value = oom_adjust[i].value; 281 if ((fp = fopen(oom_adj_path, "r+")) != NULL) { 282 if (fscanf(fp, "%d", &oom_adj_save) != 1) 283 verbose("error reading %s: %s", oom_adj_path, 284 strerror(errno)); 285 else { 286 rewind(fp); 287 if (fprintf(fp, "%d\n", value) <= 0) 288 verbose("error writing %s: %s", 289 oom_adj_path, strerror(errno)); 290 else 291 debug("Set %s from %d to %d", 292 oom_adj_path, oom_adj_save, value); 293 } 294 fclose(fp); 295 return; 296 } 297 } 298 oom_adj_path = NULL; 299 } 300 301 /* Restore the saved OOM adjustment */ 302 void 303 oom_adjust_restore(void) 304 { 305 FILE *fp; 306 307 debug3("%s", __func__); 308 if (oom_adj_save == INT_MIN || oom_adj_path == NULL || 309 (fp = fopen(oom_adj_path, "w")) == NULL) 310 return; 311 312 if (fprintf(fp, "%d\n", oom_adj_save) <= 0) 313 verbose("error writing %s: %s", oom_adj_path, strerror(errno)); 314 else 315 debug("Set %s to %d", oom_adj_path, oom_adj_save); 316 317 fclose(fp); 318 return; 319 } 320 #endif /* LINUX_OOM_ADJUST */ 321 322 #ifdef SYSTEMD_NOTIFY 323 324 static void ssh_systemd_notify(const char *, ...) 325 __attribute__((__format__ (printf, 1, 2))) __attribute__((__nonnull__ (1))); 326 327 static void 328 ssh_systemd_notify(const char *fmt, ...) 329 { 330 char *s = NULL; 331 const char *path; 332 struct stat sb; 333 struct sockaddr_un addr; 334 int fd = -1; 335 va_list ap; 336 337 if ((path = getenv("NOTIFY_SOCKET")) == NULL || strlen(path) == 0) 338 return; 339 340 va_start(ap, fmt); 341 xvasprintf(&s, fmt, ap); 342 va_end(ap); 343 344 /* Only AF_UNIX is supported, with path or abstract sockets */ 345 if (path[0] != '/' && path[0] != '@') { 346 error_f("socket \"%s\" is not compatible with AF_UNIX", path); 347 goto out; 348 } 349 350 if (path[0] == '/' && stat(path, &sb) != 0) { 351 error_f("socket \"%s\" stat: %s", path, strerror(errno)); 352 goto out; 353 } 354 355 memset(&addr, 0, sizeof(addr)); 356 addr.sun_family = AF_UNIX; 357 if (strlcpy(addr.sun_path, path, 358 sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) { 359 error_f("socket path \"%s\" too long", path); 360 goto out; 361 } 362 /* Support for abstract socket */ 363 if (addr.sun_path[0] == '@') 364 addr.sun_path[0] = 0; 365 if ((fd = socket(PF_UNIX, SOCK_DGRAM, 0)) == -1) { 366 error_f("socket \"%s\": %s", path, strerror(errno)); 367 goto out; 368 } 369 if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) { 370 error_f("socket \"%s\" connect: %s", path, strerror(errno)); 371 goto out; 372 } 373 if (write(fd, s, strlen(s)) != (ssize_t)strlen(s)) { 374 error_f("socket \"%s\" write: %s", path, strerror(errno)); 375 goto out; 376 } 377 debug_f("socket \"%s\" notified %s", path, s); 378 out: 379 if (fd != -1) 380 close(fd); 381 free(s); 382 } 383 384 void 385 ssh_systemd_notify_ready(void) 386 { 387 ssh_systemd_notify("READY=1"); 388 } 389 390 void 391 ssh_systemd_notify_reload(void) 392 { 393 struct timespec now; 394 395 monotime_ts(&now); 396 if (now.tv_sec < 0 || now.tv_nsec < 0) { 397 error_f("monotime returned negative value"); 398 ssh_systemd_notify("RELOADING=1"); 399 } else { 400 ssh_systemd_notify("RELOADING=1\nMONOTONIC_USEC=%llu", 401 ((uint64_t)now.tv_sec * 1000000ULL) + 402 ((uint64_t)now.tv_nsec / 1000ULL)); 403 } 404 } 405 #endif /* SYSTEMD_NOTIFY */ 406 407 #endif /* WITH_SELINUX || LINUX_OOM_ADJUST || SYSTEMD_NOTIFY */ 408