From owner-freebsd-bugs@FreeBSD.ORG Thu Jul 6 02:00:33 2006 Return-Path: X-Original-To: freebsd-bugs@hub.freebsd.org Delivered-To: freebsd-bugs@hub.freebsd.org Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125]) by hub.freebsd.org (Postfix) with ESMTP id A354216A4E0 for ; Thu, 6 Jul 2006 02:00:33 +0000 (UTC) (envelope-from gnats@FreeBSD.org) Received: from freefall.freebsd.org (freefall.freebsd.org [216.136.204.21]) by mx1.FreeBSD.org (Postfix) with ESMTP id 1834243D49 for ; Thu, 6 Jul 2006 02:00:33 +0000 (GMT) (envelope-from gnats@FreeBSD.org) Received: from freefall.freebsd.org (gnats@localhost [127.0.0.1]) by freefall.freebsd.org (8.13.4/8.13.4) with ESMTP id k6620Wwx039666 for ; Thu, 6 Jul 2006 02:00:32 GMT (envelope-from gnats@freefall.freebsd.org) Received: (from gnats@localhost) by freefall.freebsd.org (8.13.4/8.13.4/Submit) id k6620WrY039664; Thu, 6 Jul 2006 02:00:32 GMT (envelope-from gnats) Resent-Date: Thu, 6 Jul 2006 02:00:32 GMT Resent-Message-Id: <200607060200.k6620WrY039664@freefall.freebsd.org> Resent-From: FreeBSD-gnats-submit@FreeBSD.org (GNATS Filer) Resent-To: freebsd-bugs@FreeBSD.org Resent-Reply-To: FreeBSD-gnats-submit@FreeBSD.org, Sean Farley Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125]) by hub.freebsd.org (Postfix) with ESMTP id E21FF16A4DE for ; Thu, 6 Jul 2006 01:52:44 +0000 (UTC) (envelope-from sean-freebsd@farley.org) Received: from mail.farley.org (farley.org [67.64.95.201]) by mx1.FreeBSD.org (Postfix) with ESMTP id 1717543D46 for ; Thu, 6 Jul 2006 01:52:43 +0000 (GMT) (envelope-from sean-freebsd@farley.org) Received: from thor.farley.org (thor.farley.org [IPv6:2001:470:1f01:290:1::5]) by mail.farley.org (8.13.4/8.13.1) with ESMTP id k661s0R2042509 for ; Wed, 5 Jul 2006 20:54:01 -0500 (CDT) (envelope-from sean-freebsd@gw.farley.org) Received: from thor.farley.org (localhost [127.0.0.1]) by thor.farley.org (8.13.6/8.13.6) with ESMTP id k661qedM052994 for ; Wed, 5 Jul 2006 20:52:40 -0500 (CDT) (envelope-from sean-freebsd@thor.farley.org) Received: (from sean@localhost) by thor.farley.org (8.13.6/8.13.6/Submit) id k661qeq3052993; Wed, 5 Jul 2006 20:52:40 -0500 (CDT) (envelope-from sean-freebsd) Message-Id: <200607060152.k661qeq3052993@thor.farley.org> Date: Wed, 5 Jul 2006 20:52:40 -0500 (CDT) From: Sean Farley To: FreeBSD-gnats-submit@FreeBSD.org X-Send-Pr-Version: 3.113 Cc: Subject: bin/99826: setenv()/unsetenv() leak memory X-BeenThere: freebsd-bugs@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list Reply-To: Sean Farley List-Id: Bug reports List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 06 Jul 2006 02:00:33 -0000 >Number: 99826 >Category: bin >Synopsis: setenv()/unsetenv() leak memory >Confidential: no >Severity: non-critical >Priority: low >Responsible: freebsd-bugs >State: open >Quarter: >Keywords: >Date-Required: >Class: sw-bug >Submitter-Id: current-users >Arrival-Date: Thu Jul 06 02:00:32 GMT 2006 >Closed-Date: >Last-Modified: >Originator: Sean Farley >Release: FreeBSD 6.1-STABLE i386 >Organization: >Environment: System: FreeBSD thor.farley.org 6.1-STABLE FreeBSD 6.1-STABLE #0: Tue Jun 6 12:54:08 CDT 2006 root@thor.farley.org:/usr/obj/usr/src/sys/THOR i386 >Description: When writing code to use a third-party library, I ran into a leak that setenv() exhibits when overwriting a variable with a larger value. Calling unsetenv() will just leak memory. All of this is documented in the man page (setenv(3)): Successive calls to setenv() or putenv() assigning a differently sized value to the same name will result in a memory leak. The FreeBSD seman- tics for these functions (namely, that the contents of value are copied and that old values remain accessible indefinitely) make this bug unavoidable. Future versions may eliminate one or both of these semantic guarantees in order to fix the bug. Unfortunately, I must reset an environment variable quite a lot resulting in a quick memory leak. To prevent the leak, I have written a patch which copies the entire environment into dynamic memory upon the first setenv(). This way any time an existing variable has its value updated with a larger value the old value is freed and replaced with a new copy. unsetenv() will free values if setenv() has been called first. This was tested with dmalloc and MALLOC_OPTIONS=AX. A test program can be found here[1] and expanded here[2]. 1. http://www.farley.org/freebsd/tmp/setenv-3.tar.bz2 2. http://www.farley.org/freebsd/tmp/setenv-3/ >How-To-Repeat: Run hungry and watch it grow via top. Run lean and watch it hold steady. >Fix: It can be also found at http://www.farley.org/freebsd/tmp/setenv-3/setenv.c.diff -----Begin Fix----- --- /usr/src/lib/libc/stdlib/setenv.c Fri Mar 22 15:53:10 2002 +++ setenv.c Wed Jul 5 20:47:35 2006 @@ -40,9 +40,12 @@ #include #include #include +#include char *__findenv(const char *, int *); +static uint8_t dynamicEnv = 0; /* Env vars were copied */ + /* * setenv -- * Set the value of the environmental variable "name" to be @@ -59,6 +62,18 @@ char *c; int l_value, offset; + /* + * Make copies of environment variables to allow for + * reallocation. This stops a potential memory leak + * when resetting a variable with larger values. + */ + if (!dynamicEnv) { + for (offset = 0; environ[offset]; offset++) + if ((environ[offset] = strdup(environ[offset])) == NULL) + return (-1); + dynamicEnv = 1; + } + if (*value == '=') /* no `=' in value */ ++value; l_value = strlen(value); @@ -74,27 +89,21 @@ char **p; for (p = environ, cnt = 0; *p; ++p, ++cnt); - if (alloced == environ) { /* just increase size */ - p = (char **)realloc((char *)environ, - (size_t)(sizeof(char *) * (cnt + 2))); - if (!p) - return (-1); - alloced = environ = p; - } - else { /* get new space */ - /* copy old entries into it */ - p = malloc((size_t)(sizeof(char *) * (cnt + 2))); - if (!p) - return (-1); + p = (char **)realloc((char *)alloced, + (size_t)(sizeof(char *) * (cnt + 2))); + if (!p) + return (-1); + if (alloced != environ) { /* alloced environ */ + /* copy old entries into it */ bcopy(environ, p, cnt * sizeof(char *)); - alloced = environ = p; } + alloced = environ = p; environ[cnt + 1] = NULL; offset = cnt; } for (c = (char *)name; *c && *c != '='; ++c); /* no `=' in name */ if (!(environ[offset] = /* name + `=' + value */ - malloc((size_t)((int)(c - name) + l_value + 2)))) + reallocf(environ[offset], (size_t)((int)(c - name) + l_value + 2)))) return (-1); for (c = environ[offset]; (*c = *name++) && *c != '='; ++c); for (*c++ = '='; (*c++ = *value++); ); @@ -113,8 +122,11 @@ char **p; int offset; - while (__findenv(name, &offset)) /* if set multiple times */ + while (__findenv(name, &offset)) { /* if set multiple times */ + if (dynamicEnv) + free(environ[offset]); for (p = &environ[offset];; ++p) if (!(*p = *(p + 1))) break; + } } -----End Fix----- >Release-Note: >Audit-Trail: >Unformatted: