Based on the description, the most likely interpretation is that you are passing a pointer to an int ** object, and foo allocates memory for that object, so you'd have
void foo( int ***out )
{
*out = ...; // omitting actual allocation here since it's homework,
// but I can guarantee it won't be a single statement
}
int main( void )
{
int **pairs;
...
foo( &pairs );
...
}
Note than an object of T ** is not a 2-dimensional array; it can be used to implement something that can be indexed like a 2D array, but the "rows" don't have to be contiguous or even the same length. It basically looks something like this:
pairs pairs[i] pairs[i][0], pairs[i][1]
int ** int * int
+---+ +---+ +---+---+
| +-+-----> | +-+---------> | | |
+---+ +---+ +---+---+
| +-+------+
+---+ | +---+---+
| +-+---+ +--> | | |
+---+ | +---+---+
... |
| +---+---+
+-----> | | |
+---+---+
IOW, you have an object pairs that points to a sequence of int *, each of which (pairs[i]) points to a sequence of int. Because of how the [] subscript operator works, you can index into this structure using 2D array notation (pairs[i][j]), but otherwise it doesn't look or act like a 2D array.
So I'm assuming the job of foo is to allocate the memory for a structure like that, where each pairs[i] points to a sequence of 2 int. Since it needs to write a new value to pairs, we need to pass a pointer to pairs. Since pairs has type int **, that means the expression &pairs has type int ***.
I can tell you this will be a multi-step process - you will need to make multiple calls to malloc or calloc. The diagram above should give you some hints on how to do that.