[pacman-dev,1/2] pacman: fix possible buffer overflow

Message ID 20180922201645.13413-1-morganamilo@gmail.com
State Superseded, archived
Delegated to: Andrew Gregory
Headers show
Series [pacman-dev,1/2] pacman: fix possible buffer overflow | expand

Commit Message

morganamilo Sept. 22, 2018, 8:16 p.m. UTC
in the function query_fileowner, if the user enters a string longer
than PATH_MAX then rpath will buffer overflow when lrealpath tries to
strcat everything together.

Even if we made sure filename was never longer than PATH_MAX this would
not help because lrealpath may concatenate filename with the current
directory among other things.

So simply let lrealpath calculate the size needed and allocate it for
us.

This also fixes the compiler warning;
query.c: In function ‘query_fileowner’:
query.c:192:4: warning: ‘strncpy’ specified bound 4096 equals destination size [-Wstringop-truncation]
    strncpy(rpath, filename, PATH_MAX);

The warning could have also been Fixed by using PATH_MAX -1 and explicitly
terminating the string. However this would not fix the buffer overflow.

Signed-off-by: morganamilo <morganamilo@gmail.com>

Comments

Andrew Gregory Sept. 22, 2018, 9:20 p.m. UTC | #1
On 09/22/18 at 09:16pm, morganamilo wrote:
> in the function query_fileowner, if the user enters a string longer
> than PATH_MAX then rpath will buffer overflow when lrealpath tries to
> strcat everything together.
> 
> Even if we made sure filename was never longer than PATH_MAX this would
> not help because lrealpath may concatenate filename with the current
> directory among other things.
> 
> So simply let lrealpath calculate the size needed and allocate it for
> us.
> 
> This also fixes the compiler warning;
> query.c: In function ‘query_fileowner’:
> query.c:192:4: warning: ‘strncpy’ specified bound 4096 equals destination size [-Wstringop-truncation]
>     strncpy(rpath, filename, PATH_MAX);
> 
> The warning could have also been Fixed by using PATH_MAX -1 and explicitly
> terminating the string. However this would not fix the buffer overflow.
> 
> Signed-off-by: morganamilo <morganamilo@gmail.com>
> 
> diff --git a/src/pacman/query.c b/src/pacman/query.c
> index 00c39638..a1197cea 100644
> --- a/src/pacman/query.c
> +++ b/src/pacman/query.c
> @@ -155,10 +155,10 @@ static int query_fileowner(alpm_list_t *targets)
>  
>  	for(t = targets; t; t = alpm_list_next(t)) {
>  		char *filename = NULL;
> -		char rpath[PATH_MAX], *rel_path;
> +		char *rpath = NULL, *rpathsave, *rel_path;
>  		struct stat buf;
>  		alpm_list_t *i;
> -		size_t len;
> +		size_t len, rlen;
>  		unsigned int found = 0;
>  		int is_dir = 0, is_missing = 0;
>  
> @@ -187,9 +187,15 @@ static int query_fileowner(alpm_list_t *targets)
>  			}
>  		}
>  
> -		if(!lrealpath(filename, rpath)) {
> +		if(!(rpath = lrealpath(filename, NULL))) {
>  			/* Can't canonicalize path, try to proceed anyway */
> -			strncpy(rpath, filename, PATH_MAX);
> +			rpath = strdup(filename);
> +		}
> +
> +		rlen = strlen(rpath);
> +		if(rlen + 1 >= PATH_MAX) {
> +				pm_printf(ALPM_LOG_ERROR, _("path too long: %s/\n"), rpath);
> +				goto targcleanup;
>  		}
>  
>  		if(strncmp(rpath, root, rootlen) != 0) {
> @@ -201,11 +207,14 @@ static int query_fileowner(alpm_list_t *targets)
>  		rel_path = rpath + rootlen;
>  
>  		if((is_missing && is_dir) || (!is_missing && (is_dir = S_ISDIR(buf.st_mode)))) {
> -			size_t rlen = strlen(rpath);
>  			if(rlen + 2 >= PATH_MAX) {
>  					pm_printf(ALPM_LOG_ERROR, _("path too long: %s/\n"), rpath);
>  					goto targcleanup;
>  			}
> +			if ((rpathsave = realloc(rpath, rlen + 2)) == NULL) {
> +				goto targcleanup;
> +			}
> +			rpath = rpathsave;
>  			strcat(rpath + rlen, "/");
>  		}
>  
> -- 
> 2.19.0

Good catch, but this approach allows lrealpath to allocate a buffer
larger than PATH_MAX only to error if it actually does.  lrealpath is
intended to be the same as realpath (aside from not resolving symlinks
obviously), so it should be fixed so that it doesn't write more than
PATH_MAX into a provided buffer.

We could switch to dynamic allocation in addition to fixing lrealpath,
but then the PATH_MAX checks would be pointless and should be removed.
You also never free the malloc'd path.  You can run the entire test
suite under valgrind with `make PY_TEST_FLAGS=--valgrind check` to
catch most memory leaks.

apg
morganamilo Sept. 22, 2018, 9:46 p.m. UTC | #2
On Sat, 22 Sep 2018 at 22:20, Andrew Gregory <andrew.gregory.8@gmail.com> wrote:
> Good catch, but this approach allows lrealpath to allocate a buffer
> larger than PATH_MAX only to error if it actually does.  lrealpath is
> intended to be the same as realpath (aside from not resolving symlinks
> obviously), so it should be fixed so that it doesn't write more than
> PATH_MAX into a provided buffer.

I tried this initially, not really being that familiar with C I
couldn't really figure it out. How exactly would one communicate the
error? I can return null but then that could be anything. I could also
move the alpm_log_error into lrealpath but just seems messy. Maybe
just truncate and don't error?

> We could switch to dynamic allocation in addition to fixing lrealpath,
> but then the PATH_MAX checks would be pointless and should be removed.

I assumed PATH_MAX was an OS limit type of thing, so even if we had a
bigger buffer it would be useless as it would not work properly with
other functions. I'm guessing that's not the case?

> You also never free the malloc'd path.  You can run the entire test
> suite under valgrind with `make PY_TEST_FLAGS=--valgrind check` to
> catch most memory leaks.
>
> apg

I did free it at some point. Must have accidentally checked it out at
some point. Whoops
Andrew Gregory Sept. 22, 2018, 9:57 p.m. UTC | #3
On 09/22/18 at 10:46pm, Morgan Adamiec wrote:
> On Sat, 22 Sep 2018 at 22:20, Andrew Gregory <andrew.gregory.8@gmail.com> wrote:
> > Good catch, but this approach allows lrealpath to allocate a buffer
> > larger than PATH_MAX only to error if it actually does.  lrealpath is
> > intended to be the same as realpath (aside from not resolving symlinks
> > obviously), so it should be fixed so that it doesn't write more than
> > PATH_MAX into a provided buffer.
> 
> I tried this initially, not really being that familiar with C I
> couldn't really figure it out. How exactly would one communicate the
> error? I can return null but then that could be anything. I could also
> move the alpm_log_error into lrealpath but just seems messy. Maybe
> just truncate and don't error?

Set errno to ENAMETOOLONG and return NULL, just like realpath.

> > We could switch to dynamic allocation in addition to fixing lrealpath,
> > but then the PATH_MAX checks would be pointless and should be removed.
> 
> I assumed PATH_MAX was an OS limit type of thing, so even if we had a
> bigger buffer it would be useless as it would not work properly with
> other functions. I'm guessing that's not the case?

Sort of.  Path limitations are pretty messy, but yes, some functions
will not work correctly with paths longer than PATH_MAX.  We don't
actually use the path after resolving it though; we just do a partial
comparison against paths stored in pacman's database.

> > You also never free the malloc'd path.  You can run the entire test
> > suite under valgrind with `make PY_TEST_FLAGS=--valgrind check` to
> > catch most memory leaks.
> 
> I did free it at some point. Must have accidentally checked it out at
> some point. Whoops
morganamilo Sept. 22, 2018, 10:18 p.m. UTC | #4
On Sat, 22 Sep 2018 at 22:57, Andrew Gregory <andrew.gregory.8@gmail.com> wrote:
> Set errno to ENAMETOOLONG and return NULL, just like realpath.

The problem still remains. You can input a filename that's < PATH_MAX
and have it resolve to something > PATH_MAX. You have no way to print
what that resolved path was. You can print the original file name but
that could be misleading.

Although I guess the chances of any one running into that is pretty
tiny. So the solution might just be to not care and print the original
filename.

I'll make a patch for it, see what people think.

Patch

diff --git a/src/pacman/query.c b/src/pacman/query.c
index 00c39638..a1197cea 100644
--- a/src/pacman/query.c
+++ b/src/pacman/query.c
@@ -155,10 +155,10 @@  static int query_fileowner(alpm_list_t *targets)
 
 	for(t = targets; t; t = alpm_list_next(t)) {
 		char *filename = NULL;
-		char rpath[PATH_MAX], *rel_path;
+		char *rpath = NULL, *rpathsave, *rel_path;
 		struct stat buf;
 		alpm_list_t *i;
-		size_t len;
+		size_t len, rlen;
 		unsigned int found = 0;
 		int is_dir = 0, is_missing = 0;
 
@@ -187,9 +187,15 @@  static int query_fileowner(alpm_list_t *targets)
 			}
 		}
 
-		if(!lrealpath(filename, rpath)) {
+		if(!(rpath = lrealpath(filename, NULL))) {
 			/* Can't canonicalize path, try to proceed anyway */
-			strncpy(rpath, filename, PATH_MAX);
+			rpath = strdup(filename);
+		}
+
+		rlen = strlen(rpath);
+		if(rlen + 1 >= PATH_MAX) {
+				pm_printf(ALPM_LOG_ERROR, _("path too long: %s/\n"), rpath);
+				goto targcleanup;
 		}
 
 		if(strncmp(rpath, root, rootlen) != 0) {
@@ -201,11 +207,14 @@  static int query_fileowner(alpm_list_t *targets)
 		rel_path = rpath + rootlen;
 
 		if((is_missing && is_dir) || (!is_missing && (is_dir = S_ISDIR(buf.st_mode)))) {
-			size_t rlen = strlen(rpath);
 			if(rlen + 2 >= PATH_MAX) {
 					pm_printf(ALPM_LOG_ERROR, _("path too long: %s/\n"), rpath);
 					goto targcleanup;
 			}
+			if ((rpathsave = realloc(rpath, rlen + 2)) == NULL) {
+				goto targcleanup;
+			}
+			rpath = rpathsave;
 			strcat(rpath + rlen, "/");
 		}