threads / rfc / 58164

RFC patchmingw: avoid mktemp() in mkstemp() implementation

Subject: [RFC][PATCH] mingw: avoid mktemp() in mkstemp() implementation

## tl;dr

5 messages between Jul 15, 2022 and Jul 16, 2022. Diffs are folded; open one to read it.

replies: 4people: 4as markdown or json

René Scharfe· Jul 15, 2022, 03:58 UTC · lore

The implementation of mkstemp() for MinGW uses mktemp() and open() without the flag O_EXCL, which is racy. It's not a security problem for now because all of its callers only create files within the repository (incl. worktrees). Replace it with a call to our more secure internal function, git_mkstemp_mode(), to prevent possible future issues.

Signed-off-by: René Scharfe <l.s.r@web.de>
---
 compat/mingw.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)
Show changes to compat/mingw.c +1 −5
diff --git a/compat/mingw.c b/compat/mingw.c
index 2607de93af..b5502997e2 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1059,10 +1059,7 @@ char *mingw_mktemp(char *template)

 int mkstemp(char *template)
 {
-	char *filename = mktemp(template);
-	if (!filename)
-		return -1;
-	return open(filename, O_RDWR | O_CREAT, 0600);
+	return git_mkstemp_mode(template, 0600);
 }

 int gettimeofday(struct timeval *tv, void *tz)
--
2.37.0
Johannes Sixt· Jul 15, 2022, 06:00 UTC · re: René Scharfe · lore

Re: [RFC][PATCH] mingw: avoid mktemp() in mkstemp() implementation

Am 15.07.22 um 05:58 schrieb René Scharfe:
Show 28 quoted lines
> The implementation of mkstemp() for MinGW uses mktemp() and open()
> without the flag O_EXCL, which is racy.  It's not a security problem
> for now because all of its callers only create files within the
> repository (incl. worktrees).  Replace it with a call to our more
> secure internal function, git_mkstemp_mode(), to prevent possible
> future issues.
> 
> Signed-off-by: René Scharfe <l.s.r@web.de>
> ---
>  compat/mingw.c | 5 +----
>  1 file changed, 1 insertion(+), 4 deletions(-)
> 
> diff --git a/compat/mingw.c b/compat/mingw.c
> index 2607de93af..b5502997e2 100644
> --- a/compat/mingw.c
> +++ b/compat/mingw.c
> @@ -1059,10 +1059,7 @@ char *mingw_mktemp(char *template)
> 
>  int mkstemp(char *template)
>  {
> -	char *filename = mktemp(template);
> -	if (!filename)
> -		return -1;
> -	return open(filename, O_RDWR | O_CREAT, 0600);
> +	return git_mkstemp_mode(template, 0600);
>  }
> 
>  int gettimeofday(struct timeval *tv, void *tz)

I hate such an obvious layering violation. But we have already a ton of them elsewhere (calling xmalloc from compat/, for example), so this is just a rant, not an objection.

-- Hannes
Junio C Hamano· Jul 15, 2022, 16:38 UTC · re: Johannes Sixt · lore

Re: [RFC][PATCH] mingw: avoid mktemp() in mkstemp() implementation

Johannes Sixt <j6t@kdbg.org> writes:
Show 20 quoted lines
>> diff --git a/compat/mingw.c b/compat/mingw.c
>> index 2607de93af..b5502997e2 100644
>> --- a/compat/mingw.c
>> +++ b/compat/mingw.c
>> @@ -1059,10 +1059,7 @@ char *mingw_mktemp(char *template)
>> 
>>  int mkstemp(char *template)
>>  {
>> -	char *filename = mktemp(template);
>> -	if (!filename)
>> -		return -1;
>> -	return open(filename, O_RDWR | O_CREAT, 0600);
>> +	return git_mkstemp_mode(template, 0600);
>>  }
>> 
>>  int gettimeofday(struct timeval *tv, void *tz)
>
> I hate such an obvious layering violation. But we have already a ton of
> them elsewhere (calling xmalloc from compat/, for example), so this is
> just a rant, not an objection.

There is intended behaviour difference between xmalloc() and malloc() that justifies your "layering violation" observation. A low level library replacement implemented in compat/ should not call die() when it fails to obtain memory and instead report an error.

But it is unclear git_mkstemp_mode() and some others in wrapper.c fall into the same category. With the posted implementation above, the end result is how the platform that lack working mkstemp() would implement it.

If we were to do something to make it "cleaner", I suspect that the above implementatoin of mkstemp() can be moved out of compat/mingw.c and made reusable by _anybody_ who lack mkstemp(), just like we ship memmem() emulation for anybody who lack it in contrib/memmem.c

Thanks.
René Scharfe· Jul 16, 2022, 06:30 UTC · re: Junio C Hamano · lore

Re: [RFC][PATCH] mingw: avoid mktemp() in mkstemp() implementation

