Commit 2d9daab0 authored by Mike Hibler's avatar Mike Hibler

Fix a capserver vulnerability reported by John Hickey.

Validate those SQL args!

NOTE: we also ensure that the reporting node is listed as a legit tip server
in the tipservers table. This means that capture may stop working on nodes
whose servers are not in the table! SQL update 291 will add any servers
listed in tiplines entries that are not in tipservers to prevent this breakage.
parent d859def5
/*
* EMULAB-COPYRIGHT
* Copyright (c) 2000-2010 University of Utah and the Flux Group.
* Copyright (c) 2000-2012 University of Utah and the Flux Group.
* All rights reserved.
*/
......@@ -22,6 +22,7 @@
#include <sys/time.h>
#include <signal.h>
#include <grp.h>
#include <netdb.h>
#include "capdecls.h"
#include "config.h"
#include "tbdb.h"
......@@ -35,6 +36,15 @@ char *Pidname;
void sigterm(int);
void cleanup(void);
static struct server {
char *name;
struct in_addr ip;
} *servers;
static int nservers;
int regexinit(void);
int validwhoami(whoami_t *wai, int verbose);
char *usagestr =
"usage: capserver [-d] [-p #]\n"
" -d Turn on debugging.\n"
......@@ -54,7 +64,8 @@ main(int argc, char **argv)
MYSQL_RES *res;
MYSQL_ROW row;
int tcpsock, ch;
int length, i;
int i, nrows;
unsigned int length;
struct sockaddr_in name;
struct timeval timeout;
struct group *group;
......@@ -91,6 +102,11 @@ main(int argc, char **argv)
exit(1);
}
if (!regexinit()) {
syslog(LOG_ERR, "Cannot compile REs!");
exit(1);
}
sigemptyset(&actionsigmask);
sigaddset(&actionsigmask, SIGINT);
sigaddset(&actionsigmask, SIGTERM);
......@@ -109,6 +125,52 @@ main(int argc, char **argv)
}
admingid = group->gr_gid;
/*
* Find all the allowed tipserver machines and resolve their names.
*/
res = mydb_query("select server from tipservers", 1);
if (!res) {
syslog(LOG_ERR, "DB Error getting tipservers from DB!");
exit(1);
}
if ((nrows = (int)mysql_num_rows(res)) == 0) {
syslog(LOG_ERR, "No tipservers in DB!");
mysql_free_result(res);
exit(1);
}
servers = calloc(nrows, sizeof(struct sockaddr_in));
if (servers == NULL) {
syslog(LOG_ERR, "No memory for %d tipservers!", nrows);
mysql_free_result(res);
exit(1);
}
nservers = 0;
while (nrows > 0) {
struct hostent *he;
nrows--;
row = mysql_fetch_row(res);
if ((he = gethostbyname(row[0])) == NULL) {
syslog(LOG_WARNING,
"Could not resolve hostname '%s', ignored",
row[0]);
continue;
}
if (he->h_addrtype != AF_INET ||
he->h_length != sizeof(struct in_addr)) {
syslog(LOG_WARNING,
"Unknown addrtype/size for '%s', ignored",
row[0]);
continue;
}
servers[nservers].name = strdup(row[0]);
servers[nservers].ip = *(struct in_addr *)he->h_addr;
nservers++;
}
mysql_free_result(res);
/*
* Setup TCP socket
*/
......@@ -160,14 +222,15 @@ main(int argc, char **argv)
while (1) {
struct sockaddr_in client;
int clientsock, length = sizeof(client);
int cc, port;
int clientsock;
int cc, port, srv;
whoami_t whoami;
unsigned char node_id[64];
char node_id[64];
tipowner_t tipown;
void *reply = &tipown;
size_t reply_size = sizeof(tipown);
length = sizeof(client);
if ((clientsock = accept(tcpsock,
(struct sockaddr *)&client,
&length)) < 0) {
......@@ -183,11 +246,24 @@ main(int argc, char **argv)
syslog(LOG_INFO, "%s connected from port %d",
inet_ntoa(client.sin_addr), port);
/*
* Check IP address of server. Must be in tipservers table.
*/
for (srv = 0; srv < nservers; srv++)
if (client.sin_addr.s_addr == servers[srv].ip.s_addr)
break;
if (srv == nservers) {
syslog(LOG_ERR, "%s: Illegal server ignored.",
inet_ntoa(client.sin_addr));
goto done;
}
/*
* Check port number of sender. Must be a reserved port.
*/
if (port >= IPPORT_RESERVED || port < IPPORT_RESERVED / 2) {
syslog(LOG_ERR, "Illegal port! Ignoring.");
syslog(LOG_ERR, "%s: Illegal port %d Ignoring.",
inet_ntoa(client.sin_addr), port);
goto done;
}
......@@ -209,16 +285,23 @@ main(int argc, char **argv)
}
/*
* Read in the whoami info.
* Read and validate the whoami info.
*/
if ((cc = read(clientsock, &whoami, sizeof(whoami))) <= 0) {
if (cc < 0)
syslog(LOG_ERR, "Reading request: %m");
syslog(LOG_ERR, "Connection aborted (read)");
syslog(LOG_ERR, "%s: Connection aborted (read)",
inet_ntoa(client.sin_addr));
goto done;
}
if (cc != sizeof(whoami)) {
syslog(LOG_ERR, "Wrong byte count (read)!");
syslog(LOG_ERR, "%s: Wrong byte count (read)!",
inet_ntoa(client.sin_addr));
goto done;
}
if (!validwhoami(&whoami, debug)) {
syslog(LOG_ERR, "%s: Invalid whoami info ignored",
inet_ntoa(client.sin_addr));
goto done;
}
......@@ -237,12 +320,19 @@ main(int argc, char **argv)
goto done;
}
if ((int)mysql_num_rows(res) == 0) {
syslog(LOG_ERR, "No tipline info for %s!",
whoami.name);
syslog(LOG_ERR, "%s: No tipline info for %s!",
inet_ntoa(client.sin_addr), whoami.name);
mysql_free_result(res);
goto done;
}
row = mysql_fetch_row(res);
if (strcmp(row[0], servers[srv].name) != 0) {
syslog(LOG_WARNING,
"%s: caller ('%s') is not the right server "
"('%s') for node '%s'",
inet_ntoa(client.sin_addr), servers[srv].name,
row[0], row[1]);
}
strcpy(node_id, row[1]);
port = -1;
sscanf(row[2], "%d", &port);
......@@ -343,3 +433,49 @@ cleanup(void)
if (Pidname)
(void) unlink(Pidname);
}
#include <regex.h>
static regex_t re_nodeid, re_keystr;
int
regexinit(void)
{
/* XXX get these from DB */
if (regcomp(&re_nodeid, "^[-[:alnum:]_]+$",
REG_EXTENDED|REG_ICASE|REG_NOSUB))
return 0;
if (regcomp(&re_keystr, "^[0-9a-f]+$",
REG_EXTENDED|REG_ICASE|REG_NOSUB))
return 0;
return 1;
}
int
validwhoami(whoami_t *wai, int verbose)
{
wai->name[sizeof(wai->name)-1] = '\0';
if (regexec(&re_nodeid, wai->name, 0, NULL, 0)) {
if (verbose)
syslog(LOG_ERR, "Invalid node name string");
return 0;
}
if (wai->portnum != -1 && (wai->portnum < 1 || wai->portnum > 65535)) {
if (verbose)
syslog(LOG_ERR, "Invalid port number");
return 0;
}
if (wai->key.keylen < 0 || wai->key.keylen >= sizeof(wai->key.key)) {
if (verbose)
syslog(LOG_ERR, "Invalid key length");
return 0;
}
wai->key.key[wai->key.keylen] = '\0';
if (regexec(&re_keystr, wai->key.key, 0, NULL, 0)) {
if (verbose)
syslog(LOG_ERR, "Invalid key string");
return 0;
}
return 1;
}
#
# Compare tipservers table vs. all servers listed in tiplines and warn
# if there is a discrepancy.
#
# With the latest version of capserver, only nodes listed in tipservers
# are allowed to report info.
#
use strict;
use libdb;
my $impotent = 0;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
my %servers = ();
my $query_result =
DBQueryFatal("select server from tipservers");
while (my $row = $query_result->fetchrow_hashref()) {
my $server = $row->{'server'};
if (!defined($server) || $server eq "") {
print STDERR "*** WARNING: NULL tipservers entry ignored\n";
next;
}
$servers{$server} = 0;
}
$query_result =
DBQueryFatal("select server from tiplines group by server");
while (my $row = $query_result->fetchrow_hashref()) {
my $server = $row->{'server'};
if (!defined($server) || $server eq "") {
print STDERR "*** WARNING: NULL server in tiplines entries!\n";
next;
}
if (!exists($servers{$server})) {
print STDERR "Tip server '$server' has 'tiplines' entries ".
"but no 'tipservers' entry\n";
print STDERR " Adding entry...\n";
DBQueryFatal("INSERT INTO tipservers VALUES ('$server')");
} else {
$servers{$server} = 1;
}
}
foreach my $server (keys %servers) {
if ($servers{$server} == 0) {
print STDERR "*** WARNING: no 'tiplines' entries associated with ".
"server '$server'; consider removing from 'tipservers'\n";
}
}
return 0;
}
1;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment