Variable length argument lists are just data on the stack. va_start returns a pointer to data on the stack. va_end does not free the memory, it just resets the pointer to NULL. The memory is freed when you leave the function's scope.
The function called (in my example, would be func_1) would know what inputs it should be receiving. I'm not sure if there's a better way to do this, because the task struct should be infinitely flexible with regards to what function it is passed and what arguments it is called with. Your case with the string shows me that, even if I were to use a NULL marker or a format string to determine the size of the values, I would still not be able to preserve all of the data through malloc. How is the data then kept from being overwritten when it is in the same scope (ie. the function that creates the argument list)? If I were to not call va_end in that function, would the memory not be freed up?