[PATCH] Memory leak, at src/common/exec.c
Hi,
On exec.c, have two memory leaks, and a possible access beyond heap bounds, the patch tries to fix them.
According to documentation at:
https://en.cppreference.com/w/c/experimental/dynamic/strdup
"The returned pointer must be passed to free to avoid a memory leak. "
regards,
Ranier Vilela
Attachments:
exec_v1.patchtext/x-patch; name=exec_v1.patchDownload
diff --git a/src/common/exec.c b/src/common/exec.c
index 92dc3134a1..88a27cec78 100644
--- a/src/common/exec.c
+++ b/src/common/exec.c
@@ -72,14 +72,15 @@ validate_exec(const char *path)
int is_x;
#ifdef WIN32
- char path_exe[MAXPGPATH + sizeof(".exe") - 1];
+ char path_exe[MAXPGPATH + sizeof(".exe")];
+ int path_len;
/* Win32 requires a .exe suffix for stat() */
- if (strlen(path) >= strlen(".exe") &&
- pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
+ path_len = strlen(path);
+ if (path_len >= (sizeof(".exe") - 1) &&
+ pg_strcasecmp(path + path_len - (sizeof(".exe") - 1), ".exe") != 0)
{
- strlcpy(path_exe, path, sizeof(path_exe) - 4);
- strcat(path_exe, ".exe");
+ snprintf(path_exe, sizeof(path_exe) - 5, "%s.exe", path);
path = path_exe;
}
#endif
@@ -600,8 +601,10 @@ set_pglocale_pgservice(const char *argv0, const char *app)
snprintf(env_path, sizeof(env_path), "PGLOCALEDIR=%s", path);
canonicalize_path(env_path + 12);
dup_path = strdup(env_path);
- if (dup_path)
- putenv(dup_path);
+ if (dup_path) {
+ putenv(dup_path);
+ free(dup_path);
+ }
}
#endif
@@ -613,8 +616,10 @@ set_pglocale_pgservice(const char *argv0, const char *app)
snprintf(env_path, sizeof(env_path), "PGSYSCONFDIR=%s", path);
canonicalize_path(env_path + 13);
dup_path = strdup(env_path);
- if (dup_path)
- putenv(dup_path);
+ if (dup_path) {
+ putenv(dup_path);
+ free(dup_path);
+ }
}
}
On 12/16/19 1:22 PM, Ranier Vilela wrote:
Hi,
On exec.c, have two memory leaks, and a possible access beyond heap bounds, the patch tries to fix them.
According to documentation at:
https://en.cppreference.com/w/c/experimental/dynamic/strdup
"The returned pointer must be passed to free to avoid a memory leak."
Please see the man page for putenv. Are you certain it is safe to
free the string passed to putenv after putenv returns? I think this
may be implemented differently on various platforms.
Taken from `man putenv`:
"NOTES
The putenv() function is not required to be reentrant, and the
one in glibc 2.0 is not, but the glibc 2.1 version is.
Since version 2.1.2, the glibc implementation conforms to
SUSv2: the pointer string given to putenv() is used. In particular,
this string becomes part of the environment; changing it later will
change the environment. (Thus, it is an error is to call
putenv() with an automatic variable as the argument, then return from
the calling function while string is still part of the environment.)
However, glibc versions 2.0 to 2.1.1 differ: a copy of the
string is used. On the one hand this causes a memory leak, and on the
other hand it violates SUSv2.
The 4.4BSD version, like glibc 2.0, uses a copy.
SUSv2 removes the const from the prototype, and so does glibc 2.1.3.
"
--
Mark Dilger
Mark Dilger <hornschnorter@gmail.com> writes:
Please see the man page for putenv. Are you certain it is safe to
free the string passed to putenv after putenv returns? I think this
may be implemented differently on various platforms.
POSIX requires the behavior the glibc man page describes:
The putenv() function shall use the string argument to set environment
variable values. The string argument should point to a string of the
form "name=value". The putenv() function shall make the value of
the environment variable name equal to value by altering an existing
variable or creating a new one. In either case, the string pointed to
by string shall become part of the environment, so altering the string
shall change the environment.
So yeah, that patch is completely wrong. It might've survived light
testing with non-debug versions of malloc/free, but under any sort
of load the environment variable would become corrupted. The reason
for the strdup in our code is exactly to make a long-lived string
that can safely be given to putenv.
regards, tom lane
According to the documentation at:
https://wiki.sei.cmu.edu/confluence/display/c/POS34-C.+Do+not+call+putenv%28%29+with+a+pointer+to+an+automatic+variable+as+the+argument
"Using setenv() is easier and consequently less error prone than using putenv()."
putenv is problematic and error prone, better replace by setenv.
As a result, set_pglocale_pgservice, is much simpler and more readable.
regards,
Ranier Vilela
Attachments:
exec_v2.patchtext/x-patch; name=exec_v2.patchDownload
diff --git a/src/common/exec.c b/src/common/exec.c
index 92dc3134a1..82e94b4df1 100644
--- a/src/common/exec.c
+++ b/src/common/exec.c
@@ -72,14 +72,15 @@ validate_exec(const char *path)
int is_x;
#ifdef WIN32
- char path_exe[MAXPGPATH + sizeof(".exe") - 1];
+ char path_exe[MAXPGPATH + sizeof(".exe")];
+ int path_len;
/* Win32 requires a .exe suffix for stat() */
- if (strlen(path) >= strlen(".exe") &&
- pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
+ path_len = strlen(path);
+ if (path_len >= (sizeof(".exe") - 1) &&
+ pg_strcasecmp(path + path_len - (sizeof(".exe") - 1), ".exe") != 0)
{
- strlcpy(path_exe, path, sizeof(path_exe) - 4);
- strcat(path_exe, ".exe");
+ snprintf(path_exe, sizeof(path_exe) - 5, "%s.exe", path);
path = path_exe;
}
#endif
@@ -566,11 +567,8 @@ set_pglocale_pgservice(const char *argv0, const char *app)
{
char path[MAXPGPATH];
char my_exec_path[MAXPGPATH];
- char env_path[MAXPGPATH + sizeof("PGSYSCONFDIR=")]; /* longer than
- * PGLOCALEDIR */
- char *dup_path;
- /* don't set LC_ALL in the backend */
+ /* don't set LC_ALL in the backend */
if (strcmp(app, PG_TEXTDOMAIN("postgres")) != 0)
{
setlocale(LC_ALL, "");
@@ -596,25 +594,19 @@ set_pglocale_pgservice(const char *argv0, const char *app)
if (getenv("PGLOCALEDIR") == NULL)
{
- /* set for libpq to use */
- snprintf(env_path, sizeof(env_path), "PGLOCALEDIR=%s", path);
- canonicalize_path(env_path + 12);
- dup_path = strdup(env_path);
- if (dup_path)
- putenv(dup_path);
+ /* set for libpq to use */
+ canonicalize_path(path);
+ setenv("PGLOCALEDIR", path, 1);
}
#endif
if (getenv("PGSYSCONFDIR") == NULL)
{
- get_etc_path(my_exec_path, path);
-
- /* set for libpq to use */
- snprintf(env_path, sizeof(env_path), "PGSYSCONFDIR=%s", path);
- canonicalize_path(env_path + 13);
- dup_path = strdup(env_path);
- if (dup_path)
- putenv(dup_path);
+ get_etc_path(my_exec_path, path);
+
+ /* set for libpq to use */
+ canonicalize_path(path);
+ setenv("PGSYSCONFDIR", path, 1);
}
}
Ranier Vilela <ranier_gyn@hotmail.com> writes:
According to the documentation at:
https://wiki.sei.cmu.edu/confluence/display/c/POS34-C.+Do+not+call+putenv%28%29+with+a+pointer+to+an+automatic+variable+as+the+argument
"Using setenv() is easier and consequently less error prone than using putenv()."
putenv is problematic and error prone, better replace by setenv.
setenv is also less portable: it does not appear in SUSv2, which is still
our baseline spec for Unix platforms. We've avoided its use since 2001,
cf. ec7ddc158.
It's also fair to wonder how well this change would fly on Windows,
where we have to implement putenv for ourselves to get things to work
right (cf. src/port/win32env.c, which does not offer support for
setenv).
Please stop inventing reasons to change code that's worked fine for
decades. We have better things to spend our time on.
regards, tom lane
According to [1]/messages/by-id/29478.1576537771@sss.pgh.pa.us, windows does not support setenv, so for the patch to work [3]/messages/by-id/SN2PR05MB264066382E2CC75E734492C8E3510@SN2PR05MB2640.namprd05.prod.outlook.com, would need to add it.
With the possibility of setenv going further [2]/messages/by-id/30119.1576538578@sss.pgh.pa.us, I am submitting in this thread, the patch to add setenv support on the windows side, avoiding starting a new trhead.
It is based on pre-existing functions, and seeks to correctly emulate the functioning of the POSIX setenv, but has not yet been tested.
If this work is not acceptable then it is finished. And two memory leaks and a possible access beyond heap bounds, reported and not fixed.
regards,
Ranier Vilela
[1]: /messages/by-id/29478.1576537771@sss.pgh.pa.us
[2]: /messages/by-id/30119.1576538578@sss.pgh.pa.us
[3]: /messages/by-id/SN2PR05MB264066382E2CC75E734492C8E3510@SN2PR05MB2640.namprd05.prod.outlook.com
Attachments:
exec_v2.patchtext/x-patch; name=exec_v2.patchDownload
diff --git a/src/common/exec.c b/src/common/exec.c
index 92dc3134a1..82e94b4df1 100644
--- a/src/common/exec.c
+++ b/src/common/exec.c
@@ -72,14 +72,15 @@ validate_exec(const char *path)
int is_x;
#ifdef WIN32
- char path_exe[MAXPGPATH + sizeof(".exe") - 1];
+ char path_exe[MAXPGPATH + sizeof(".exe")];
+ int path_len;
/* Win32 requires a .exe suffix for stat() */
- if (strlen(path) >= strlen(".exe") &&
- pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
+ path_len = strlen(path);
+ if (path_len >= (sizeof(".exe") - 1) &&
+ pg_strcasecmp(path + path_len - (sizeof(".exe") - 1), ".exe") != 0)
{
- strlcpy(path_exe, path, sizeof(path_exe) - 4);
- strcat(path_exe, ".exe");
+ snprintf(path_exe, sizeof(path_exe) - 5, "%s.exe", path);
path = path_exe;
}
#endif
@@ -566,11 +567,8 @@ set_pglocale_pgservice(const char *argv0, const char *app)
{
char path[MAXPGPATH];
char my_exec_path[MAXPGPATH];
- char env_path[MAXPGPATH + sizeof("PGSYSCONFDIR=")]; /* longer than
- * PGLOCALEDIR */
- char *dup_path;
- /* don't set LC_ALL in the backend */
+ /* don't set LC_ALL in the backend */
if (strcmp(app, PG_TEXTDOMAIN("postgres")) != 0)
{
setlocale(LC_ALL, "");
@@ -596,25 +594,19 @@ set_pglocale_pgservice(const char *argv0, const char *app)
if (getenv("PGLOCALEDIR") == NULL)
{
- /* set for libpq to use */
- snprintf(env_path, sizeof(env_path), "PGLOCALEDIR=%s", path);
- canonicalize_path(env_path + 12);
- dup_path = strdup(env_path);
- if (dup_path)
- putenv(dup_path);
+ /* set for libpq to use */
+ canonicalize_path(path);
+ setenv("PGLOCALEDIR", path, 1);
}
#endif
if (getenv("PGSYSCONFDIR") == NULL)
{
- get_etc_path(my_exec_path, path);
-
- /* set for libpq to use */
- snprintf(env_path, sizeof(env_path), "PGSYSCONFDIR=%s", path);
- canonicalize_path(env_path + 13);
- dup_path = strdup(env_path);
- if (dup_path)
- putenv(dup_path);
+ get_etc_path(my_exec_path, path);
+
+ /* set for libpq to use */
+ canonicalize_path(path);
+ setenv("PGSYSCONFDIR", path, 1);
}
}
Mkvcbuild_v1.patchtext/x-patch; name=Mkvcbuild_v1.patchDownload
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 275f3bb699..33dc9bf5ad 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -65,7 +65,7 @@ my @frontend_uselibpgcommon = (
my $frontend_extralibs = {
'initdb' => ['ws2_32.lib'],
'pg_restore' => ['ws2_32.lib'],
- 'pgbench' => ['ws2_32.lib'],
+ 'pgbench' => ['ws2_32.lib', 'bcrypt.lib'],
'psql' => ['ws2_32.lib']
};
my $frontend_extraincludes = {
@@ -184,6 +184,7 @@ sub mkvcbuild
$postgres->AddFiles('src/backend/utils/adt', 'jsonpath_scan.l',
'jsonpath_gram.y');
$postgres->AddDefine('BUILDING_DLL');
+ $postgres->AddLibrary('bcrypt.lib') if ($vsVersion > '12.00');
$postgres->AddLibrary('secur32.lib');
$postgres->AddLibrary('ws2_32.lib');
$postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap});
@@ -246,6 +247,7 @@ sub mkvcbuild
$libpq->AddDefine('FRONTEND');
$libpq->AddDefine('UNSAFE_STAT_OK');
$libpq->AddIncludeDir('src/port');
+ $libpq->AddLibrary('bcrypt.lib') if ($vsVersion > '12.00');
$libpq->AddLibrary('secur32.lib');
$libpq->AddLibrary('ws2_32.lib');
$libpq->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap});
win32_setenv_v1.patchtext/x-patch; name=win32_setenv_v1.patchDownload
diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h
index c459a2417d..1ffddcceee 100644
--- a/src/include/port/win32_port.h
+++ b/src/include/port/win32_port.h
@@ -463,6 +463,7 @@ extern void _dosmaperr(unsigned long);
/* in port/win32env.c */
extern int pgwin32_putenv(const char *);
extern void pgwin32_unsetenv(const char *);
+extern int pgwin32_setenv(const char *name, const char *envval, int overwrite);
/* in port/win32security.c */
extern int pgwin32_is_service(void);
@@ -473,6 +474,7 @@ extern BOOL AddUserToTokenDacl(HANDLE hToken);
#define putenv(x) pgwin32_putenv(x)
#define unsetenv(x) pgwin32_unsetenv(x)
+#define setenv(n,v,o) pgwin32_setenv(n, v, o)
/* Things that exist in MinGW headers, but need to be added to MSVC */
#ifdef _MSC_VER
diff --git a/src/port/win32env.c b/src/port/win32env.c
index 6f4994c96a..e9845a78c8 100644
--- a/src/port/win32env.c
+++ b/src/port/win32env.c
@@ -123,3 +123,35 @@ pgwin32_unsetenv(const char *name)
pgwin32_putenv(envbuf);
free(envbuf);
}
+
+
+int
+pgwin32_setenv(const char *name, const char *envval, int overwrite)
+{
+ if (name == NULL)
+ return -1;
+
+ if (overwrite || !getenv(name)) {
+ char *envbuf;
+ int name_len;
+
+ name_len = strlen(name);
+ if (envval != NULL) {
+ envbuf = (char *) malloc(name_len + strlen(envval) + 2);
+ if (!envbuf)
+ return -1;
+
+ sprintf(envbuf, "%s=%s", name, envval);
+ } else {
+ envbuf = (char *) malloc(name_len + 2);
+ if (!envbuf)
+ return -1;
+
+ sprintf(envbuf, "%s=", name);
+ }
+ pgwin32_putenv(envbuf);
+ free(envbuf);
+ }
+
+ return 0;
+}