Bug 10484 - getaddrinfo segfaults if /etc/hosts has a long line
Summary: getaddrinfo segfaults if /etc/hosts has a long line
Status: RESOLVED FIXED
Alias: None
Product: glibc
Classification: Unclassified
Component: libc (show other bugs)
Version: 2.13
: P2 minor
Target Milestone: ---
Assignee: Ulrich Drepper
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2009-08-05 15:22 UTC by Lars Wirzenius
Modified: 2014-07-01 07:26 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
fweimer: security-


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Lars Wirzenius 2009-08-05 15:22:27 UTC
If /etc/hosts contains a long line (thousands of bytes), getaddrinfo causes a
segmentation fault. A small program to test:

-- 8< ---
#include <stddef.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int main(void)
{
    struct addrinfo *result;
    getaddrinfo("localhost", NULL, NULL, &result);
    return 0;
}
--- 8< ---

On a system with short lines in /etc/hosts, the above program exits normally. If
/etc/hosts has a very long line (5500 bytes is sufficiently long), it
segmentation faults.

I think this is due to using alloca and extend_alloca to hold the line when
parsing the file, which leads to a stack overflow, which then results in the
kernel sending a SIGSEGV to the program. The parsing code is not set up to
handle that. Unfortunately, I am too stupid to provide a patch to fix this.

My test /etc/hosts file has IPv6 addresses commented out, so the segmentation
fault happens in sysdeps/posix/getaddrinfo.c, function gaih_inet, around line
531, on this line:

                  rc = __gethostbyname2_r (name, family, &th, tmpbuf,
                                           tmpbuflen, &h, &herrno);

My stack limit is 8 megabytes.

This was originally filed as a bug in Ubuntu, and applies to both versions 2.9
and 2.10 in that distribution. I have compared the source file against the
current version in git, and it has no relevant changes. (I was unable to set up
a chroot to actually test the current git version, sorry.)

Original bug: https://bugs.launchpad.net/ubuntu/+source/eglibc/+bug/386791
Comment 1 Fibonacci Prower 2009-08-05 19:28:48 UTC
This also happens on plain x86 processors. The original bug was found on a PIV.

Perhaps it shouldn't be marked as x86_64.
Comment 2 Ulrich Drepper 2009-10-30 05:38:19 UTC
You have to be much more precise.  I cannot reproduce any problem and your
description doesn't say where the stack overflow is supposed to happen.
Comment 3 Fibonacci Prower 2009-10-30 13:49:45 UTC
Try a longer line. I've gotten 100k+ lines just by using a hosts file for
adblock and then running network-admin.
Comment 4 Adam Langley 2011-01-07 17:40:48 UTC
The following program will blow its stack and crash if I have an /etc/hosts line longer than 4Kish.

---
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int main() {
  struct addrinfo hints, *res;

  memset(&hints, 0, sizeof(hints));
  hints.ai_family = PF_INET;
  getaddrinfo("www.google.com", "http", &hints, &res);

  return 0;
}
---

Setting the ai_family in the hints is required in order to reproduce the crash.

(File and line references in the following are relative to git 16c2895feabae0962e0eba2b9164c6a83014bfe4)

In sysdeps/posix/getaddrinfo.c:531 we have a loop in gaih_inet which allocas a buffer and doubles the size of that buffer each time __gethostbyname2_r returns with ERANGE.

The __gethostbyname2_r ends up in nss/nss_files/files-hosts.c:128:

      if (status == NSS_STATUS_SUCCESS»·»·······»·······»·······»·······      \
»·······  && _res_hconf.flags & HCONF_FLAG_MULTI)»······»·······»·······      \
»·······{»······»·······»·······»·······»·······»·······»·······»·······      \
»·······  /* We have to get all host entries from the file.  */»»·······      \
»·······  const size_t tmp_buflen = MIN (buflen, 4096);»»·······»·······      \
»·······  char tmp_buffer[tmp_buflen]»··»·······»·······»·······»·······      \
»·······    __attribute__ ((__aligned__ (__alignof__ (struct hostent_data))));\

Here, if HCONF_FLAG_MULTI is set then a secondary buffer is created on the stack for the use of internal_getent. This buffer is limited to 4K in size.

internal_getent will try to read lines from /etc/hosts and it will return ERANGE if the line (plus an internal structure) doesn't fit into |tmp_buffer|. When this happens the loop in getaddrinfo.c will try doubling the size of its buffer. However, |tmp_buffer| was limited to 4K so __gethostbyname2_r repeatedly returns ERANGE and gaih_inet uselessly expands the buffer on the stack until the program crashes.

I believe that the best solution is to replace:
  const size_t tmp_buflen = MIN (buflen, 4096);
with:
  const size_t tmp_buflen = buflen;

I can confirm that this fixes the crash for me.
Comment 5 Ulrich Drepper 2011-01-13 16:29:25 UTC
I've checked in a patch.
Comment 6 Jackie Rosen 2014-02-16 18:29:21 UTC Comment hidden (spam)
Comment 7 Florian Weimer 2014-07-01 07:26:03 UTC
No trust boundary is crossed because write access to /etc/hosts is very restricted.  I don't think it is even updated when connecting to different networks using tools such as NetworkManager.