List of Phobos functions that allocate memory?

Adam D. Ruppe destructionator at gmail.com
Thu Feb 6 14:56:43 PST 2014


On Thursday, 6 February 2014 at 21:38:03 UTC, Dicebot wrote:
> Any application that operates on some external user input will 
> be subject to DoS attack vector if it uses Phobos directly.

Hmm, I hadn't considered that. Maybe exceptions could be handled 
automatically though due to the facts that there are rarely more 
than one in flight at any time and they typically don't live for 
long:

1) prohibit escaping of exception objects from catch blocks (we 
could just say it is undefined behavior in the spec). The data 
pointed to by the throwable object should be normal though, if 
you want to keep the exception, you can thus just shallow copy it.

2) Set aside a static (thread local) buffer early on with a size 
of like 512 bytes.

3) Make "throw new" call a special function which favors the 
static buffer. It can do a simple bump-the-pointer allocation in 
the static region or call the regular GC if there isn't enough 
room (should be extremely rare).

throw e; works the same way it does now. You can pre-allocate 
with some other method if you want.

4) Have the compiler automatically insert a call to 
_d_free_exception in a scope(success) block inside every catch 
block. It checks the given reference, if it is in the static 
buffer, just zero it all out. If all the chain is in there, 
zeroing it will free it all. If there's any GC chained 
exceptions, zeroing it will orphan them and they'll be freed on 
the next sweep. Otherwise ... well do nothing, let the GC clean 
up after it.



Proof of concept:


bool isThrowable(const ClassInfo ci) {
     if(ci is null) return false;
     if(ci is typeid(Throwable)) return true;
     return isThrowable(ci.base);
}

byte[512] exceptionHolder = 0;
size_t exceptionHolderPosition = 0;

extern(C)
     Object _d_newclass(const ClassInfo ci) {
         if(!isThrowable(ci))
             return _d_newclass_original(ci);

         auto size = ci.init.length;
         if(exceptionHolderPosition + size > 
exceptionHolder.length)
             return _d_newclass_original(ci);

         byte[] slice = exceptionHolder[exceptionHolderPosition .. 
exceptionHolderPosition + size];
         exceptionHolderPosition += size;

         slice[] = ci.init[];
         import core.stdc.stdio;
         printf("Magic allocation to %d\n", 
exceptionHolderPosition);
         return cast(Object) slice.ptr;
     }

extern(C)
     void _d_freeexception(Throwable t) {
         auto ptr = cast(void*) t;
         if(ptr >= exceptionHolder.ptr && ptr < 
exceptionHolder.ptr + exceptionHolder.length) {
             exceptionHolder[] = 0;
             exceptionHolderPosition = 0;
             import core.stdc.stdio;
             printf("Freeing\n");
         }
         // else do nothing, the GC will handle it
     }

void main() {
     import std.stdio;
     try {
         writefln("%s"); // orphaned argument
     } catch(Exception e) {
         scope(success) _d_freeexception(e);
         writeln(e);
     }
}

// copy/paste from druntime as fallback
extern (C) void onOutOfMemoryError();
extern (C) void*  gc_malloc( size_t sz, uint ba = 0 );
extern (C) Object _d_newclass_original(const ClassInfo ci)
{
     import core.stdc.stdlib;
     static import core.memory;
     alias BlkAttr = core.memory.GC.BlkAttr;


     void* p;

     if (ci.m_flags & TypeInfo_Class.ClassFlags.isCOMclass)
     {
         p = malloc(ci.init.length);
         if (!p)
             onOutOfMemoryError();
     }
     else
     {
         // TODO: should this be + 1 to avoid having pointers to 
the next block?
         BlkAttr attr = BlkAttr.FINALIZE;
         // extern(C++) classes don't have a classinfo pointer in 
their vtable so the GC can't finalize them
         if (ci.m_flags & TypeInfo_Class.ClassFlags.isCPPclass)
             attr &= ~BlkAttr.FINALIZE;
         if (ci.m_flags & TypeInfo_Class.ClassFlags.noPointers)
             attr |= BlkAttr.NO_SCAN;
         p = gc_malloc(ci.init.length, attr);
     }

     // initialize it
     (cast(byte*) p)[0 .. ci.init.length] = ci.init[];

     debug(PRINTF) printf("initialization done\n");
     return cast(Object) p;
}

===

Just compile and run normally, the linker will prefer our 
d_newclass to the one in phobos.lib automatically.

And you'll see the throw from writeln went into our static buffer 
and was freed at the end.

I toyed with a few other things too:

void main() {
     import std.stdio;
     try {
         try {
             writefln("%s"); // orphaned argument
         } catch(Exception e) {
             scope(success) _d_freeexception(e); // don't forget 
these
             throw new Exception("LOL", e);
         }
     } catch(Exception e) {
         scope(success) _d_freeexception(e);
         writeln(e);
         writeln(e.next);
     }
}

still works.



Am I missing a fatal flaw here? It seems to work and is kinda 
simple to do... exceptions don't really need a huge amount of 
dynamic memory.


More information about the Digitalmars-d mailing list