Added rad_recv_header() function, which calls MSG_PEEK on the
authoraland <aland>
Fri, 20 Apr 2007 08:48:54 +0000 (08:48 +0000)
committeraland <aland>
Fri, 20 Apr 2007 08:48:54 +0000 (08:48 +0000)
socket to get the header, and the source IP.  This allows us
to perform some basic sanity checks, like "known client" before
we allocate memory for the packet.

This slows the server down slightly for normal cases, but can
greatly improve its robustness to DoS attacks.  As of now, it
logs *nothing* and allocates *no* memory on a DoS, so it should
be able to deal with them pretty well.

The rad_recv_header() function also returns the size of the packet
(taken from the header), which can permit us in the future to
minimize the number of memory allocations we make.

src/include/libradius.h
src/lib/radius.c
src/main/listen.c

index 46e861b..4f45850 100644 (file)
@@ -264,6 +264,8 @@ void lrad_hmac_sha1(const uint8_t *text, int text_len,
 int            rad_send(RADIUS_PACKET *, const RADIUS_PACKET *, const char *secret);
 int            rad_packet_ok(RADIUS_PACKET *packet);
 RADIUS_PACKET  *rad_recv(int fd);
+ssize_t rad_recv_header(int sockfd, lrad_ipaddr_t *src_ipaddr, int *src_port,
+                       int *code);
 int            rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original,
                           const char *secret);
 int            rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original, const char *secret);
index 78fb508..30bdc5e 100644 (file)
@@ -217,8 +217,88 @@ static int rad_sendto(int sockfd, void *data, size_t data_len, int flags,
 }
 
 
