If i was to write such checker, i'd try the following:
Map va_list-type variables (as memory regions) upon va_start(), map them to the number of read arguments (0 so far), and to the current StackFrameContext pointer, which represents a particular function call within which we are now (CheckerContext::getCurrentStackFrame()) (second program state map - or map to structure with two items).
You can retrieve [path-sensitive] function declaration and [path-insensitive] call expression from the stack frame context, and compute the number of variadic arguments passed based on that.
If va_start() is called upon a variable that is already tracked, warn.
If va_start() is called twice in the same StackFrameContext, warn.
Upon va_arg(), increment the number of reads for the respective variable. If the variable is not tracked, then the user has forgotten to call va_start(), hence warn. If the number exceeds the remembered total number of arguments, warn.
Upon va_copy(), copy the whole map item to the new variable. If the source variable is not va_start()'ed, warn.
Upon va_end(), erase the map item, which means that you no longer track the variable. If the variable is not va_start()'ed, warn.
Upon checkDeadSymbols, see if any of the tracked va_list variables is dead (in the sense of SymbolReaper::isLiveRegion()). If it is dead, then va_end() will never be called on it (and it wasn't so far, because the variable is still in the map). Hence warn on forgotten va_end().
You don't need to track end of function to warn on forgotten va_end() - checkDeadSymbols is already more frequent.
If you expect seeing a lot of passing va_list variables by pointers (which is insane but i guess possible), then consider the following.
Upon checkPointerEscape, see if any of the tracked va_list variables escapes. If so, mark it in the map as escaped: it might have va_end()'ed elsewhere, so we should not warn if it dies. Hence you cannot erase it from the program state - instead, you'd essentially need a separate trait flag (though you may put some magic constant into the existing trait).
If the memory region of the va_list variable has symbolic base (came to us by pointer, rather than variable declaration), then it might have already been initialized with va_start. Hence you shouldn't warn if va_arg()/va_end()/va_copy() is called upon it without prior va_start().
Hence the checker looks pretty similar to SimpleStreamChecker described in the video, and it's a very typical path-sensitive checker. I don't expect much problems on this path - it may sound like a lot of info, but basically it should be easy.
If you are only interested in argument count overflows, but not in other va_* API misuse, then probably you may simplify this "typestate machine". But you'd still need to catch va_start() and va_args() and most likely va_copy(), and once you do, you get all other checks for almost-free.