RFC: GCC plugin to find encrypted function pointer calls in glibc

Aldy Hernandez aldyh@redhat.com
Fri Apr 29 10:23:00 GMT 2016


Hi folks!

I'm working on a GCC plugin to pinpoint places in glibc where indirect 
function calls are made through a function pointer that has not been 
demangled.

As a reference, I am attaching a test case with various things that my 
plugin currently warns on that may serve as examples to what I am trying 
to accomplish.

The general idea is that I have defined a decryption operation as the 
output of a binary operator (xor, ior, and), so while we would warn for 
something like this:

	typedef void (*callback_t) (void);
	void foo (callback_t cb)
	{
	  cb ();
	}

...the following would be OK:

	#define PTR_DEMANGLE(var) \
	  (var) = (__typeof(var)) ((unsigned long) (var) ^ MAGIC)

	typedef void (*callback_t) (void);
	void foo (callback_t cb)
	{
	  callback_t tmp = cb;
	  PTR_DEMANGLE (tmp);
	  tmp ();
	}

However, I see glibc uses the following idiom:

#  define PTR_DEMANGLE(reg)	asm ("ror $2*" LP_SIZE "+1....)

Since I would prefer not to assume the output of all inline asm's are a 
demangling operation, I would like to get feedback from the community on 
what would be preferred.

My preferred approach is to add an attribute to an inline function that 
would wrap the asm:

	__attribute__((decrypt)) static inline funcp demangler (funcp f)
	{
		asm("blah");
	}

This is straightforward, clean, and follows language semantics (not to 
mention that I already have it implemented into my plugin :)), but 
Florian made funny faces when I showed it to him, so here I am :).

It would be neat if GCC had a way of tagging individual gimple 
statements with an attribute, so we could tag them with 
__attribute__((decrypt)), but alas we don't have such mechanism, and I'd 
prefer not to perform major surgery to GCC to make it so.

Another alternative would be to tag inline asms with commented out magic 
at the end, such that the plugin would notice and take appropriate action:

#ifdef FUNCTION_POINTER_CHECKS
#define DEMANGLE_TAG " ##_DEMANGLE_##"
#else
#define DEMANGLE_TAG ""
#endif
#  define PTR_DEMANGLE(var)  asm("ror $2*..."##DEMANGLE_TAG \
				: "=r" (var)
				: "0" (var)
				etc
				etc

This is straightforward, but I still prefer the inline function plus 
attribute idea.

If anyone is interested, I can post the code to my plugin.

What do y'all think?

Aldy
-------------- next part --------------
typedef void (*funcp) (void);

/* Binary operations on a function pointer is a recognized form of
   pointer encryption.

   The decryption must be done in place (to a local/temporary or a
   register), and never written to memory, otherwise it is considered
   unsafe.  */
#define PTR_DEMANGLE(var) \
  (var) = (__typeof(var)) ((unsigned long) (var) ^ 0xc00ffee)

/* A function with __attribute__((encrypt)) is considered an
   acceptable way to encrypt a function pointer.  Similar to PTR_DEMANGLE
   above, the decryption must be done in place.

   Note: It would be neat if we could tag the attribute directly onto the
   asm statement, but GCC currently doesn't do this, and it would
   probably be over kill.  */
__attribute__((encrypt))
static funcp asm_demangler (funcp f)
{
  asm ("#decrypt_operation $2, $0" : "=r" (f) : "0" (f));
  return f;
}

void (*encrypted_funcp) (void);
void (*plain_funcp) (void);
void direct_function (void);

extern int bar();

void foo()
{
  PTR_DEMANGLE (encrypted_funcp);
  encrypted_funcp ();		// WARN, written to memory.

  encrypted_funcp = asm_demangler (encrypted_funcp);
  encrypted_funcp ();		// WARN, written to memory.

  plain_funcp ();		// WARN, no decryption at all.

  direct_function();		// OK.  Don't care about plain func calls.

  funcp f = encrypted_funcp;
  f = asm_demangler (f);
  f();				// OK.  Decryption to a temporary, not memory.

  f = encrypted_funcp;
  f();				// WARN, no decryption at all.

  PTR_DEMANGLE (f);
  f();				// OK. Decryption in place.

  f = encrypted_funcp;
  if (bar())
    PTR_DEMANGLE (f);
  f ();				// WARN, path not demangled.

  // Test that phi convergence code works.
  f = encrypted_funcp;
  if (bar())
    PTR_DEMANGLE (f);
  else
    {
      funcp g = f;
      PTR_DEMANGLE (g);
      f = g;
    }
  f ();				// OK, all paths demangled.

  f = encrypted_funcp;
  if (bar())
    PTR_DEMANGLE (f);
  f();				// WARN, only one path mangled.

  extern void qsort(funcp callback);
  qsort (encrypted_funcp); // WARN, passing non decrypted funcp in argument

  qsort(0);		    // OK to pass a constant even though it's
			    // not decrypted.

  if (encrypted_funcp == 0) // WARN, comparing non-decrypted with constant.
    direct_function();

  f = encrypted_funcp;
  PTR_DEMANGLE (f);
  if (f != 0)		    // OK to compare with constant, we've been
			    // demangled.
    direct_function();
}

void foo2()
{
  funcp f = encrypted_funcp;
  funcp g = plain_funcp;
  PTR_DEMANGLE (f);
  if (f == g)		   // WARN, incompatible encryptness comparison
    bar();

  f = encrypted_funcp;     // OK to compare same encryption type.
  if (f == g)
    bar();
}

// Test that the propagator doesn't go into an infinite loop.

void
bad_inifinite_loop (const signed char *inptr)
{
  while (1)
    {
      signed char ch = *inptr;

      if (!ch)
        continue;

      ch &= 0x1f;

      for (unsigned i = 1; i < 10000; ++i)
        ch |= inptr[0] & 0x3f;
    }
}


More information about the Libc-alpha mailing list