+ssize_t rad_recv_header(int sockfd, lrad_ipaddr_t *src_ipaddr, int *src_port,
+                       int *code)
+{
+       ssize_t                 data_len, packet_len;
+       uint8_t                 header[4];
+       struct sockaddr_storage src;
+       socklen_t               sizeof_src = sizeof(src);
+
+       data_len = recvfrom(sockfd, header, sizeof(header), MSG_PEEK,
+                           (struct sockaddr *)&src, &sizeof_src);
+       if (data_len < 0) return -1;
+
+       /*
+        *      Too little data is available, discard the packet.
+        */
+       if (data_len < 4) {
+               recvfrom(sockfd, header, sizeof(header), 0,
+                        (struct sockaddr *)&src, &sizeof_src);
+               return 1;
+
+       } else {                /* we got 4 bytes of data. */
+               /*
+                *      See how long the packet says it is.
+                */
+               packet_len = (header[2] * 256) + header[3];
+
+               /*
+                *      The length in the packet says it's less than
+                *      a RADIUS header length: discard it.
+                */
+               if (packet_len < AUTH_HDR_LEN) {
+                       recvfrom(sockfd, header, sizeof(header), 0,
+                                (struct sockaddr *)&src, &sizeof_src);
+                       return 1;
+
+                       /*
+                        *      Enforce RFC requirements, for sanity.
+                        *      Anything after 4k will be discarded.
+                        */
+               } else if (packet_len > MAX_PACKET_LEN) {
+                       recvfrom(sockfd, header, sizeof(header), 0, 
+                                (struct sockaddr *)&src, &sizeof_src);
+                       return 1;
+               }
+       }
+
+       if (src.ss_family == AF_INET) {
+               struct sockaddr_in      *s4;
+
+               s4 = (struct sockaddr_in *)&src;
+               src_ipaddr->af = AF_INET;
+               src_ipaddr->ipaddr.ip4addr = s4->sin_addr;
+               *src_port = ntohs(s4->sin_port);
+
+#ifdef HAVE_STRUCT_SOCKADDR_IN6
+       } else if (src.ss_family == AF_INET6) {
+               struct sockaddr_in6     *s6;
+
+               s6 = (struct sockaddr_in6 *)&src;
+               src_ipaddr->af = AF_INET6;
+               src_ipaddr->ipaddr.ip6addr = s6->sin6_addr;
+               *src_port = ntohs(s6->sin6_port);
+
+#endif
+       } else {
+               recvfrom(sockfd, header, sizeof(header), 0, 
+                        (struct sockaddr *)&src, &sizeof_src);
+               return 1;
+       }
+
+       *code = header[0];
+
+       /*
+        *      The packet says it's this long, but the actual UDP
+        *      size could still be smaller.
+        */
+       return packet_len;
+}
+
+
 /*
- *     Wrapper for recvfrom, which handles recvfromto, IPv6, and all
+ *     wrapper for recvfrom, which handles recvfromto, IPv6, and all
  *     possible combinations.
  */
 static ssize_t rad_recvfrom(int sockfd, uint8_t **pbuf, int flags,
@@ -2414,12 +2494,12 @@ int rad_pwdecode(char *passwd, int pwlen, const char *secret,
                        lrad_MD5Final(digest, &context);
 
                        context = old;
-                       lrad_MD5Update(&context, passwd, AUTH_PASS_LEN);
+                       if (pwlen > AUTH_PASS_LEN) lrad_MD5Update(&context, passwd, AUTH_PASS_LEN);
                } else {
                        lrad_MD5Final(digest, &context);
 
                        context = old;
-                       lrad_MD5Update(&context, passwd + n, AUTH_PASS_LEN);
+                       if (pwlen > (n + AUTH_PASS_LEN)) lrad_MD5Update(&context, passwd + n, AUTH_PASS_LEN);
                }
                
                for (i = 0; i < AUTH_PASS_LEN; i++) {
@@ -2805,6 +2885,7 @@ void rad_free(RADIUS_PACKET **radius_packet_ptr)
        radius_packet = *radius_packet_ptr;
 
        free(radius_packet->data);
+
        pairfree(&radius_packet->vps);
 
        free(radius_packet);
index cdbb1a9..888811c 100644 (file)
@@ -351,38 +351,42 @@ static int proxy_socket_send(rad_listen_t *listener, REQUEST *request)
 static int auth_socket_recv(rad_listen_t *listener,
                            RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
 {
+       ssize_t         rcode;
+       int             code, src_port;
        RADIUS_PACKET   *packet;
        RAD_REQUEST_FUNP fun = NULL;
        char            buffer[128];
        RADCLIENT       *client;
+       lrad_ipaddr_t   src_ipaddr;
 
-       packet = rad_recv(listener->fd);
-       if (!packet) {
-               RAD_SNMP_TYPE_INC(listener, total_requests);
+       rcode = rad_recv_header(listener->fd, &src_ipaddr, &src_port, &code);
+       if (rcode < 0) return 0;
+
+       RAD_SNMP_TYPE_INC(listener, total_requests);
+
+       if (rcode < 20) {       /* AUTH_HDR_LEN */
                RAD_SNMP_TYPE_INC(listener, total_malformed_requests);
-               radlog(L_ERR, "%s", librad_errstr);
                return 0;
        }
-       
-       RAD_SNMP_TYPE_INC(listener, total_requests);
 
        if ((client = client_listener_find(listener,
-                                          &packet->src_ipaddr)) == NULL) {
+                                          &src_ipaddr)) == NULL) {
                RAD_SNMP_TYPE_INC(listener, total_invalid_requests);
                
-               radlog(L_ERR, "Ignoring request from unknown client %s port %d",
-                      inet_ntop(packet->src_ipaddr.af,
-                                &packet->src_ipaddr.ipaddr,
-                                buffer, sizeof(buffer)),
-                      packet->src_port);
-               rad_free(&packet);
+               /*
+                *      This is debugging rather than logging, so that
+                *      DoS attacks don't affect us.
+                */
+               DEBUG("Ignoring request from unknown client %s port %d",
+                     inet_ntop(src_ipaddr.af, &src_ipaddr.ipaddr,
+                               buffer, sizeof(buffer)), src_port);
                return 0;
        }
 
        /*
         *      Some sanity checks, based on the packet code.
         */
-       switch(packet->code) {
+       switch(code) {
        case PW_AUTHENTICATION_REQUEST:
                RAD_SNMP_CLIENT_INC(listener, client, requests);
                fun = rad_authenticate;
@@ -393,7 +397,6 @@ static int auth_socket_recv(rad_listen_t *listener,
                        RAD_SNMP_TYPE_INC(listener, total_packets_dropped);
                        RAD_SNMP_CLIENT_INC(listener, client, packets_dropped);
                        DEBUG("WARNING: Ignoring Status-Server request due to security configuration");
-                       rad_free(&packet);
                        return 0;
                }
                fun = rad_status_server;
@@ -403,15 +406,23 @@ static int auth_socket_recv(rad_listen_t *listener,
                RAD_SNMP_INC(rad_snmp.auth.total_unknown_types);
                RAD_SNMP_CLIENT_INC(listener, client, unknown_types);
                
-               radlog(L_ERR, "Invalid packet code %d sent to authentication port from client %s port %d "
-                      "- ID %d : IGNORED",
-                      packet->code, client->shortname,
-                      packet->src_port, packet->id);
-               rad_free(&packet);
+               DEBUG("Invalid packet code %d sent to authentication port from client %s port %d : IGNORED",
+                     code, client->shortname, src_port);
                return 0;
                break;
        } /* switch over packet types */
        
+       /*
+        *      Now that we've sanity checked everything, receive the
+        *      packet.
+        */
+       packet = rad_recv(listener->fd);
+       if (!packet) {
+               RAD_SNMP_TYPE_INC(listener, total_malformed_requests);
+               radlog(L_ERR, "%s", librad_errstr);
+               return 0;
+       }
+       
        if (!received_request(listener, packet, prequest, client)) {
                RAD_SNMP_TYPE_INC(listener, total_packets_dropped);
                RAD_SNMP_CLIENT_INC(listener, client, packets_dropped);
@@ -428,36 +439,43 @@ static int auth_socket_recv(rad_listen_t *listener,
  *     Receive packets from an accounting socket
  */
 static int acct_socket_recv(rad_listen_t *listener,
-       RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
+                           RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
 {
+       ssize_t         rcode;
+       int             code, src_port;
        RADIUS_PACKET   *packet;
        RAD_REQUEST_FUNP fun = NULL;
        char            buffer[128];
        RADCLIENT       *client;
+       lrad_ipaddr_t   src_ipaddr;
        
-       packet = rad_recv(listener->fd);
-       if (!packet) {
-               RAD_SNMP_TYPE_INC(listener, total_requests);
+       rcode = rad_recv_header(listener->fd, &src_ipaddr, &src_port, &code);
+       if (rcode < 0) return 0;
+
+       RAD_SNMP_TYPE_INC(listener, total_requests);
+
+       if (rcode < 20) {       /* AUTH_HDR_LEN */
                RAD_SNMP_TYPE_INC(listener, total_malformed_requests);
-               radlog(L_ERR, "%s", librad_errstr);
                return 0;
        }
-       
-       RAD_SNMP_TYPE_INC(listener, total_requests);
 
        if ((client = client_listener_find(listener,
-                                          &packet->src_ipaddr)) == NULL) {
+                                          &src_ipaddr)) == NULL) {
                RAD_SNMP_TYPE_INC(listener, total_invalid_requests);
                
-               radlog(L_ERR, "Ignoring request from unknown client %s port %d",
-                      inet_ntop(packet->src_ipaddr.af,
-                                &packet->src_ipaddr.ipaddr,
-                                buffer, sizeof(buffer)),
-                      packet->src_port);
-               rad_free(&packet);
+               /*
+                *      This is debugging rather than logging, so that
+                *      DoS attacks don't affect us.
+                */
+               DEBUG("Ignoring request from unknown client %s port %d",
+                     inet_ntop(src_ipaddr.af, &src_ipaddr.ipaddr,
+                               buffer, sizeof(buffer)), src_port);
                return 0;
        }
 
+       /*
+        *      Some sanity checks, based on the packet code.
+        */
        switch(packet->code) {
        case PW_ACCOUNTING_REQUEST:
                RAD_SNMP_CLIENT_INC(listener, client, requests);
@@ -470,7 +488,6 @@ static int acct_socket_recv(rad_listen_t *listener,
                        RAD_SNMP_CLIENT_INC(listener, client, unknown_types);
 
                        DEBUG("WARNING: Ignoring Status-Server request due to security configuration");
-                       rad_free(&packet);
                        return 0;
                }
                fun = rad_status_server;
@@ -483,17 +500,24 @@ static int acct_socket_recv(rad_listen_t *listener,
                RAD_SNMP_TYPE_INC(listener, total_unknown_types);
                RAD_SNMP_CLIENT_INC(listener, client, unknown_types);
 
-               radlog(L_ERR, "Invalid packet code %d sent to a accounting port "
-                      "from client %s port %d - ID %d : IGNORED",
-                      packet->code, client->shortname,
-                      packet->src_port, packet->id);
-               rad_free(&packet);
+               DEBUG("Invalid packet code %d sent to a accounting port from client %s port %d : IGNORED",
+                     code, client->shortname, src_port);
                return 0;
-       }
+       } /* switch over packet types */
 
        /*
-        *      FIXME: Accounting duplicates should be handled
-        *      differently than authentication duplicates.
+        *      Now that we've sanity checked everything, receive the
+        *      packet.
+        */
+       packet = rad_recv(listener->fd);
+       if (!packet) {
+               RAD_SNMP_TYPE_INC(listener, total_malformed_requests);
+               radlog(L_ERR, "%s", librad_errstr);
+               return 0;
+       }
+       
+       /*
+        *      There can be no duplicate accounting packets.
         */
        if (!received_request(listener, packet, prequest, client)) {
                RAD_SNMP_TYPE_INC(listener, total_packets_dropped);