procnet.c 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. /*
  2. * Locally authenticate a TCP socket via /proc/net.
  3. *
  4. * Obviously, if a TCP connection comes from a different host, there's
  5. * no way to find out the identity of the thing at the other end (or
  6. * even really to assign that concept a meaning) except by the usual
  7. * method of speaking a protocol over the socket itself which involves
  8. * some form of (preferably cryptographic) authentication exchange.
  9. *
  10. * But if the connection comes from localhost, then on at least some
  11. * operating systems, you can do better. On Linux, /proc/net/tcp and
  12. * /proc/net/tcp6 list the full set of active TCP connection
  13. * endpoints, and they list an owning uid for each one. So once you've
  14. * accepted a connection to a listening socket and found that the
  15. * other end of it is a localhost address, you can look up the _other_
  16. * endpoint in the right one of those files, and find out which uid
  17. * owns it.
  18. */
  19. #include <ctype.h>
  20. #include <stddef.h>
  21. #include <stdio.h>
  22. #include <unistd.h>
  23. #include <sys/types.h>
  24. #include <sys/socket.h>
  25. #include <arpa/inet.h>
  26. #include <netinet/in.h>
  27. #include "misc.h"
  28. static ptrlen get_space_separated_field(ptrlen *string)
  29. {
  30. const char *p = string->ptr, *end = p + string->len;
  31. while (p < end && isspace((unsigned char)*p))
  32. p++;
  33. if (p == end)
  34. return PTRLEN_LITERAL("");
  35. const char *start = p;
  36. while (p < end && !isspace((unsigned char)*p))
  37. p++;
  38. *string = make_ptrlen(p, end - p);
  39. return make_ptrlen(start, p - start);
  40. }
  41. enum { GOT_LOCAL_UID = 1, GOT_REMOTE_UID = 2 };
  42. /*
  43. * Open a file formatted like /proc/net/tcp{,6}, and search it for
  44. * both ends of a particular connection.
  45. *
  46. * The operands 'local' and 'remote' give the expected string
  47. * representations of the local and remote addresses of the connection
  48. * we're looking for.
  49. *
  50. * Return value is the bitwise OR of 1 if we found the local end of
  51. * the connection and 2 if we found the remote. Each output uid_t
  52. * parameter is filled in iff the corresponding bit is set in the
  53. * return value.
  54. */
  55. static int lookup_uids_in_procnet_file(
  56. const char *path, ptrlen local, ptrlen remote,
  57. uid_t *local_uid, uid_t *remote_uid)
  58. {
  59. FILE *fp = NULL;
  60. int toret = 0;
  61. ptrlen line, field;
  62. enum { GF_LOCAL = 1, GF_REMOTE = 2, GF_UID = 4 };
  63. fp = fopen(path, "r");
  64. if (!fp)
  65. goto out;
  66. /* Expected indices of fields in /proc/net/tcp* */
  67. const int LOCAL_ADDR_INDEX = 1;
  68. const int REMOTE_ADDR_INDEX = 2;
  69. const int UID_INDEX = 7;
  70. for (char *linez; (linez = chomp(fgetline(fp))) != NULL ;) {
  71. line = ptrlen_from_asciz(linez);
  72. int gotfields = 0;
  73. ptrlen local_addr = PTRLEN_LITERAL("");
  74. ptrlen remote_addr = PTRLEN_LITERAL("");
  75. long uid = -1;
  76. for (int i = 0; (field = get_space_separated_field(&line)).len != 0;
  77. i++) {
  78. if (i == LOCAL_ADDR_INDEX) {
  79. gotfields |= GF_LOCAL;
  80. local_addr = field;
  81. } else if (i == REMOTE_ADDR_INDEX) {
  82. gotfields |= GF_REMOTE;
  83. remote_addr = field;
  84. } else if (i == UID_INDEX) {
  85. uid = 0;
  86. for (const char *p = field.ptr, *end = p + field.len;
  87. p < end; p++) {
  88. if (!isdigit((unsigned char)*p)) {
  89. uid = -1;
  90. break;
  91. }
  92. int dval = *p - '0';
  93. if (uid > LONG_MAX/10) {
  94. uid = -1;
  95. break;
  96. }
  97. uid *= 10;
  98. if (uid > LONG_MAX - dval) {
  99. uid = -1;
  100. break;
  101. }
  102. uid += dval;
  103. }
  104. gotfields |= GF_UID;
  105. }
  106. }
  107. if (gotfields == (GF_LOCAL | GF_REMOTE | GF_UID)) {
  108. if (ptrlen_eq_ptrlen(local_addr, local) &&
  109. ptrlen_eq_ptrlen(remote_addr, remote)) {
  110. *local_uid = uid;
  111. toret |= GOT_LOCAL_UID;
  112. }
  113. if (ptrlen_eq_ptrlen(local_addr, remote) &&
  114. ptrlen_eq_ptrlen(remote_addr, local)) {
  115. *remote_uid = uid;
  116. toret |= GOT_REMOTE_UID;
  117. }
  118. }
  119. sfree(linez);
  120. }
  121. fclose(fp);
  122. fp = NULL;
  123. out:
  124. if (fp)
  125. fclose(fp);
  126. return toret;
  127. }
  128. static const char *procnet_path(int family)
  129. {
  130. switch (family) {
  131. case AF_INET: return "/proc/net/tcp";
  132. case AF_INET6: return "/proc/net/tcp6";
  133. default: return NULL;
  134. }
  135. }
  136. static char *format_sockaddr(const void *addr, int family)
  137. {
  138. if (family == AF_INET) {
  139. const struct sockaddr_in *a = (const struct sockaddr_in *)addr;
  140. assert(a->sin_family == family);
  141. /* Linux /proc/net formats the IP address native-endian, so we
  142. * don't use ntohl */
  143. return dupprintf("%08X:%04X", a->sin_addr.s_addr, ntohs(a->sin_port));
  144. } else if (family == AF_INET6) {
  145. struct sockaddr_in6 *a = (struct sockaddr_in6 *)addr;
  146. assert(a->sin6_family == family);
  147. strbuf *sb = strbuf_new();
  148. const uint32_t *addrwords = (const uint32_t *)a->sin6_addr.s6_addr;
  149. for (int i = 0; i < 4; i++)
  150. put_fmt(sb, "%08X", addrwords[i]);
  151. put_fmt(sb, ":%04X", ntohs(a->sin6_port));
  152. return strbuf_to_str(sb);
  153. } else {
  154. return NULL;
  155. }
  156. }
  157. bool socket_peer_is_same_user(int fd)
  158. {
  159. struct sockaddr_storage addr;
  160. socklen_t addrlen;
  161. int family;
  162. bool toret = false;
  163. char *local = NULL, *remote = NULL;
  164. const char *path;
  165. addrlen = sizeof(addr);
  166. if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) != 0)
  167. goto out;
  168. family = addr.ss_family;
  169. if ((path = procnet_path(family)) == NULL)
  170. goto out;
  171. local = format_sockaddr(&addr, family);
  172. if (!local)
  173. goto out;
  174. addrlen = sizeof(addr);
  175. if (getpeername(fd, (struct sockaddr *)&addr, &addrlen) != 0)
  176. goto out;
  177. if (addr.ss_family != family)
  178. goto out;
  179. remote = format_sockaddr(&addr, family);
  180. if (!remote)
  181. goto out;
  182. ptrlen locpl = ptrlen_from_asciz(local);
  183. ptrlen rempl = ptrlen_from_asciz(remote);
  184. /*
  185. * Check that _both_ end of the socket are the uid we expect, as a
  186. * sanity check on the /proc/net file being reasonable at all.
  187. */
  188. uid_t our_uid = getuid();
  189. uid_t local_uid = -1, remote_uid = -1;
  190. int got = lookup_uids_in_procnet_file(
  191. path, locpl, rempl, &local_uid, &remote_uid);
  192. if (got == (GOT_LOCAL_UID | GOT_REMOTE_UID) &&
  193. local_uid == our_uid && remote_uid == our_uid)
  194. toret = true;
  195. out:
  196. sfree(local);
  197. sfree(remote);
  198. return toret;
  199. }