1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00
putty-source/unix/procnet.c
Simon Tatham be8d3974ff Generalise strbuf_catf() into put_fmt().
marshal.h now provides a macro put_fmt() which allows you to write
arbitrary printf-formatted data to an arbitrary BinarySink.

We already had this facility for strbufs in particular, in the form of
strbuf_catf(). That was able to take advantage of knowing the inner
structure of a strbuf to minimise memory allocation (it would snprintf
directly into the strbuf's existing buffer if possible). For a general
black-box BinarySink we can't do that, so instead we dupvprintf into a
temporary buffer.

For consistency, I've removed strbuf_catf, and converted all uses of
it into the new put_fmt - and I've also added an extra vtable method
in the BinarySink API, so that put_fmt can still use strbuf_catf's
more efficient memory management when talking to a strbuf, and fall
back to the simpler strategy when that's not available.
2021-11-19 11:32:47 +00:00

230 lines
6.7 KiB
C

/*
* Locally authenticate a TCP socket via /proc/net.
*
* Obviously, if a TCP connection comes from a different host, there's
* no way to find out the identity of the thing at the other end (or
* even really to assign that concept a meaning) except by the usual
* method of speaking a protocol over the socket itself which involves
* some form of (preferably cryptographic) authentication exchange.
*
* But if the connection comes from localhost, then on at least some
* operating systems, you can do better. On Linux, /proc/net/tcp and
* /proc/net/tcp6 list the full set of active TCP connection
* endpoints, and they list an owning uid for each one. So once you've
* accepted a connection to a listening socket and found that the
* other end of it is a localhost address, you can look up the _other_
* endpoint in the right one of those files, and find out which uid
* owns it.
*/
#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "misc.h"
static ptrlen get_space_separated_field(ptrlen *string)
{
const char *p = string->ptr, *end = p + string->len;
while (p < end && isspace((unsigned char)*p))
p++;
if (p == end)
return PTRLEN_LITERAL("");
const char *start = p;
while (p < end && !isspace((unsigned char)*p))
p++;
*string = make_ptrlen(p, end - p);
return make_ptrlen(start, p - start);
}
enum { GOT_LOCAL_UID = 1, GOT_REMOTE_UID = 2 };
/*
* Open a file formatted like /proc/net/tcp{,6}, and search it for
* both ends of a particular connection.
*
* The operands 'local' and 'remote' give the expected string
* representations of the local and remote addresses of the connection
* we're looking for.
*
* Return value is the bitwise OR of 1 if we found the local end of
* the connection and 2 if we found the remote. Each output uid_t
* parameter is filled in iff the corresponding bit is set in the
* return value.
*/
static int lookup_uids_in_procnet_file(
const char *path, ptrlen local, ptrlen remote,
uid_t *local_uid, uid_t *remote_uid)
{
FILE *fp = NULL;
int toret = 0;
ptrlen line, field;
enum { GF_LOCAL = 1, GF_REMOTE = 2, GF_UID = 4 };
fp = fopen(path, "r");
if (!fp)
goto out;
/* Expected indices of fields in /proc/net/tcp* */
const int LOCAL_ADDR_INDEX = 1;
const int REMOTE_ADDR_INDEX = 2;
const int UID_INDEX = 7;
for (char *linez; (linez = chomp(fgetline(fp))) != NULL ;) {
line = ptrlen_from_asciz(linez);
int gotfields = 0;
ptrlen local_addr = PTRLEN_LITERAL("");
ptrlen remote_addr = PTRLEN_LITERAL("");
long uid = -1;
for (int i = 0; (field = get_space_separated_field(&line)).len != 0;
i++) {
if (i == LOCAL_ADDR_INDEX) {
gotfields |= GF_LOCAL;
local_addr = field;
} else if (i == REMOTE_ADDR_INDEX) {
gotfields |= GF_REMOTE;
remote_addr = field;
} else if (i == UID_INDEX) {
uid = 0;
for (const char *p = field.ptr, *end = p + field.len;
p < end; p++) {
if (!isdigit((unsigned char)*p)) {
uid = -1;
break;
}
int dval = *p - '0';
if (uid > LONG_MAX/10) {
uid = -1;
break;
}
uid *= 10;
if (uid > LONG_MAX - dval) {
uid = -1;
break;
}
uid += dval;
}
gotfields |= GF_UID;
}
}
if (gotfields == (GF_LOCAL | GF_REMOTE | GF_UID)) {
if (ptrlen_eq_ptrlen(local_addr, local) &&
ptrlen_eq_ptrlen(remote_addr, remote)) {
*local_uid = uid;
toret |= GOT_LOCAL_UID;
}
if (ptrlen_eq_ptrlen(local_addr, remote) &&
ptrlen_eq_ptrlen(remote_addr, local)) {
*remote_uid = uid;
toret |= GOT_REMOTE_UID;
}
}
sfree(linez);
}
fclose(fp);
fp = NULL;
out:
if (fp)
fclose(fp);
return toret;
}
static const char *procnet_path(int family)
{
switch (family) {
case AF_INET: return "/proc/net/tcp";
case AF_INET6: return "/proc/net/tcp6";
default: return NULL;
}
}
static char *format_sockaddr(const void *addr, int family)
{
if (family == AF_INET) {
const struct sockaddr_in *a = (const struct sockaddr_in *)addr;
assert(a->sin_family == family);
/* Linux /proc/net formats the IP address native-endian, so we
* don't use ntohl */
return dupprintf("%08X:%04X", a->sin_addr.s_addr, ntohs(a->sin_port));
} else if (family == AF_INET6) {
struct sockaddr_in6 *a = (struct sockaddr_in6 *)addr;
assert(a->sin6_family == family);
strbuf *sb = strbuf_new();
const uint32_t *addrwords = (const uint32_t *)a->sin6_addr.s6_addr;
for (int i = 0; i < 4; i++)
put_fmt(sb, "%08X", addrwords[i]);
put_fmt(sb, ":%04X", ntohs(a->sin6_port));
return strbuf_to_str(sb);
} else {
return NULL;
}
}
bool socket_peer_is_same_user(int fd)
{
struct sockaddr_storage addr;
socklen_t addrlen;
int family;
bool toret = false;
char *local = NULL, *remote = NULL;
const char *path;
addrlen = sizeof(addr);
if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) != 0)
goto out;
family = addr.ss_family;
if ((path = procnet_path(family)) == NULL)
goto out;
local = format_sockaddr(&addr, family);
if (!local)
goto out;
addrlen = sizeof(addr);
if (getpeername(fd, (struct sockaddr *)&addr, &addrlen) != 0)
goto out;
if (addr.ss_family != family)
goto out;
remote = format_sockaddr(&addr, family);
if (!remote)
goto out;
ptrlen locpl = ptrlen_from_asciz(local);
ptrlen rempl = ptrlen_from_asciz(remote);
/*
* Check that _both_ end of the socket are the uid we expect, as a
* sanity check on the /proc/net file being reasonable at all.
*/
uid_t our_uid = getuid();
uid_t local_uid = -1, remote_uid = -1;
int got = lookup_uids_in_procnet_file(
path, locpl, rempl, &local_uid, &remote_uid);
if (got == (GOT_LOCAL_UID | GOT_REMOTE_UID) &&
local_uid == our_uid && remote_uid == our_uid)
toret = true;
out:
sfree(local);
sfree(remote);
return toret;
}