The authentication code is a combination of key and context: there are 5 total keys in the system, and then an unlimited number of contexts. Contexts are I think most useable on the "return" edges, because you can add the current stack pointer value to the context when you push the boxed return address onto the stack, then re-derive that context when you're at the return site.
That exact scheme doesn't work that well on the forward edge, because the stack pointers will be different when calling function pointer F in function A vs function B. What you can probably do is encode something about the type of F into the context. However, as they outline in the white paper, this isn't enough on its own because the type signature of gets and system are really similar and if your type-to-context encoding scheme maps them to the same context, an attacker could take a call to gets-via-function-pointer and replace that value with the value of system as it appears elsewhere in your program.
Authenticated pointers can assume this threat model. The attacker can read and write arbitrary memory, but it doesn't do anything for their ability to hijack the control flow of the application because all values stored in memory that relate to control flow are signed and encrypted. The attacker can't create a new code-pointer value and write it in to memory without knowledge of the secret keys, which are not in memory. The attacker could cause the program to crash or exit early, but oh well.
By my reading, this allows not a whitelist of pages, but a whitelist of arbitrary addresses. Different granularities entirely. Can anyone else bring a light to bear on this?