Am 15.07.22 um 18:38 schrieb Junio C Hamano:
Show 32 quoted lines
> Johannes Sixt <j6t@kdbg.org> writes:
>
>>> diff --git a/compat/mingw.c b/compat/mingw.c
>>> index 2607de93af..b5502997e2 100644
>>> --- a/compat/mingw.c
>>> +++ b/compat/mingw.c
>>> @@ -1059,10 +1059,7 @@ char *mingw_mktemp(char *template)
>>>
>>>  int mkstemp(char *template)
>>>  {
>>> -	char *filename = mktemp(template);
>>> -	if (!filename)
>>> -		return -1;
>>> -	return open(filename, O_RDWR | O_CREAT, 0600);
>>> +	return git_mkstemp_mode(template, 0600);
>>>  }
>>>
>>>  int gettimeofday(struct timeval *tv, void *tz)
>>
>> I hate such an obvious layering violation. But we have already a ton of
>> them elsewhere (calling xmalloc from compat/, for example), so this is
>> just a rant, not an objection.
>
> There is intended behaviour difference between xmalloc() and
> malloc() that justifies your "layering violation" observation.  A
> low level library replacement implemented in compat/ should not call
> die() when it fails to obtain memory and instead report an error.
>
> But it is unclear git_mkstemp_mode() and some others in wrapper.c
> fall into the same category.  With the posted implementation above,
> the end result is how the platform that lack working mkstemp() would
> implement it.

We'd have a problem if git_mkstemp_mode() depended on mkstemp(). It doesn't now, but if it is ever turned into a mkstemp() wrapper then it would fall flat on its face, but only on Windows.

> If we were to do something to make it "cleaner", I suspect that the
> above implementatoin of mkstemp() can be moved out of compat/mingw.c
> and made reusable by _anybody_ who lack mkstemp(), just like we ship
> memmem() emulation for anybody who lack it in contrib/memmem.c

All other platforms seem to have a native mkstemp(). There may still be interest in using our own if the native implementation is inferior, e.g. uses a weak RNG and/or doesn't retry.

We could also convert the handful of mkstemp() callers (two if we ignore reftable/) to use git_mkstemp_mode(). One of them is quite tempting for the code deduplication aspect alone (see below)..

René
---
 wrapper.c | 19 +------------------
 1 file changed, 1 insertion(+), 18 deletions(-)
Show changes to wrapper.c +1 −19
diff --git a/wrapper.c b/wrapper.c
index 1c3c970080..77ca923040 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -439,24 +439,7 @@ FILE *fopen_or_warn(const char *path, const char *mode)

 int xmkstemp(char *filename_template)
 {
-	int fd;
-	char origtemplate[PATH_MAX];
-	strlcpy(origtemplate, filename_template, sizeof(origtemplate));
-
-	fd = mkstemp(filename_template);
-	if (fd < 0) {
-		int saved_errno = errno;
-		const char *nonrelative_template;
-
-		if (strlen(filename_template) != strlen(origtemplate))
-			filename_template = origtemplate;
-
-		nonrelative_template = absolute_path(filename_template);
-		errno = saved_errno;
-		die_errno("Unable to create temporary file '%s'",
-			nonrelative_template);
-	}
-	return fd;
+	return xmkstemp_mode(filename_template, 0600);
 }

 /* Adapted from libiberty's mkstemp.c. */
--
2.37.0
Johannes Schindelin· Jul 15, 2022, 14:27 UTC · re: René Scharfe · lore

Re: [RFC][PATCH] mingw: avoid mktemp() in mkstemp() implementation

Hi René,
On Fri, 15 Jul 2022, René Scharfe wrote:
Show 6 quoted lines
> The implementation of mkstemp() for MinGW uses mktemp() and open()
> without the flag O_EXCL, which is racy.  It's not a security problem
> for now because all of its callers only create files within the
> repository (incl. worktrees).  Replace it with a call to our more
> secure internal function, git_mkstemp_mode(), to prevent possible
> future issues.
Excellent analysis! And thank you for noticing and fixing it!

I agree with what you wrote, there is one instance where not only files inside the `.git` directory are created but also files in the worktree: `ll-merge.c` has some code to write out files in the worktree before calling an external merge driver.

I believe that your assessment is correct, and that this cannot realistically be exploited (the only attack vector I came up with involved a shared repository, a symbolic link to overwrite/corrupt some files only writable by the attack's target, and some rather narrow TOCTOU window between that `mktemp()` and the `open()` call).

Show 13 quoted lines
> diff --git a/compat/mingw.c b/compat/mingw.c
> index 2607de93af..b5502997e2 100644
> --- a/compat/mingw.c
> +++ b/compat/mingw.c
> @@ -1059,10 +1059,7 @@ char *mingw_mktemp(char *template)
>
>  int mkstemp(char *template)
>  {
> -	char *filename = mktemp(template);
> -	if (!filename)
> -		return -1;
> -	return open(filename, O_RDWR | O_CREAT, 0600);
> +	return git_mkstemp_mode(template, 0600);

It is also much simpler to reason about the post image of this patch than about the pre image.

ACK!

Thank you so much! Dscho

Show 6 quoted lines
>  }
>
>  int gettimeofday(struct timeval *tv, void *tz)
> --
> 2.37.0
>

← back to recent threads