|
68 | 68 |
|
69 | 69 | using namespace llvm;
|
70 | 70 |
|
| 71 | +enum PtrauthCheckMode { Default, Unchecked, Poison, Trap }; |
| 72 | +static cl::opt<PtrauthCheckMode> PtrauthAuthChecks( |
| 73 | + "aarch64-ptrauth-auth-checks", cl::Hidden, |
| 74 | + cl::values(clEnumValN(Unchecked, "none", "don't test for failure"), |
| 75 | + clEnumValN(Poison, "poison", "poison on failure"), |
| 76 | + clEnumValN(Trap, "trap", "trap on failure")), |
| 77 | + cl::desc("Check pointer authentication auth/resign failures"), |
| 78 | + cl::init(Default)); |
| 79 | + |
71 | 80 | #define DEBUG_TYPE "asm-printer"
|
72 | 81 |
|
73 | 82 | namespace {
|
@@ -134,6 +143,10 @@ class AArch64AsmPrinter : public AsmPrinter {
|
134 | 143 |
|
135 | 144 | // Emit the sequence for BRA/BLRA (authenticate + branch/call).
|
136 | 145 | void emitPtrauthBranch(const MachineInstr *MI);
|
| 146 | + |
| 147 | + // Emit the sequence for AUT or AUTPAC. |
| 148 | + void emitPtrauthAuthResign(const MachineInstr *MI); |
| 149 | + |
137 | 150 | // Emit the sequence to compute a discriminator into x17, or reuse AddrDisc.
|
138 | 151 | unsigned emitPtrauthDiscriminator(uint16_t Disc, unsigned AddrDisc,
|
139 | 152 | unsigned &InstsEmitted);
|
@@ -1760,6 +1773,222 @@ unsigned AArch64AsmPrinter::emitPtrauthDiscriminator(uint16_t Disc,
|
1760 | 1773 | return AArch64::X17;
|
1761 | 1774 | }
|
1762 | 1775 |
|
| 1776 | +void AArch64AsmPrinter::emitPtrauthAuthResign(const MachineInstr *MI) { |
| 1777 | + unsigned InstsEmitted = 0; |
| 1778 | + const bool IsAUTPAC = MI->getOpcode() == AArch64::AUTPAC; |
| 1779 | + |
| 1780 | + // We can expand AUT/AUTPAC into 3 possible sequences: |
| 1781 | + // - unchecked: |
| 1782 | + // autia x16, x0 |
| 1783 | + // pacib x16, x1 ; if AUTPAC |
| 1784 | + // |
| 1785 | + // - checked and clearing: |
| 1786 | + // mov x17, x0 |
| 1787 | + // movk x17, #disc, lsl #48 |
| 1788 | + // autia x16, x17 |
| 1789 | + // mov x17, x16 |
| 1790 | + // xpaci x17 |
| 1791 | + // cmp x16, x17 |
| 1792 | + // b.eq Lsuccess |
| 1793 | + // mov x16, x17 |
| 1794 | + // b Lend |
| 1795 | + // Lsuccess: |
| 1796 | + // mov x17, x1 |
| 1797 | + // movk x17, #disc, lsl #48 |
| 1798 | + // pacib x16, x17 |
| 1799 | + // Lend: |
| 1800 | + // Where we only emit the AUT if we started with an AUT. |
| 1801 | + // |
| 1802 | + // - checked and trapping: |
| 1803 | + // mov x17, x0 |
| 1804 | + // movk x17, #disc, lsl #48 |
| 1805 | + // autia x16, x0 |
| 1806 | + // mov x17, x16 |
| 1807 | + // xpaci x17 |
| 1808 | + // cmp x16, x17 |
| 1809 | + // b.eq Lsuccess |
| 1810 | + // brk #<0xc470 + aut key> |
| 1811 | + // Lsuccess: |
| 1812 | + // mov x17, x1 |
| 1813 | + // movk x17, #disc, lsl #48 |
| 1814 | + // pacib x16, x17 ; if AUTPAC |
| 1815 | + // Where the b.eq skips over the trap if the PAC is valid. |
| 1816 | + // |
| 1817 | + // This sequence is expensive, but we need more information to be able to |
| 1818 | + // do better. |
| 1819 | + // |
| 1820 | + // We can't TBZ the poison bit because EnhancedPAC2 XORs the PAC bits |
| 1821 | + // on failure. |
| 1822 | + // We can't TST the PAC bits because we don't always know how the address |
| 1823 | + // space is setup for the target environment (and the bottom PAC bit is |
| 1824 | + // based on that). |
| 1825 | + // Either way, we also don't always know whether TBI is enabled or not for |
| 1826 | + // the specific target environment. |
| 1827 | + |
| 1828 | + // By default, auth/resign sequences check for auth failures. |
| 1829 | + bool ShouldCheck = true; |
| 1830 | + // In the checked sequence, we only trap if explicitly requested. |
| 1831 | + bool ShouldTrap = MF->getFunction().hasFnAttribute("ptrauth-auth-traps"); |
| 1832 | + |
| 1833 | + // On an FPAC CPU, you get traps whether you want them or not: there's |
| 1834 | + // no point in emitting checks or traps. |
| 1835 | + if (STI->hasFPAC()) |
| 1836 | + ShouldCheck = ShouldTrap = false; |
| 1837 | + |
| 1838 | + // However, command-line flags can override this, for experimentation. |
| 1839 | + switch (PtrauthAuthChecks) { |
| 1840 | + case PtrauthCheckMode::Default: |
| 1841 | + break; |
| 1842 | + case PtrauthCheckMode::Unchecked: |
| 1843 | + ShouldCheck = ShouldTrap = false; |
| 1844 | + break; |
| 1845 | + case PtrauthCheckMode::Poison: |
| 1846 | + ShouldCheck = true; |
| 1847 | + ShouldTrap = false; |
| 1848 | + break; |
| 1849 | + case PtrauthCheckMode::Trap: |
| 1850 | + ShouldCheck = ShouldTrap = true; |
| 1851 | + break; |
| 1852 | + } |
| 1853 | + |
| 1854 | + auto AUTKey = (AArch64PACKey::ID)MI->getOperand(0).getImm(); |
| 1855 | + uint64_t AUTDisc = MI->getOperand(1).getImm(); |
| 1856 | + unsigned AUTAddrDisc = MI->getOperand(2).getReg(); |
| 1857 | + |
| 1858 | + unsigned XPACOpc = getXPACOpcodeForKey(AUTKey); |
| 1859 | + |
| 1860 | + // Compute aut discriminator into x17 |
| 1861 | + assert(isUInt<16>(AUTDisc)); |
| 1862 | + unsigned AUTDiscReg = |
| 1863 | + emitPtrauthDiscriminator(AUTDisc, AUTAddrDisc, InstsEmitted); |
| 1864 | + bool AUTZero = AUTDiscReg == AArch64::XZR; |
| 1865 | + unsigned AUTOpc = getAUTOpcodeForKey(AUTKey, AUTZero); |
| 1866 | + |
| 1867 | + // autiza x16 ; if AUTZero |
| 1868 | + // autia x16, x17 ; if !AUTZero |
| 1869 | + MCInst AUTInst; |
| 1870 | + AUTInst.setOpcode(AUTOpc); |
| 1871 | + AUTInst.addOperand(MCOperand::createReg(AArch64::X16)); |
| 1872 | + AUTInst.addOperand(MCOperand::createReg(AArch64::X16)); |
| 1873 | + if (!AUTZero) |
| 1874 | + AUTInst.addOperand(MCOperand::createReg(AUTDiscReg)); |
| 1875 | + EmitToStreamer(*OutStreamer, AUTInst); |
| 1876 | + ++InstsEmitted; |
| 1877 | + |
| 1878 | + // Unchecked or checked-but-non-trapping AUT is just an "AUT": we're done. |
| 1879 | + if (!IsAUTPAC && (!ShouldCheck || !ShouldTrap)) { |
| 1880 | + assert(STI->getInstrInfo()->getInstSizeInBytes(*MI) >= InstsEmitted * 4); |
| 1881 | + return; |
| 1882 | + } |
| 1883 | + |
| 1884 | + MCSymbol *EndSym = nullptr; |
| 1885 | + |
| 1886 | + // Checked sequences do an additional strip-and-compare. |
| 1887 | + if (ShouldCheck) { |
| 1888 | + MCSymbol *SuccessSym = createTempSymbol("auth_success_"); |
| 1889 | + |
| 1890 | + // XPAC has tied src/dst: use x17 as a temporary copy. |
| 1891 | + // mov x17, x16 |
| 1892 | + EmitToStreamer(*OutStreamer, MCInstBuilder(AArch64::ORRXrs) |
| 1893 | + .addReg(AArch64::X17) |
| 1894 | + .addReg(AArch64::XZR) |
| 1895 | + .addReg(AArch64::X16) |
| 1896 | + .addImm(0)); |
| 1897 | + ++InstsEmitted; |
| 1898 | + |
| 1899 | + // xpaci x17 |
| 1900 | + EmitToStreamer( |
| 1901 | + *OutStreamer, |
| 1902 | + MCInstBuilder(XPACOpc).addReg(AArch64::X17).addReg(AArch64::X17)); |
| 1903 | + ++InstsEmitted; |
| 1904 | + |
| 1905 | + // cmp x16, x17 |
| 1906 | + EmitToStreamer(*OutStreamer, MCInstBuilder(AArch64::SUBSXrs) |
| 1907 | + .addReg(AArch64::XZR) |
| 1908 | + .addReg(AArch64::X16) |
| 1909 | + .addReg(AArch64::X17) |
| 1910 | + .addImm(0)); |
| 1911 | + ++InstsEmitted; |
| 1912 | + |
| 1913 | + // b.eq Lsuccess |
| 1914 | + EmitToStreamer(*OutStreamer, MCInstBuilder(AArch64::Bcc) |
| 1915 | + .addImm(AArch64CC::EQ) |
| 1916 | + .addExpr(MCSymbolRefExpr::create( |
| 1917 | + SuccessSym, OutContext))); |
| 1918 | + ++InstsEmitted; |
| 1919 | + |
| 1920 | + if (ShouldTrap) { |
| 1921 | + // Trapping sequences do a 'brk'. |
| 1922 | + // brk #<0xc470 + aut key> |
| 1923 | + EmitToStreamer(*OutStreamer, |
| 1924 | + MCInstBuilder(AArch64::BRK).addImm(0xc470 | AUTKey)); |
| 1925 | + ++InstsEmitted; |
| 1926 | + } else { |
| 1927 | + // Non-trapping checked sequences return the stripped result in x16, |
| 1928 | + // skipping over the PAC if there is one. |
| 1929 | + |
| 1930 | + // FIXME: can we simply return the AUT result, already in x16? without.. |
| 1931 | + // ..traps this is usable as an oracle anyway, based on high bits |
| 1932 | + // mov x17, x16 |
| 1933 | + EmitToStreamer(*OutStreamer, MCInstBuilder(AArch64::ORRXrs) |
| 1934 | + .addReg(AArch64::X16) |
| 1935 | + .addReg(AArch64::XZR) |
| 1936 | + .addReg(AArch64::X17) |
| 1937 | + .addImm(0)); |
| 1938 | + ++InstsEmitted; |
| 1939 | + |
| 1940 | + if (IsAUTPAC) { |
| 1941 | + EndSym = createTempSymbol("resign_end_"); |
| 1942 | + |
| 1943 | + // b Lend |
| 1944 | + EmitToStreamer(*OutStreamer, MCInstBuilder(AArch64::B) |
| 1945 | + .addExpr(MCSymbolRefExpr::create( |
| 1946 | + EndSym, OutContext))); |
| 1947 | + ++InstsEmitted; |
| 1948 | + } |
| 1949 | + } |
| 1950 | + |
| 1951 | + // If the auth check succeeds, we can continue. |
| 1952 | + // Lsuccess: |
| 1953 | + OutStreamer->emitLabel(SuccessSym); |
| 1954 | + } |
| 1955 | + |
| 1956 | + // We already emitted unchecked and checked-but-non-trapping AUTs. |
| 1957 | + // That left us with trapping AUTs, and AUTPACs. |
| 1958 | + // Trapping AUTs don't need PAC: we're done. |
| 1959 | + if (!IsAUTPAC) { |
| 1960 | + assert(STI->getInstrInfo()->getInstSizeInBytes(*MI) >= InstsEmitted * 4); |
| 1961 | + return; |
| 1962 | + } |
| 1963 | + |
| 1964 | + auto PACKey = (AArch64PACKey::ID)MI->getOperand(3).getImm(); |
| 1965 | + uint64_t PACDisc = MI->getOperand(4).getImm(); |
| 1966 | + unsigned PACAddrDisc = MI->getOperand(5).getReg(); |
| 1967 | + |
| 1968 | + // Compute pac discriminator into x17 |
| 1969 | + assert(isUInt<16>(PACDisc)); |
| 1970 | + unsigned PACDiscReg = |
| 1971 | + emitPtrauthDiscriminator(PACDisc, PACAddrDisc, InstsEmitted); |
| 1972 | + bool PACZero = PACDiscReg == AArch64::XZR; |
| 1973 | + unsigned PACOpc = getPACOpcodeForKey(PACKey, PACZero); |
| 1974 | + |
| 1975 | + // pacizb x16 ; if PACZero |
| 1976 | + // pacib x16, x17 ; if !PACZero |
| 1977 | + MCInst PACInst; |
| 1978 | + PACInst.setOpcode(PACOpc); |
| 1979 | + PACInst.addOperand(MCOperand::createReg(AArch64::X16)); |
| 1980 | + PACInst.addOperand(MCOperand::createReg(AArch64::X16)); |
| 1981 | + if (!PACZero) |
| 1982 | + PACInst.addOperand(MCOperand::createReg(PACDiscReg)); |
| 1983 | + EmitToStreamer(*OutStreamer, PACInst); |
| 1984 | + ++InstsEmitted; |
| 1985 | + |
| 1986 | + assert(STI->getInstrInfo()->getInstSizeInBytes(*MI) >= InstsEmitted * 4); |
| 1987 | + // Lend: |
| 1988 | + if (EndSym) |
| 1989 | + OutStreamer->emitLabel(EndSym); |
| 1990 | +} |
| 1991 | + |
1763 | 1992 | void AArch64AsmPrinter::emitPtrauthBranch(const MachineInstr *MI) {
|
1764 | 1993 | unsigned InstsEmitted = 0;
|
1765 | 1994 | bool IsCall = MI->getOpcode() == AArch64::BLRA;
|
@@ -2214,6 +2443,11 @@ void AArch64AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
2214 | 2443 | return;
|
2215 | 2444 | }
|
2216 | 2445 |
|
| 2446 | + case AArch64::AUT: |
| 2447 | + case AArch64::AUTPAC: |
| 2448 | + emitPtrauthAuthResign(MI); |
| 2449 | + return; |
| 2450 | + |
2217 | 2451 | case AArch64::LOADauthptrstatic:
|
2218 | 2452 | LowerLOADauthptrstatic(*MI);
|
2219 | 2453 | return;
|
|
0 commit comments