1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
|
From b6c8da512960cfc7e202d02bb94ced87a9b92bd0 Mon Sep 17 00:00:00 2001
From: Colin Watson <cjwatson@debian.org>
Date: Sun, 9 Feb 2014 16:09:58 +0000
Subject: Allow harmless group-writability
Allow secure files (~/.ssh/config, ~/.ssh/authorized_keys, etc.) to be
group-writable, provided that the group in question contains only the file's
owner. Rejected upstream for IMO incorrect reasons (e.g. a misunderstanding
about the contents of gr->gr_mem). Given that per-user groups and umask 002
are the default setup in Debian (for good reasons - this makes operating in
setgid directories with other groups much easier), we need to permit this by
default.
Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1060
Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=314347
Last-Update: 2025-10-06
Patch-Name: user-group-modes.patch
---
auth-rhosts.c | 6 ++----
auth.c | 3 +--
misc.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++----
misc.h | 2 ++
readconf.c | 3 +--
ssh.1 | 2 ++
ssh_config.5 | 2 ++
7 files changed, 61 insertions(+), 12 deletions(-)
diff --git a/auth-rhosts.c b/auth-rhosts.c
index 031186f24..560a0b894 100644
--- a/auth-rhosts.c
+++ b/auth-rhosts.c
@@ -263,8 +263,7 @@ auth_rhosts2(struct passwd *pw, const char *client_user, const char *hostname,
return 0;
}
if (options.strict_modes &&
- ((st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
- (st.st_mode & 022) != 0)) {
+ !secure_permissions(&st, pw->pw_uid)) {
logit("Rhosts authentication refused for %.100s: "
"bad ownership or modes for home directory.", pw->pw_name);
auth_debug_add("Rhosts authentication refused for %.100s: "
@@ -293,8 +292,7 @@ auth_rhosts2(struct passwd *pw, const char *client_user, const char *hostname,
* allowing access to their account by anyone.
*/
if (options.strict_modes &&
- ((st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
- (st.st_mode & 022) != 0)) {
+ !secure_permissions(&st, pw->pw_uid)) {
logit("Rhosts authentication refused for %.100s: "
"bad modes for %.200s", pw->pw_name, path);
auth_debug_add("Bad file modes for %.200s", path);
diff --git a/auth.c b/auth.c
index d25653ee4..f075355e7 100644
--- a/auth.c
+++ b/auth.c
@@ -428,8 +428,7 @@ check_key_in_hostfiles(struct passwd *pw, struct sshkey *key, const char *host,
user_hostfile = tilde_expand_filename(userfile, pw->pw_uid);
if (options.strict_modes &&
(stat(user_hostfile, &st) == 0) &&
- ((st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
- (st.st_mode & 022) != 0)) {
+ !secure_permissions(&st, pw->pw_uid)) {
logit("Authentication refused for %.100s: "
"bad owner or modes for %.200s",
pw->pw_name, user_hostfile);
diff --git a/misc.c b/misc.c
index ce77ec943..3c1a6751f 100644
--- a/misc.c
+++ b/misc.c
@@ -1466,6 +1466,55 @@ percent_dollar_expand(const char *string, ...)
return ret;
}
+int
+secure_permissions(struct stat *st, uid_t uid)
+{
+ if (!platform_sys_dir_uid(st->st_uid) && st->st_uid != uid)
+ return 0;
+ if ((st->st_mode & 002) != 0)
+ return 0;
+ if ((st->st_mode & 020) != 0) {
+ /* If the file is group-writable, the group in question must
+ * have exactly one member, namely the file's owner.
+ * (Zero-member groups are typically used by setgid
+ * binaries, and are unlikely to be suitable.)
+ */
+ struct passwd *pw;
+ struct group *gr;
+ int members = 0;
+
+ gr = getgrgid(st->st_gid);
+ if (!gr)
+ return 0;
+
+ /* Check primary group memberships. */
+ while ((pw = getpwent()) != NULL) {
+ if (pw->pw_gid == gr->gr_gid) {
+ ++members;
+ if (pw->pw_uid != uid)
+ return 0;
+ }
+ }
+ endpwent();
+
+ pw = getpwuid(st->st_uid);
+ if (!pw)
+ return 0;
+
+ /* Check supplementary group memberships. */
+ if (gr->gr_mem[0]) {
+ ++members;
+ if (strcmp(pw->pw_name, gr->gr_mem[0]) ||
+ gr->gr_mem[1])
+ return 0;
+ }
+
+ if (!members)
+ return 0;
+ }
+ return 1;
+}
+
int
tun_open(int tun, int mode, char **ifname)
{
@@ -2288,8 +2337,7 @@ safe_path(const char *name, struct stat *stp, const char *pw_dir,
snprintf(err, errlen, "%s is not a regular file", buf);
return -1;
}
- if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) ||
- (stp->st_mode & 022) != 0) {
+ if (!secure_permissions(stp, uid)) {
snprintf(err, errlen, "bad ownership or modes for file %s",
buf);
return -1;
@@ -2309,8 +2357,7 @@ safe_path(const char *name, struct stat *stp, const char *pw_dir,
strlcpy(buf, cp, sizeof(buf));
if (stat(buf, &st) == -1 ||
- (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) ||
- (st.st_mode & 022) != 0) {
+ !secure_permissions(&st, uid)) {
snprintf(err, errlen,
"bad ownership or modes for directory %s", buf);
return -1;
diff --git a/misc.h b/misc.h
index f3c5a18c6..aad15dfbd 100644
--- a/misc.h
+++ b/misc.h
@@ -254,6 +254,8 @@ struct notifier_ctx *notify_start(int, const char *, ...)
void notify_complete(struct notifier_ctx *, const char *, ...)
__attribute__((format(printf, 2, 3)));
+int secure_permissions(struct stat *st, uid_t uid);
+
#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b))
#define ROUNDUP(x, y) ((((x)+((y)-1))/(y))*(y))
diff --git a/readconf.c b/readconf.c
index 95d497938..c568697cf 100644
--- a/readconf.c
+++ b/readconf.c
@@ -2674,8 +2674,7 @@ read_config_file_depth(const char *filename, struct passwd *pw,
if (fstat(fileno(f), &sb) == -1)
fatal("fstat %s: %s", filename, strerror(errno));
- if (((sb.st_uid != 0 && sb.st_uid != getuid()) ||
- (sb.st_mode & 022) != 0))
+ if (!secure_permissions(&sb, getuid()))
fatal("Bad owner or permissions on %s", filename);
}
diff --git a/ssh.1 b/ssh.1
index f83514c8f..62bb40a50 100644
--- a/ssh.1
+++ b/ssh.1
@@ -1582,6 +1582,8 @@ The file format and configuration options are described in
.Xr ssh_config 5 .
Because of the potential for abuse, this file must have strict permissions:
read/write for the user, and not writable by others.
+It may be group-writable provided that the group in question contains only
+the user.
.Pp
.It Pa ~/.ssh/environment
Contains additional definitions for environment variables; see
diff --git a/ssh_config.5 b/ssh_config.5
index 718c870f8..c947aba0a 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -2512,6 +2512,8 @@ The format of this file is described above.
This file is used by the SSH client.
Because of the potential for abuse, this file must have strict permissions:
read/write for the user, and not writable by others.
+It may be group-writable provided that the group in question contains only
+the user.
.It Pa /etc/ssh/ssh_config
Systemwide configuration file.
This file provides defaults for those
|