Subject: Re: tftp/tftpd spec non-conformance
To: None <tech-net@netbsd.org>
From: None <itojun@iijlab.net>
List: tech-net
Date: 11/22/2000 00:23:51
> there are spec non-conformance in 4.4BSD tftpd/tftp.
> if we strictly follow RFC1350, we should allow IP address pairs to be
> switched - we should validate connection based on udp src/dst port
> number only.
>
> 4.4BSD tftpd calls connect(2), which forbids us to change IP address
> pairs. NetBSD tftpd is worse because it calls bind(2) with
> non-wildcard address as well. 4.4BSD tftp trusts server too much
> (always copies port number got from recvfrom).
>
> question - are there any other implementation that chokes, if we
> correct it?
here's a proposed patch.
itojun
Index: libexec/tftpd/tftpd.c
===================================================================
RCS file: /cvsroot/basesrc/libexec/tftpd/tftpd.c,v
retrieving revision 1.21
diff -u -r1.21 tftpd.c
--- libexec/tftpd/tftpd.c 2000/11/21 13:50:25 1.21
+++ libexec/tftpd/tftpd.c 2000/11/21 15:22:23
@@ -93,6 +93,7 @@
char ackbuf[PKTSIZE];
struct sockaddr_storage from;
int fromlen;
+struct sockaddr_storage client; /* valid peer port number */
/*
* Null-terminated directory prefix list for absolute pathname requests and
@@ -117,6 +118,7 @@
static const char *errtomsg __P((int));
static void nak __P((int));
static char *verifyhost __P((struct sockaddr *));
+static int cmpport __P((struct sockaddr *, struct sockaddr *));
static void usage __P((void));
void timer __P((int));
void sendfile __P((struct formats *));
@@ -158,7 +160,6 @@
int ch, on;
int fd = 0;
struct sockaddr_storage me;
- int len;
char *tgtuser, *tgtgroup, *ep;
uid_t curuid, tgtuid;
gid_t curgid, tgtgid;
@@ -344,29 +345,6 @@
}
}
- /*
- * remember what address this was sent to, so we can respond on the
- * same interface
- */
- len = sizeof(me);
- if (getsockname(fd, (struct sockaddr *)&me, &len) == 0) {
- switch (me.ss_family) {
- case AF_INET:
- ((struct sockaddr_in *)&me)->sin_port = 0;
- break;
- case AF_INET6:
- ((struct sockaddr_in6 *)&me)->sin6_port = 0;
- break;
- default:
- /* unsupported */
- break;
- }
- } else {
- memset(&me, 0, sizeof(me));
- me.ss_family = from.ss_family;
- me.ss_len = from.ss_len;
- }
-
alarm(0);
close(fd);
close(1);
@@ -375,14 +353,22 @@
syslog(LOG_ERR, "socket: %m");
exit(1);
}
+ /*
+ * allocate port number for my side.
+ * we shouldn't bind(2) nor connect(2) with explicit address. if we
+ * do, we would unintentionally fix IP address pairs.
+ */
+ memset(&me, 0, sizeof(me));
+ me.ss_family = from.ss_family;
+ me.ss_len = from.ss_len;
if (bind(peer, (struct sockaddr *)&me, me.ss_len) < 0) {
syslog(LOG_ERR, "bind: %m");
exit(1);
}
- if (connect(peer, (struct sockaddr *)&from, from.ss_len) < 0) {
- syslog(LOG_ERR, "connect: %m");
- exit(1);
- }
+ /*
+ * keep peer's port number, for future validation
+ */
+ client = from;
tp = (struct tftphdr *)buf;
tp->th_opcode = ntohs(tp->th_opcode);
if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
@@ -610,19 +596,27 @@
(void)setjmp(timeoutbuf);
send_data:
- if (send(peer, dp, size + 4, 0) != size + 4) {
+ if (sendto(peer, dp, size + 4, 0, (struct sockaddr *)&from,
+ fromlen) != size + 4) {
syslog(LOG_ERR, "tftpd: write: %m");
goto abort;
}
read_ahead(file, pf->f_convert);
for ( ; ; ) {
alarm(rexmtval); /* read the ack */
- n = recv(peer, ackbuf, sizeof (ackbuf), 0);
+ fromlen = sizeof(from);
+ n = recvfrom(peer, ackbuf, sizeof (ackbuf), 0,
+ (struct sockaddr *)&from, &fromlen);
alarm(0);
if (n < 0) {
syslog(LOG_ERR, "tftpd: read: %m");
goto abort;
}
+ if (!cmpport((struct sockaddr *)&from,
+ (struct sockaddr *)&client)) {
+ syslog(LOG_ERR, "tftpd: client port mismatch");
+ goto abort;
+ }
ap->th_opcode = ntohs((u_short)ap->th_opcode);
ap->th_block = ntohs((u_short)ap->th_block);
@@ -675,19 +669,27 @@
block++;
(void) setjmp(timeoutbuf);
send_ack:
- if (send(peer, ackbuf, 4, 0) != 4) {
+ if (sendto(peer, ackbuf, 4, 0, (struct sockaddr *)&from,
+ fromlen) != 4) {
syslog(LOG_ERR, "tftpd: write: %m");
goto abort;
}
write_behind(file, pf->f_convert);
for ( ; ; ) {
alarm(rexmtval);
- n = recv(peer, dp, PKTSIZE, 0);
+ fromlen = sizeof(from);
+ n = recvfrom(peer, dp, PKTSIZE, 0,
+ (struct sockaddr *)&from, &fromlen);
alarm(0);
if (n < 0) { /* really? */
syslog(LOG_ERR, "tftpd: read: %m");
goto abort;
}
+ if (!cmpport((struct sockaddr *)&from,
+ (struct sockaddr *)&client)) {
+ syslog(LOG_ERR, "tftpd: client port mismatch");
+ goto abort;
+ }
dp->th_opcode = ntohs((u_short)dp->th_opcode);
dp->th_block = ntohs((u_short)dp->th_block);
if (dp->th_opcode == ERROR)
@@ -715,16 +717,25 @@
ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */
ap->th_block = htons((u_short)(block));
- (void) send(peer, ackbuf, 4, 0);
+ (void) sendto(peer, ackbuf, 4, 0, (struct sockaddr *)&from, fromlen);
signal(SIGALRM, justquit); /* just quit on timeout */
alarm(rexmtval);
- n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */
+ fromlen = sizeof(from);
+ /* normally times out and quits */
+ n = recvfrom(peer, buf, sizeof (buf), 0, (struct sockaddr *)&from,
+ &fromlen);
alarm(0);
+ if (!cmpport((struct sockaddr *)&from, (struct sockaddr *)&client)) {
+ syslog(LOG_ERR, "tftpd: client port mismatch");
+ goto abort;
+ }
if (n >= 4 && /* if read some data */
dp->th_opcode == DATA && /* and got a data block */
block == dp->th_block) { /* then my last ack was lost */
- (void) send(peer, ackbuf, 4, 0); /* resend final ack */
+ /* resend final ack */
+ (void) sendto(peer, ackbuf, 4, 0, (struct sockaddr *)&from,
+ fromlen);
}
abort:
return;
@@ -791,7 +802,8 @@
}
length = strlen(tp->th_msg);
msglen = &tp->th_msg[length + 1] - buf;
- if (send(peer, buf, msglen, 0) != msglen)
+ if (sendto(peer, buf, msglen, 0, (struct sockaddr *)&from,
+ fromlen) != msglen)
syslog(LOG_ERR, "nak: %m");
}
@@ -804,4 +816,21 @@
if (getnameinfo(fromp, fromp->sa_len, hbuf, sizeof(hbuf), NULL, 0, 0))
strlcpy(hbuf, "?", sizeof(hbuf));
return hbuf;
+}
+
+static int
+cmpport(sa, sb)
+ struct sockaddr *sa;
+ struct sockaddr *sb;
+{
+ char a[NI_MAXSERV], b[NI_MAXSERV];
+
+ if (getnameinfo(sa, sa->sa_len, NULL, 0, a, sizeof(a), NI_NUMERICSERV))
+ return 0;
+ if (getnameinfo(sb, sb->sa_len, NULL, 0, b, sizeof(b), NI_NUMERICSERV))
+ return 0;
+ if (strcmp(a, b) != 0)
+ return 0;
+
+ return 1;
}
Index: usr.bin/tftp/tftp.c
===================================================================
RCS file: /cvsroot/basesrc/usr.bin/tftp/tftp.c,v
retrieving revision 1.14
diff -u -r1.14 tftp.c
--- usr.bin/tftp/tftp.c 2000/11/21 14:58:21 1.14
+++ usr.bin/tftp/tftp.c 2000/11/21 15:22:25
@@ -62,6 +62,7 @@
#include <stdio.h>
#include <string.h>
#include <unistd.h>
+#include <netdb.h>
#include "extern.h"
#include "tftpsubs.h"
@@ -86,6 +87,7 @@
static void stopclock __P((void));
static void timer __P((int));
static void tpacket __P((const char *, struct tftphdr *, int));
+static int cmpport __P((struct sockaddr *, struct sockaddr *));
/*
* Send the requested file.
@@ -106,6 +108,7 @@
int fromlen;
FILE *file;
struct sockaddr_storage peer;
+ struct sockaddr_storage serv; /* valid server port number */
startclock(); /* start stat's clock */
dp = r_init(); /* reset fillbuf/read-ahead code */
@@ -115,6 +118,7 @@
block = 0;
amount = 0;
memcpy(&peer, &peeraddr, peeraddr.ss_len);
+ memset(&serv, 0, sizeof(serv));
signal(SIGALRM, timer);
do {
@@ -154,19 +158,14 @@
warn("recvfrom");
goto abort;
}
- switch (peer.ss_family) { /* added */
- case AF_INET:
- ((struct sockaddr_in *)&peer)->sin_port =
- ((struct sockaddr_in *)&from)->sin_port;
- break;
- case AF_INET6:
- ((struct sockaddr_in6 *)&peer)->sin6_port =
- ((struct sockaddr_in6 *)&from)->sin6_port;
- break;
- default:
- /* unsupported */
- break;
+ if (!serv.ss_family)
+ serv = from;
+ else if (!cmpport((struct sockaddr *)&serv,
+ (struct sockaddr *)&from)) {
+ warn("server port mismatch");
+ goto abort;
}
+ peer = from;
if (trace)
tpacket("received", ap, n);
/* should verify packet came from server */
@@ -227,6 +226,7 @@
FILE *file;
volatile int convert; /* true if converting crlf -> lf */
struct sockaddr_storage peer;
+ struct sockaddr_storage serv; /* valid server port number */
startclock();
dp = w_init();
@@ -237,6 +237,7 @@
firsttrip = 1;
amount = 0;
memcpy(&peer, &peeraddr, peeraddr.ss_len);
+ memset(&serv, 0, sizeof(serv));
signal(SIGALRM, timer);
do {
@@ -273,19 +274,14 @@
warn("recvfrom");
goto abort;
}
- switch (peer.ss_family) { /* added */
- case AF_INET:
- ((struct sockaddr_in *)&peer)->sin_port =
- ((struct sockaddr_in *)&from)->sin_port;
- break;
- case AF_INET6:
- ((struct sockaddr_in6 *)&peer)->sin6_port =
- ((struct sockaddr_in6 *)&from)->sin6_port;
- break;
- default:
- /* unsupported */
- break;
+ if (!serv.ss_family)
+ serv = from;
+ else if (!cmpport((struct sockaddr *)&serv,
+ (struct sockaddr *)&from)) {
+ warn("server port mismatch");
+ goto abort;
}
+ peer = from;
if (trace)
tpacket("received", dp, n);
/* should verify client address */
@@ -499,4 +495,21 @@
longjmp(toplevel, -1);
}
longjmp(timeoutbuf, 1);
+}
+
+static int
+cmpport(sa, sb)
+ struct sockaddr *sa;
+ struct sockaddr *sb;
+{
+ char a[NI_MAXSERV], b[NI_MAXSERV];
+
+ if (getnameinfo(sa, sa->sa_len, NULL, 0, a, sizeof(a), NI_NUMERICSERV))
+ return 0;
+ if (getnameinfo(sb, sb->sa_len, NULL, 0, b, sizeof(b), NI_NUMERICSERV))
+ return 0;
+ if (strcmp(a, b) != 0)
+ return 0;
+
+ return 1;
}