xorl %eax, %eax

CVE-2007-2172: Linux RTN_MAX typo

leave a comment »

This is a good fellow from the past. It was discovered back in March of 2007 and later patched by David S. Miller [1, 2]. I personally like these bugs because it were result of common typo on simple codes such as variable declarations. This issue affects the Linux kernel 2.6 up to 2.6.21-rc6 and 2.4 tree up to 2.4.35. The first vulnerable code is at net/decnet/dn_fib.c. This file contains a Forwarding Information Base (FIB – RFC3222) DECnet protocol implementation for the Linux kernel. The code presented here has been taken from Linux kernel 2.6.20. So, the first typo is here:

62 static struct
63 {
64         int error;
65         u8 scope;
66 } dn_fib_props[RTA_MAX+1] = {
67         [RTN_UNSPEC] =      { .error = 0,       .scope = RT_SCOPE_NOWHERE },
68         [RTN_UNICAST] =     { .error = 0,       .scope = RT_SCOPE_UNIVERSE },

As you can see here we have a declaration and initialization of a structure named dn_fib_props[] (which stands for DECnet Forwarding Information Base Properties) which has size of RTA_MAX+1. This value is retrieved from include/linux/rtnetlink.h as you can see here:

269 #define RTA_MAX (__RTA_MAX - 1)

This element, __RTA_MAX is member of the rtattr_type_t (routing attributes type) enumeration structure which is also included in the same header file:

248 enum rtattr_type_t
249 {
250         RTA_UNSPEC,
251         RTA_DST,
252         RTA_SRC,
253         RTA_IIF,
254         RTA_OIF,
255         RTA_GATEWAY,
256         RTA_PRIORITY,
257         RTA_PREFSRC,
258         RTA_METRICS,
259         RTA_MULTIPATH,
260         RTA_PROTOINFO,
261         RTA_FLOW,
262         RTA_CACHEINFO,
263         RTA_SESSION,
264         RTA_MP_ALGO,
265         RTA_TABLE,
266         __RTA_MAX
267 }; 

Nothing seems to be wrong with the above code on a first quick look. But a closer look at the initialization function reveals a mistake. Here is the initialization function:

272 struct dn_fib_info *dn_fib_create_info(const struct rtmsg *r, struct dn_kern_rta *rta, const struct nlmsghdr *nlh, int *errp)
273 {
274         int err;
275         struct dn_fib_info *fi = NULL;
276         struct dn_fib_info *ofi;
277         int nhs = 1;

This function has various user controlled data and it can easily be reached using netlink API. The first argument is the usual rtnetlink message structure which is located at include/linux/rtnetlink.h. This is almost completely user controlled. The interesting elements for us are:

141 struct rtmsg
142 {
143         unsigned char           rtm_family;
144         unsigned char           rtm_dst_len;
150         unsigned char           rtm_scope;      /* See below */ 
151         unsigned char           rtm_type;       /* See below    */
154 };

Some of you might say that “Of course we can control the message type of our packets!“. Well, that’s true but what legitimate message types are available to rtnetlink anyway?

156 /* rtm_type */
158 enum
159 {
160         RTN_UNSPEC,
161         RTN_UNICAST,            /* Gateway or direct route      */
162         RTN_LOCAL,              /* Accept locally               */
163         RTN_BROADCAST,          /* Accept locally as broadcast,
164                                    send as broadcast */
165         RTN_ANYCAST,            /* Accept locally as broadcast,
166                                    but send as unicast */
167         RTN_MULTICAST,          /* Multicast route              */
168         RTN_BLACKHOLE,          /* Drop                         */
169         RTN_UNREACHABLE,        /* Destination is unreachable   */
170         RTN_PROHIBIT,           /* Administratively prohibited  */
171         RTN_THROW,              /* Not in this table            */
172         RTN_NAT,                /* Translate this address       */
173         RTN_XRESOLVE,           /* Use external resolver        */
174         __RTN_MAX
175 };
177 #define RTN_MAX (__RTN_MAX - 1) 

Yeah, that’s the typo. The structure was declared of size RTA_MAX but it is used to store RTN_MAX elements, so is there any way we can harm the index of the array? Definitely, the next lines of the dn_fib_create_info() function are:

279         if (dn_fib_props[r->rtm_type].scope > r->rtm_scope)
280                 goto err_inval;

It directly uses our message type without any check if it’s valid type, we can create a packet with any number from 0 to 255 (rtm_type is of unsigned character data type) and thus access arbitrary memory locations. To fix this D. Miller wrote the following patch:

        u8 scope;
-} dn_fib_props[RTA_MAX+1] = {
+} dn_fib_props[RTN_MAX+1] = {
        [RTN_UNSPEC] =      { .error = 0,       .scope = RT_SCOPE_NOWHERE },

Which fixes the typo on the declaration and also:

+       if (r->rtm_type > RTN_MAX)
+               goto err_inval;
        if (dn_fib_props[r->rtm_type].scope > r->rtm_scope)
                goto err_inval;

Which limits the user input for message type to RTN_MAX. The exploitation of this kind of bug is not trivial, you have to make index get a value without any access violation that will be later processed on the function in a way that can result in something useful, in most cases these class of vulnerabilities can easily turn into kernel memory information leakage but it is always dependent on the bug. In some cases, it can even lead to arbitrary code execution in the context of the kernel.

The next bug under this CVE is found at net/ipv4/fib_frontend.c. This sub-system is used for Forwarding Information Base (FIB) just like the previous one but for IPv4 instead of DECnet protocol. Here is the vulnerable function:

471 static int rtm_to_fib_config(struct sk_buff *skb, struct nlmsghdr *nlh,
472                              struct fib_config *cfg)
473 {
474         struct nlattr *attr;
475         int err, remaining;
476         struct rtmsg *rtm;

Where once again, structure fib_config (which is located at include/net/ip_fib.h) has some user controlled elements such as the FIB type:

23 struct fib_config {
24         u8                      fc_dst_len;
25         u8                      fc_tos;
28         u8                      fc_type;
44        struct nl_info          fc_nlinfo;
45 };

And guess what? rtm_to_fib_config() trusts the user input and immediately uses it to validate the netlink message with its attributes using the macro nlmsg_for_each_attr() like this:

478         err = nlmsg_validate(nlh, sizeof(*rtm), RTA_MAX, rtm_ipv4_policy);
479         if (err < 0)
480                 goto errout;
497         nlmsg_for_each_attr(attr, nlh, sizeof(struct rtmsg), remaining) {

Of course this can lead to similar exploitable situations as the previously discussed vulnerability. The patch for this bug was also straightforward:

+       if (cfg->fc_type > RTN_MAX) {
+               err = -EINVAL;
+               goto errout;
+       }
        nlmsg_for_each_attr(attr, nlh, sizeof(struct rtmsg), remaining) {

Just check whether the type is greater than RTN_MAX. At last, under this CVE ID there was another small typo. It was the exact same one as the DECnet’s dn_fib_props[] (DECnet FIB properties) structure but for IPv4 netlink packets. So.. here is the vulnerable code from net/ipv4/fib_semantics.c:

91         u8      scope;
92 } fib_props[RTA_MAX + 1] = {
93         {
94                 .error  = 0,

And here is the patch for this bug:

        u8      scope;
-} fib_props[RTA_MAX + 1] = {
+} fib_props[RTN_MAX + 1] = {

The type is checked at rtm_to_fib_config() which stores an rtm structure to an fib_config. So, at a first look the fib_create_info() from net/ipv4/fib_semantics.c might also seem vulnerable since it is something like this:

678 struct fib_info *fib_create_info(struct fib_config *cfg)
679 {
680         int err;
681         struct fib_info *fi = NULL;
682         struct fib_info *ofi;
683         int nhs = 1;
685         /* Fast check to catch the most weird cases */
686         if (fib_props[cfg->fc_type].scope > cfg->fc_scope)
687                 goto err_inval;

Which looks exactly as the vulnerable DECnet’s dn_fib_create_info() but this time the structure passed as argument is already checked for values greater than RTN_MAX at rtm_to_fib_config(). I’ve already said that this CVE was interesting even if it’s old. :P

Written by xorl

January 12, 2009 at 14:16

Posted in linux, vulnerabilities

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s