Your description is correct - the UTXO set is reverted to the previous state and then updated for the blocks in the new chain branch. Reverting is done by means of "undo data" that is stored along each block by Bitcoin Core - and that keeps all the information necessary to revert changes made (e.g. outputs that have been spent in the block and were thus removed from the UTXO set, so that they can be added back in).
Actually there is no need to any "undo" data, the block which has to be thrown out of the main chain holds all of the data necessary for undo: its transactions have spent utxos as inputs which are the rows to be added (again) and their outputs are the rows to be removed, given the reorg process is happening reasonably in a bottom-up fashion.
That's unfortunately not true. While the spending transaction contains the txid and vout of the spent output, it does not contain all data that the UTXO set holds - notably missing are the scriptPubKey and amount, among other things. Those could be looked up in the block that created the output, but that one might already be pruned and the lookup would be inefficient. Because of that, these pieces of data are stored by Bitcoin Core in undo data.