Hay muchos tipos de CFI. Sin embargo, el CFI que soporta la protección de los bordes hacia adelante y hacia atrás, es determinista y también es muy complejo (el único ejemplo concreto que conozco es la versión comercial de PaX RAP). La mayoría de los CFI son de grano grueso, lo que significa que una función puede devolver no solo a esa función, sino a varias funciones con la misma firma de función (es decir, el mismo tipo de retorno y el mismo argumentos). Los bordes delanteros provienen de saltos y llamadas, mientras que los bordes posteriores provienen de retornos. La gran mayoría de CFI solo admite uno u otro (como el CFG de Microsoft o el CFI de Clang), lo que limita enormemente sus capacidades.
Otro problema con CFI es que a menudo es probabilístico, usando algún valor secreto. Cualquier infoleaks que exponga ese valor secreto podría usarse para romper el CFI. El CFI determinista no sufre de esto, pero el CFI más determinista es muy de grano grueso. En resumen:
-
La mayoría de los CFI no son tanto hacia delante como hacia atrás, lo que limita gravemente sus capacidades.
-
La mayoría de los CFI son probabilísticos, lo que los hace vulnerables a infoleaks que pueden romperlos.
-
La mayoría de los CFI son generales, lo que le permite regresar a grupos de funciones con la misma firma.
Puede aprender mucho más acerca de las implementaciones reales de CFI si revisa kCFI y PaX RAP, las únicas dos que conozco que ofrecen una protección total de los bordes hacia adelante y hacia atrás. Desafortunadamente, dado que CFI es un tema tan complejo y no hay una implementación one o incluso una técnica para CFI, no hay forma de responder exactamente cómo un atacante puede evitarlo sin especificar la implementación.