[PATCH] vfprint: validate nargs and argument-based offsets

Ryan S. Arnold ryan.arnold@gmail.com
Thu Feb 2 18:05:00 GMT 2012


On Thu, Feb 2, 2012 at 10:04 AM, Kees Cook <kees@outflux.net> wrote:
> 2012-02-02  Kees Cook  <keescook@chromium.org>
>
>        * stdio-common/vfprintf.c (vfprintf): Checks for nargs overflow and
>        validates argument-based array offsets.
>        * stdio-common/tst-vfprintf-nargs.c: New file.
>        * stdio-common/Makefile (tests): Add nargs overflow test.

Hi Kees, Thanks for the contribution.

The addition of the tst-vfprintf-nargs file makes this contribution
legally significant.  Please verify your FSF copyright-assignment
status with your employer.

> diff --git a/stdio-common/Makefile b/stdio-common/Makefile
> index 006f546..7d34b7a 100644
> --- a/stdio-common/Makefile
> +++ b/stdio-common/Makefile
> @@ -60,7 +60,8 @@ tests := tstscanf test_rdwr test-popen tstgetln test-fseek \
>         tst-popen tst-unlockedio tst-fmemopen2 tst-put-error tst-fgets \
>         tst-fwrite bug16 bug17 tst-swscanf tst-sprintf2 bug18 bug18a \
>         bug19 bug19a tst-popen2 scanf13 scanf14 scanf15 bug20 bug21 bug22 \
> -        scanf16 scanf17 tst-setvbuf1 tst-grouping bug23 bug24
> +        scanf16 scanf17 tst-setvbuf1 tst-grouping bug23 bug24 \
> +        tst-vfprintf-nargs
>
>  test-srcs = tst-unbputc tst-printf
>
> diff --git a/stdio-common/tst-vfprintf-nargs.c b/stdio-common/tst-vfprintf-nargs.c
> new file mode 100644
> index 0000000..7c48e20
> --- /dev/null
> +++ b/stdio-common/tst-vfprintf-nargs.c

I believe that you should add a GLIBC copyright header, including
copyright year and a "Contributed by " statement but perhaps someone
else with an opinion can comment.

Does the 64-bit case blowing the stack may preclude the use of
test-skeleton.c for the testcase?

> @@ -0,0 +1,91 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <stdint.h>
> +#include <unistd.h>
> +#include <inttypes.h>
> +#include <string.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +
> +int
> +format_failed(const char *fmt, const char *expected, int wanted)
> +{
> +  char output[80];
> +  pid_t pid;
> +  int status;
> +
> +  printf("%s : ", fmt);
> +  fflush(NULL);
> +
> +  /* Since the stack could be extremely wrecked by this test, use
> +     an external supervisor process to catch the signals, since a
> +     signal handler may not be able to recover.
> +   */
> +  pid = fork();
> +  if (pid < 0)
> +    {
> +      perror("fork");
> +      return 1;
> +    }
> +  if (pid)
> +    {
> +      waitpid(pid, &status, 0);
> +      if (WIFEXITED(status))
> +        {
> +          puts(WEXITSTATUS(status) == wanted ? "ok" : "FAILED");
> +          return WEXITSTATUS(status);
> +        }
> +      else if (WIFSIGNALED(status))
> +        {
> +          if (WTERMSIG(status) == -wanted)
> +            {
> +              printf("ok (ignored expected signal %d)\n", -wanted);
> +              return 0;
> +            }
> +          fprintf(stderr, "signal %d\n", WTERMSIG(status));
> +          return 1;
> +        }
> +      else
> +        {
> +          fprintf(stderr, "Unexpected failure\n");
> +          return 2;
> +        }
> +    }
> +
> +  memset(output, 0, sizeof(output));
> +  /* Having sprintf itself detect a failure is good.  */
> +  if (sprintf(output, fmt, 1, 2, 3, "test") < 0)
> +      exit(0);
> +  if (strcmp(output, expected))
> +    {
> +      fprintf(stderr, "(output '%s' != expected '%s') : ", output, expected);
> +      exit(1);
> +    }
> +  exit(0);
> +}
> +
> +int
> +main(int argc, char *argv[])
> +{
> +  int rc = 0, wanted;
> +  char buf[64];
> +
> +  /* Positional arguments are constructed via read_int(), so nargs
> +     can only overflow on 32bit systems. On 64bit systems, it will
> +     attempt to allocate a giant amount of stack memory and crash,
> +     which is the expected situation.  */
> +  if (sizeof(long) == 4)
> +    wanted = 0;
> +  else
> +    wanted = -11;

I believe using a compile time macro is more common:

#if __WORDSIZE == 32
  wanted = 0;
#else
  wanted = -11;
#endif

> +  sprintf(buf, "%%1$d %%%" PRIdPTR "$d", UINT32_MAX / sizeof(int));
> +  if (format_failed(buf, "1 %$d", wanted)) rc = 1;

I believe the precedent is to newline and indent the "rc = 1;".

> +
> +  /* Regular positionals work.  */
> +  if (format_failed("%1$d", "1", 0)) rc = 1;
> +
> +  /* Regular width positionals work.  */
> +  if (format_failed("%1$*2$d", " 1", 0)) rc = 1;

In general you needs a space between the function name and the
parenthesis throughout the patch, e.g., printf() should be printf ().

Regards,
Ryan S. Arnold



More information about the Libc-alpha mailing list