diff options
| author | Yuuki Harano | 2021-07-25 23:34:55 +0900 |
|---|---|---|
| committer | Yuuki Harano | 2021-07-25 23:34:55 +0900 |
| commit | 13a9a5e836cbe6e64aadaba40fe1f7eb83320d08 (patch) | |
| tree | 242ac1f485cf6762680a904952747d63b295e198 /lib-src | |
| parent | b242394f24b154f8e20f5abf4b2f826629e99ea6 (diff) | |
| parent | 41a55a330f518254da795719ac6e3085254d4110 (diff) | |
| download | emacs-13a9a5e836cbe6e64aadaba40fe1f7eb83320d08.tar.gz emacs-13a9a5e836cbe6e64aadaba40fe1f7eb83320d08.zip | |
Merge branch 'master' of git.sv.gnu.org:/srv/git/emacs into feature/pgtk
Diffstat (limited to 'lib-src')
| -rw-r--r-- | lib-src/emacsclient.c | 226 |
1 files changed, 147 insertions, 79 deletions
diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index 6d3f827a253..8fa571fd4ef 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c | |||
| @@ -80,6 +80,9 @@ char *w32_getenv (const char *); | |||
| 80 | #include <sys/stat.h> | 80 | #include <sys/stat.h> |
| 81 | #include <unistd.h> | 81 | #include <unistd.h> |
| 82 | 82 | ||
| 83 | #ifndef WINDOWSNT | ||
| 84 | # include <acl.h> | ||
| 85 | #endif | ||
| 83 | #include <filename.h> | 86 | #include <filename.h> |
| 84 | #include <intprops.h> | 87 | #include <intprops.h> |
| 85 | #include <min-max.h> | 88 | #include <min-max.h> |
| @@ -91,6 +94,10 @@ char *w32_getenv (const char *); | |||
| 91 | # pragma GCC diagnostic ignored "-Wformat-truncation=2" | 94 | # pragma GCC diagnostic ignored "-Wformat-truncation=2" |
| 92 | #endif | 95 | #endif |
| 93 | 96 | ||
| 97 | #if !defined O_PATH && !defined WINDOWSNT | ||
| 98 | # define O_PATH O_SEARCH | ||
| 99 | #endif | ||
| 100 | |||
| 94 | 101 | ||
| 95 | /* Name used to invoke this program. */ | 102 | /* Name used to invoke this program. */ |
| 96 | static char const *progname; | 103 | static char const *progname; |
| @@ -1133,24 +1140,74 @@ process_grouping (void) | |||
| 1133 | 1140 | ||
| 1134 | #ifdef SOCKETS_IN_FILE_SYSTEM | 1141 | #ifdef SOCKETS_IN_FILE_SYSTEM |
| 1135 | 1142 | ||
| 1136 | /* Return the file status of NAME, ordinarily a socket. | 1143 | /* A local socket address. The union avoids the need to cast. */ |
| 1137 | It should be owned by UID. Return one of the following: | 1144 | union local_sockaddr |
| 1138 | >0 - 'stat' failed with this errno value | 1145 | { |
| 1139 | -1 - isn't owned by us | 1146 | struct sockaddr_un un; |
| 1140 | 0 - success: none of the above */ | 1147 | struct sockaddr sa; |
| 1148 | }; | ||
| 1149 | |||
| 1150 | /* Relative to the directory DIRFD, connect the socket file named ADDR | ||
| 1151 | to the socket S. Return 0 if successful, -1 if DIRFD is not | ||
| 1152 | AT_FDCWD and DIRFD's permissions would allow a symlink attack, an | ||
| 1153 | errno otherwise. */ | ||
| 1141 | 1154 | ||
| 1142 | static int | 1155 | static int |
| 1143 | socket_status (const char *name, uid_t uid) | 1156 | connect_socket (int dirfd, char const *addr, int s, uid_t uid) |
| 1144 | { | 1157 | { |
| 1145 | struct stat statbfr; | 1158 | int sock_status = 0; |
| 1146 | 1159 | ||
| 1147 | if (stat (name, &statbfr) != 0) | 1160 | union local_sockaddr server; |
| 1148 | return errno; | 1161 | if (sizeof server.un.sun_path <= strlen (addr)) |
| 1162 | return ENAMETOOLONG; | ||
| 1163 | server.un.sun_family = AF_UNIX; | ||
| 1164 | strcpy (server.un.sun_path, addr); | ||
| 1149 | 1165 | ||
| 1150 | if (statbfr.st_uid != uid) | 1166 | /* If -1, WDFD is not set yet. If nonnegative, WDFD is a file |
| 1151 | return -1; | 1167 | descriptor for the initial working directory. Otherwise -1 - WDFD is |
| 1168 | the error number for the initial working directory. */ | ||
| 1169 | static int wdfd = -1; | ||
| 1152 | 1170 | ||
| 1153 | return 0; | 1171 | if (dirfd != AT_FDCWD) |
| 1172 | { | ||
| 1173 | /* Fail if DIRFD's permissions are bogus. */ | ||
| 1174 | struct stat st; | ||
| 1175 | if (fstat (dirfd, &st) != 0) | ||
| 1176 | return errno; | ||
| 1177 | if (st.st_uid != uid || (st.st_mode & (S_IWGRP | S_IWOTH))) | ||
| 1178 | return -1; | ||
| 1179 | |||
| 1180 | if (wdfd == -1) | ||
| 1181 | { | ||
| 1182 | /* Save the initial working directory. */ | ||
| 1183 | wdfd = open (".", O_PATH | O_CLOEXEC); | ||
| 1184 | if (wdfd < 0) | ||
| 1185 | wdfd = -1 - errno; | ||
| 1186 | } | ||
| 1187 | if (wdfd < 0) | ||
| 1188 | return -1 - wdfd; | ||
| 1189 | if (fchdir (dirfd) != 0) | ||
| 1190 | return errno; | ||
| 1191 | |||
| 1192 | /* Fail if DIRFD has an ACL, which means its permissions are | ||
| 1193 | almost surely bogus. */ | ||
| 1194 | int has_acl = file_has_acl (".", &st); | ||
| 1195 | if (has_acl) | ||
| 1196 | sock_status = has_acl < 0 ? errno : -1; | ||
| 1197 | } | ||
| 1198 | |||
| 1199 | if (!sock_status) | ||
| 1200 | sock_status = connect (s, &server.sa, sizeof server.un) == 0 ? 0 : errno; | ||
| 1201 | |||
| 1202 | /* Fail immediately if we cannot change back to the initial working | ||
| 1203 | directory, as that can mess up the rest of execution. */ | ||
| 1204 | if (dirfd != AT_FDCWD && fchdir (wdfd) != 0) | ||
| 1205 | { | ||
| 1206 | message (true, "%s: .: %s\n", progname, strerror (errno)); | ||
| 1207 | exit (EXIT_FAILURE); | ||
| 1208 | } | ||
| 1209 | |||
| 1210 | return sock_status; | ||
| 1154 | } | 1211 | } |
| 1155 | 1212 | ||
| 1156 | 1213 | ||
| @@ -1327,32 +1384,49 @@ act_on_signals (HSOCKET emacs_socket) | |||
| 1327 | } | 1384 | } |
| 1328 | } | 1385 | } |
| 1329 | 1386 | ||
| 1330 | /* Create in SOCKNAME (of size SOCKNAMESIZE) a name for a local socket. | 1387 | enum { socknamesize = sizeof ((struct sockaddr_un *) NULL)->sun_path }; |
| 1331 | The first TMPDIRLEN bytes of SOCKNAME are already initialized to be | 1388 | |
| 1332 | the name of a temporary directory. Use UID and SERVER_NAME to | 1389 | /* Given a local socket S, create in *SOCKNAME a name for a local socket |
| 1333 | concoct the name. Return the total length of the name if successful, | 1390 | and connect to that socket. The first TMPDIRLEN bytes of *SOCKNAME are |
| 1334 | -1 if it does not fit (and store a truncated name in that case). | 1391 | already initialized to be the name of a temporary directory. |
| 1335 | Fail if TMPDIRLEN is out of range. */ | 1392 | Use UID and SERVER_NAME to concoct the name. Return 0 if |
| 1393 | successful, -1 if the socket's parent directory is not safe, and an | ||
| 1394 | errno if there is some other problem. */ | ||
| 1336 | 1395 | ||
| 1337 | static int | 1396 | static int |
| 1338 | local_sockname (char *sockname, int socknamesize, int tmpdirlen, | 1397 | local_sockname (int s, char sockname[socknamesize], int tmpdirlen, |
| 1339 | uintmax_t uid, char const *server_name) | 1398 | uid_t uid, char const *server_name) |
| 1340 | { | 1399 | { |
| 1341 | /* If ! (0 <= TMPDIRLEN && TMPDIRLEN < SOCKNAMESIZE) the truncated | 1400 | /* If ! (0 <= TMPDIRLEN && TMPDIRLEN < SOCKNAMESIZE) the truncated |
| 1342 | temporary directory name is already in SOCKNAME, so nothing more | 1401 | temporary directory name is already in SOCKNAME, so nothing more |
| 1343 | need be stored. */ | 1402 | need be stored. */ |
| 1344 | if (0 <= tmpdirlen) | 1403 | if (! (0 <= tmpdirlen && tmpdirlen < socknamesize)) |
| 1345 | { | 1404 | return ENAMETOOLONG; |
| 1346 | int remaining = socknamesize - tmpdirlen; | 1405 | |
| 1347 | if (0 < remaining) | 1406 | /* Put the full address name into the buffer, since the caller might |
| 1348 | { | 1407 | need it for diagnostics. But don't overrun the buffer. */ |
| 1349 | int suffixlen = snprintf (&sockname[tmpdirlen], remaining, | 1408 | uintmax_t uidmax = uid; |
| 1350 | "/emacs%"PRIuMAX"/%s", uid, server_name); | 1409 | int emacsdirlen; |
| 1351 | if (0 <= suffixlen && suffixlen < remaining) | 1410 | int suffixlen = snprintf (sockname + tmpdirlen, socknamesize - tmpdirlen, |
| 1352 | return tmpdirlen + suffixlen; | 1411 | "/emacs%"PRIuMAX"%n/%s", uidmax, &emacsdirlen, |
| 1353 | } | 1412 | server_name); |
| 1354 | } | 1413 | if (! (0 <= suffixlen && suffixlen < socknamesize - tmpdirlen)) |
| 1355 | return -1; | 1414 | return ENAMETOOLONG; |
| 1415 | |||
| 1416 | /* Make sure the address's parent directory is not a symlink and is | ||
| 1417 | this user's directory and does not let others write to it; this | ||
| 1418 | fends off some symlink attacks. To avoid races, keep the parent | ||
| 1419 | directory open while checking. */ | ||
| 1420 | char *emacsdirend = sockname + tmpdirlen + emacsdirlen; | ||
| 1421 | *emacsdirend = '\0'; | ||
| 1422 | int dir = openat (AT_FDCWD, sockname, | ||
| 1423 | O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); | ||
| 1424 | *emacsdirend = '/'; | ||
| 1425 | if (dir < 0) | ||
| 1426 | return errno; | ||
| 1427 | int sock_status = connect_socket (dir, server_name, s, uid); | ||
| 1428 | close (dir); | ||
| 1429 | return sock_status; | ||
| 1356 | } | 1430 | } |
| 1357 | 1431 | ||
| 1358 | /* Create a local socket for SERVER_NAME and connect it to Emacs. If | 1432 | /* Create a local socket for SERVER_NAME and connect it to Emacs. If |
| @@ -1363,28 +1437,43 @@ local_sockname (char *sockname, int socknamesize, int tmpdirlen, | |||
| 1363 | static HSOCKET | 1437 | static HSOCKET |
| 1364 | set_local_socket (char const *server_name) | 1438 | set_local_socket (char const *server_name) |
| 1365 | { | 1439 | { |
| 1366 | union { | 1440 | union local_sockaddr server; |
| 1367 | struct sockaddr_un un; | 1441 | int sock_status; |
| 1368 | struct sockaddr sa; | ||
| 1369 | } server = {{ .sun_family = AF_UNIX }}; | ||
| 1370 | char *sockname = server.un.sun_path; | 1442 | char *sockname = server.un.sun_path; |
| 1371 | enum { socknamesize = sizeof server.un.sun_path }; | ||
| 1372 | int tmpdirlen = -1; | 1443 | int tmpdirlen = -1; |
| 1373 | int socknamelen = -1; | 1444 | int socknamelen = -1; |
| 1374 | uid_t uid = geteuid (); | 1445 | uid_t uid = geteuid (); |
| 1375 | bool tmpdir_used = false; | 1446 | bool tmpdir_used = false; |
| 1447 | int s = cloexec_socket (AF_UNIX, SOCK_STREAM, 0); | ||
| 1448 | if (s < 0) | ||
| 1449 | { | ||
| 1450 | message (true, "%s: can't create socket: %s\n", | ||
| 1451 | progname, strerror (errno)); | ||
| 1452 | fail (); | ||
| 1453 | } | ||
| 1376 | 1454 | ||
| 1377 | if (strchr (server_name, '/') | 1455 | if (strchr (server_name, '/') |
| 1378 | || (ISSLASH ('\\') && strchr (server_name, '\\'))) | 1456 | || (ISSLASH ('\\') && strchr (server_name, '\\'))) |
| 1379 | socknamelen = snprintf (sockname, socknamesize, "%s", server_name); | 1457 | { |
| 1458 | socknamelen = snprintf (sockname, socknamesize, "%s", server_name); | ||
| 1459 | sock_status = (0 <= socknamelen && socknamelen < socknamesize | ||
| 1460 | ? connect_socket (AT_FDCWD, sockname, s, 0) | ||
| 1461 | : ENAMETOOLONG); | ||
| 1462 | } | ||
| 1380 | else | 1463 | else |
| 1381 | { | 1464 | { |
| 1382 | /* socket_name is a file name component. */ | 1465 | /* socket_name is a file name component. */ |
| 1466 | sock_status = ENOENT; | ||
| 1383 | char const *xdg_runtime_dir = egetenv ("XDG_RUNTIME_DIR"); | 1467 | char const *xdg_runtime_dir = egetenv ("XDG_RUNTIME_DIR"); |
| 1384 | if (xdg_runtime_dir) | 1468 | if (xdg_runtime_dir) |
| 1385 | socknamelen = snprintf (sockname, socknamesize, "%s/emacs/%s", | 1469 | { |
| 1386 | xdg_runtime_dir, server_name); | 1470 | socknamelen = snprintf (sockname, socknamesize, "%s/emacs/%s", |
| 1387 | else | 1471 | xdg_runtime_dir, server_name); |
| 1472 | sock_status = (0 <= socknamelen && socknamelen < socknamesize | ||
| 1473 | ? connect_socket (AT_FDCWD, sockname, s, 0) | ||
| 1474 | : ENAMETOOLONG); | ||
| 1475 | } | ||
| 1476 | if (sock_status == ENOENT) | ||
| 1388 | { | 1477 | { |
| 1389 | char const *tmpdir = egetenv ("TMPDIR"); | 1478 | char const *tmpdir = egetenv ("TMPDIR"); |
| 1390 | if (tmpdir) | 1479 | if (tmpdir) |
| @@ -1403,23 +1492,24 @@ set_local_socket (char const *server_name) | |||
| 1403 | if (tmpdirlen < 0) | 1492 | if (tmpdirlen < 0) |
| 1404 | tmpdirlen = snprintf (sockname, socknamesize, "/tmp"); | 1493 | tmpdirlen = snprintf (sockname, socknamesize, "/tmp"); |
| 1405 | } | 1494 | } |
| 1406 | socknamelen = local_sockname (sockname, socknamesize, tmpdirlen, | 1495 | sock_status = local_sockname (s, sockname, tmpdirlen, |
| 1407 | uid, server_name); | 1496 | uid, server_name); |
| 1408 | tmpdir_used = true; | 1497 | tmpdir_used = true; |
| 1409 | } | 1498 | } |
| 1410 | } | 1499 | } |
| 1411 | 1500 | ||
| 1412 | if (! (0 <= socknamelen && socknamelen < socknamesize)) | 1501 | if (sock_status == 0) |
| 1502 | return s; | ||
| 1503 | |||
| 1504 | if (sock_status == ENAMETOOLONG) | ||
| 1413 | { | 1505 | { |
| 1414 | message (true, "%s: socket-name %s... too long\n", progname, sockname); | 1506 | message (true, "%s: socket-name %s... too long\n", progname, sockname); |
| 1415 | fail (); | 1507 | fail (); |
| 1416 | } | 1508 | } |
| 1417 | 1509 | ||
| 1418 | /* See if the socket exists, and if it's owned by us. */ | 1510 | if (tmpdir_used) |
| 1419 | int sock_status = socket_status (sockname, uid); | ||
| 1420 | if (sock_status) | ||
| 1421 | { | 1511 | { |
| 1422 | /* Failing that, see if LOGNAME or USER exist and differ from | 1512 | /* See whether LOGNAME or USER exist and differ from |
| 1423 | our euid. If so, look for a socket based on the UID | 1513 | our euid. If so, look for a socket based on the UID |
| 1424 | associated with the name. This is reminiscent of the logic | 1514 | associated with the name. This is reminiscent of the logic |
| 1425 | that init_editfns uses to set the global Vuser_full_name. */ | 1515 | that init_editfns uses to set the global Vuser_full_name. */ |
| @@ -1436,48 +1526,26 @@ set_local_socket (char const *server_name) | |||
| 1436 | if (pw && pw->pw_uid != uid) | 1526 | if (pw && pw->pw_uid != uid) |
| 1437 | { | 1527 | { |
| 1438 | /* We're running under su, apparently. */ | 1528 | /* We're running under su, apparently. */ |
| 1439 | socknamelen = local_sockname (sockname, socknamesize, tmpdirlen, | 1529 | sock_status = local_sockname (s, sockname, tmpdirlen, |
| 1440 | pw->pw_uid, server_name); | 1530 | pw->pw_uid, server_name); |
| 1441 | if (socknamelen < 0) | 1531 | if (sock_status == 0) |
| 1532 | return s; | ||
| 1533 | if (sock_status == ENAMETOOLONG) | ||
| 1442 | { | 1534 | { |
| 1443 | message (true, "%s: socket-name %s... too long\n", | 1535 | message (true, "%s: socket-name %s... too long\n", |
| 1444 | progname, sockname); | 1536 | progname, sockname); |
| 1445 | exit (EXIT_FAILURE); | 1537 | exit (EXIT_FAILURE); |
| 1446 | } | 1538 | } |
| 1447 | |||
| 1448 | sock_status = socket_status (sockname, uid); | ||
| 1449 | } | 1539 | } |
| 1450 | } | 1540 | } |
| 1451 | } | 1541 | } |
| 1452 | 1542 | ||
| 1453 | if (sock_status == 0) | 1543 | close (s); |
| 1454 | { | ||
| 1455 | HSOCKET s = cloexec_socket (AF_UNIX, SOCK_STREAM, 0); | ||
| 1456 | if (s < 0) | ||
| 1457 | { | ||
| 1458 | message (true, "%s: socket: %s\n", progname, strerror (errno)); | ||
| 1459 | return INVALID_SOCKET; | ||
| 1460 | } | ||
| 1461 | if (connect (s, &server.sa, sizeof server.un) != 0) | ||
| 1462 | { | ||
| 1463 | message (true, "%s: connect: %s\n", progname, strerror (errno)); | ||
| 1464 | CLOSE_SOCKET (s); | ||
| 1465 | return INVALID_SOCKET; | ||
| 1466 | } | ||
| 1467 | 1544 | ||
| 1468 | struct stat connect_stat; | 1545 | if (sock_status == -1) |
| 1469 | if (fstat (s, &connect_stat) != 0) | 1546 | message (true, |
| 1470 | sock_status = errno; | 1547 | "%s: Invalid permissions on parent directory of socket: %s\n", |
| 1471 | else if (connect_stat.st_uid == uid) | 1548 | progname, sockname); |
| 1472 | return s; | ||
| 1473 | else | ||
| 1474 | sock_status = -1; | ||
| 1475 | |||
| 1476 | CLOSE_SOCKET (s); | ||
| 1477 | } | ||
| 1478 | |||
| 1479 | if (sock_status < 0) | ||
| 1480 | message (true, "%s: Invalid socket owner\n", progname); | ||
| 1481 | else if (sock_status == ENOENT) | 1549 | else if (sock_status == ENOENT) |
| 1482 | { | 1550 | { |
| 1483 | if (tmpdir_used) | 1551 | if (tmpdir_used) |
| @@ -1507,7 +1575,7 @@ set_local_socket (char const *server_name) | |||
| 1507 | } | 1575 | } |
| 1508 | } | 1576 | } |
| 1509 | else | 1577 | else |
| 1510 | message (true, "%s: can't stat %s: %s\n", | 1578 | message (true, "%s: can't connect to %s: %s\n", |
| 1511 | progname, sockname, strerror (sock_status)); | 1579 | progname, sockname, strerror (sock_status)); |
| 1512 | 1580 | ||
| 1513 | return INVALID_SOCKET; | 1581 | return INVALID_SOCKET; |