The test program at the end of this message is cut down from a real
program that uses swapcontext and friends (from ucontext.h) to run a
computation on sensitive data and then erase the stack used by that
computation.  The memset at the end of call_worker provokes an
invalid-write error from memcheck:

==12261== Memcheck, a memory error detector
==12261== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==12261== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==12261== Command: ./vg_test_2
==12261==
sizeof(data)        = 17328
offsetof(inner_ctx) = 16384
34 34
==12261== Invalid write of size 8
==12261==    at 0x4C32547: memset (vg_replace_strmem.c:1234)
==12261==    by 0x1089E6: call_worker.constprop.0 (vg_test_2.c:42)
==12261==    by 0x10874E: main (vg_test_2.c:62)
==12261==  Address 0x30cfe0 is 16224 bytes inside data symbol "ss.3473"
==12261==
00 00
==12261==
==12261== HEAP SUMMARY:
==12261==     in use at exit: 0 bytes in 0 blocks
==12261==   total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==12261==
==12261== All heap blocks were freed -- no leaks are possible
==12261==
==12261== For counts of detected and suppressed errors, rerun with: -v
==12261== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

"16224 bytes inside data symbol ss.3473" is 160 bytes from the high
end of the inner stack -- it's plausible that about this much space
would be used on the inner stack, on this architecture, by using
makecontext and swapcontext to call a no-op function.  If you compile
the test program with -DDONT_CALL_SWAPCONTEXT, the error goes away, so
this is definitely provoked by whatever swapcontext does internally.
Note that the program already has VALGRIND_STACK_REGISTER/DEREGISTER
annotations.

I can work around this by forcibly adjusting valgrind's state for the
"data" object (compile with -DDO_MAKE_MEM_WRITABLE to see) but I would
like to understand *why* this memory has become unwritable, why the
STACK_REGISTER/DEREGISTER was insufficient, and whether there's a more
appropriate work-around.

Thanks,
zw

#include <stdalign.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#include <valgrind/memcheck.h>

struct data
{
  char alignas(max_align_t) inner_stack[16384];
  ucontext_t inner_ctx;
};

static void
worker(void)
{
}

static void
call_worker(struct data *ip)
{
  ucontext_t outer_ctx;
  unsigned vg_stackid;
  if (getcontext(&ip->inner_ctx))
    abort();

  vg_stackid = VALGRIND_STACK_REGISTER
    (ip->inner_stack, ip->inner_stack + sizeof ip->inner_stack);

  ip->inner_ctx.uc_stack.ss_sp   = ip->inner_stack;
  ip->inner_ctx.uc_stack.ss_size = sizeof ip->inner_stack;
  ip->inner_ctx.uc_link          = &outer_ctx;
  makecontext(&ip->inner_ctx, worker, 0);

#ifndef DONT_CALL_SWAPCONTEXT
  swapcontext(&outer_ctx, &ip->inner_ctx);
#endif

  VALGRIND_STACK_DEREGISTER(vg_stackid);

#ifdef DO_MAKE_MEM_WRITABLE
  VALGRIND_MAKE_MEM_UNDEFINED(ip, sizeof *ip);
#endif

  memset(ip, 0, sizeof *ip);
}

#define ZX(x) ((unsigned int)(unsigned char)(x))

int
main(void)
{
  printf("sizeof(data)        = %zu\n"
         "offsetof(inner_ctx) = %zu\n",
         sizeof(struct data),
         offsetof(struct data, inner_ctx));

  static struct data ss;
  memset(&ss, 0x34, sizeof ss);

  printf("%02x %02x\n",
         ZX(((char *)&ss)[0]),
         ZX(((char *)&ss)[sizeof ss - 1]));

  call_worker(&ss);

  printf("%02x %02x\n",
         ZX(((char *)&ss)[0]),
         ZX(((char *)&ss)[sizeof ss - 1]));

  return 0;
}

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Valgrind-users mailing list
Valgrind-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/valgrind-users

Reply via email to