I should clarify. The dereference of the const pointer, and assignment to the dereferenced non-const object is okay on its face, but like most everything else in C, it's your responsibility to make sure that the pointer points to a valid object. And in this case, it's critical to understand that the pointer value, not the pointer object, is what's being bound by the Block (unless, of course, you used the __block scope/storage qualifier). You can visualize the normal case by adding a pseudo-prologue:
int i;
int *p = &i;
(void)(^block)(void) = ^{ int *const local_p = p; *local_p = 0; };
// is the equivalent of ^{ *p = 0; }
Finally, I'm fairly certain now that in order to return a Block from a function you have to manually use Block_copy() and Block_release(). So, there's no magic garbage collection going on under the hood, though there is some magic in the optimization of moving a Block from auto storage to dynamic storage--thus the __block qualifier and rules.
Implication being, that if you are passed a Block object and aren't going to use it exclusively from the current scope (e.g. the code flow falls back into an event loop, storing the Block for later use), you need to manage it with Block_copy() and Block_release(), similar to malloc()/free(). And I'm sure libdispatch/GCD does exactly this when passed a Block.