Support for zisslpcfi in opensbi
cover Support for zisslpcfi in opensbi
Zisslpcfi [1] extension extends risc-v architecture to mitigate against control-flow integrity attacks (ROP/JOP/COP).
Zisslpcfi uses bits (b23-b29) in (m/s/vs) status CSR for enabling cfi in lesser privileged mode and record cfi state. One such state is expected landing pad (ELP). If forward cfi is enabled, indirect call/jmp updates hart's ELP state (1bit) to true. ELP state is cleared only by a landing pad instruction else trap is delivered with ELP state recorded in sstatus CSR.
This two patch series adds following changes to opensbi
- Adds support in opensbi to detect zisslpcfi
- trap redirection updates ELP state accordingly
[1] - https://github.com/riscv/riscv-cfi
Deepak Gupta (2):
include: adding support for zsslpcfi encodings
lib: sbi: zsslpcfi detection and elp cfi state reflect back in status
include/sbi/riscv_encoding.h | 6 ++++++ include/sbi/sbi_hart.h | 2 ++ lib/sbi/sbi_hart.c | 21 +++++++++++++++++++++ lib/sbi/sbi_trap.c | 18 ++++++++++++++++++ 4 files changed, 47 insertions(+)
Signature · 1 line
2.25.1
PATCH 1/2 include: adding support for zsslpcfi encodings
zsslpcfi extension (see links) introduces b60 (CFI) in menvcfg CSR to enable shadow stack and landing pad for machine and lesser privileged modes. Additionally extension introduces new bits in xstatus for cfi state.
Links:https://github.com/riscv/riscv-cfi
--- include/sbi/riscv_encoding.h | 4 ++++ include/sbi/sbi_hart.h | 2 ++ 2 files changed, 6 insertions(+)
include/sbi/riscv_encoding.h +4 -0
@@ -210,9 +210,11 @@#if __riscv_xlen >32 #define ENVCFG_STCE (_ULL(1) << 63)#define ENVCFG_PBMTE (_ULL(1) << 62) +#define ENVCFG_CFI (_ULL(1) << 60)#else #define ENVCFGH_STCE (_UL(1) << 31)#define ENVCFGH_PBMTE (_UL(1) << 30) +#define ENVCFGH_CFI (_UL(1) << 28)#endif #define ENVCFG_CBZE (_UL(1) << 7)#define ENVCFG_CBCFE (_UL(1) << 6)
@@ -229,6 +231,8 @@#define CSR_USTATUS 0x000#define CSR_UIE 0x004#define CSR_UTVEC 0x005 +#define CSR_LPLR 0x006 +#define CSR_SSP 0x020/* User Trap Handling (N-extension) */ #define CSR_USCRATCH 0x040
include/sbi/sbi_hart.h +2 -0
@@ -36,6 +36,8 @@ enum sbi_hart_extensions {SBI_HART_EXT_SMSTATEEN ,/** HART has Sstc extension */ SBI_HART_EXT_SSTC , +/** HART has sslpcfi extension */ +SBI_HART_EXT_SSLPCFI ,/** Maximum index of Hart extension */ SBI_HART_EXT_MAX,
Signature · 1 line
2.25.1
PATCH 1/2 include: adding support for zsslpcfi encodings
Quoted 2 lines · Deepak Gupta
zsslpcfi extension (see links) introduces b60 (CFI) in menvcfg CSR to
The extension name is Zisslpcfi (capital Z)
Quoted 5 lines
enable shadow stack and landing pad for machine and lesser privileged modes. Additionally extension introduces new bits in xstatus for cfi state.
nits: need a space after :
s/Links/Link
Quoted 37 lines
Signed-off-by Deepak Gupta <[email protected]>--- include/sbi/riscv_encoding.h | 4 ++++ include/sbi/sbi_hart.h | 2 ++ 2 files changed, 6 insertions(+)include/sbi/riscv_encoding.h +4
-0@@ -210,9 +210,11 @@#if __riscv_xlen >32 #define ENVCFG_STCE (_ULL(1) << 63)#define ENVCFG_PBMTE (_ULL(1) << 62) +#define ENVCFG_CFI (_ULL(1) << 60)#else #define ENVCFGH_STCE (_UL(1) << 31)#define ENVCFGH_PBMTE (_UL(1) << 30) +#define ENVCFGH_CFI (_UL(1) << 28)#endif #define ENVCFG_CBZE (_UL(1) << 7)#define ENVCFG_CBCFE (_UL(1) << 6)@@ -229,6 +231,8 @@#define CSR_USTATUS 0x000#define CSR_UIE 0x004#define CSR_UTVEC 0x005 +#define CSR_LPLR 0x006 +#define CSR_SSP 0x020/* User Trap Handling (N-extension) */ #define CSR_USCRATCH 0x040include/sbi/sbi_hart.h +0
-0
include/sbi/sbi_hart.h enum sbi_hart_extensions { · line 39
Quoted 4 lines
include/sbi/sbi_hart.h +1
-0@@ -36,6 +36,8 @@ enum sbi_hart_extensions {SBI_HART_EXT_SMSTATEEN ,/** HART has Sstc extension */ SBI_HART_EXT_SSTC, +/** HART has sslpcfi extension */
Zisslpcfi
include/sbi/sbi_hart.h enum sbi_hart_extensions { · line 40
Quoted 1 line
include/sbi/sbi_hart.h +1
-0+ SBI_HART_EXT_SSLPCFI,
SBI_HART_EXT_ZISSLPCFI
include/sbi/sbi_hart.h enum sbi_hart_extensions {
Quoted 4 lines
include/sbi/sbi_hart.h +0
-1/** Maximum index of Hart extension */ SBI_HART_EXT_MAX, --
Regards,
Bin
PATCH 2/2 lib: sbi: zsslpcfi detection and elp cfi state reflect back in status
In reply to Deepak GuptaThis patch adds support for zsslpcfi detection in sbi_hart.c If zsslpcfi is detected, this turns on menvcfg.CFI
zsslpcfi records status of cfi state in xstatus csr. Missing landing pad sets MPELP in mstatus. When SBI is redirecting back to S/VS/HS, SPELP is set in sstatus/vsstatus.
--- include/sbi/riscv_encoding.h | 2 ++ lib/sbi/sbi_hart.c | 21 +++++++++++++++++++++ lib/sbi/sbi_trap.c | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+)
include/sbi/riscv_encoding.h +2 -0
@@ -32,6 +32,8 @@#define MSTATUS_TVM _UL(0x00100000)#define MSTATUS_TW _UL(0x00200000)#define MSTATUS_TSR _UL(0x00400000) +#define MSTATUS_SPELP _UL(0x10000000) +#define MSTATUS_MPELP _UL(0x20000000)#define MSTATUS32_SD _UL(0x80000000)#if __riscv_xlen ==64 #define MSTATUS_UXL _ULL(0x0000000300000000)
lib/sbi/sbi_hart.c +21 -0
@@ -157,6 +157,16 @@ static void mstatus_init(struct sbi_scratch *scratch)#endif } +if (sbi_hart_has_extension (scratch,SBI_HART_EXT_SSLPCFI )) { +#if __riscv_xlen ==32 +unsigned long menvcfgh_val; + menvcfgh_val =csr_read (CSR_MENVCFGH ); + menvcfgh_val |=ENVCFGH_CFI ; +csr_write (CSR_MENVCFGH , menvcfgh_val); +#else + menvcfg_val |=ENVCFG_CFI ; +#endif + }csr_write (CSR_MENVCFG , menvcfg_val); }
@@ -555,6 +565,7 @@ static int hart_detect_features(struct sbi_scratch *scratch)sbi_scratch_offset_ptr (scratch, hart_features_offset);unsigned long val, oldval;int rc; +int ssp_exist, lplr_exist;/* If hart features already detected then do nothing */ if (hfeatures->detected)
@@ -693,6 +704,16 @@ __mhpm_skip: SBI_HART_EXT_SMSTATEEN, true); } +if (hfeatures->priv_version >=SBI_HART_PRIV_VER_1_12 ) { + val =csr_read_allowed (CSR_SSP , (unsigned long )&trap); + ssp_exist = trap.cause?0 :1 ; + val =csr_read_allowed (CSR_LPLR , (unsigned long )&trap); + lplr_exist = trap.cause?0 :1 ; +if (lplr_exist & ssp_exist) +__sbi_hart_update_extension (hfeatures, +SBI_HART_EXT_SSLPCFI , true); + } +/* Let platform populate extensions */ rc =sbi_platform_extensions_init (sbi_platform_thishart_ptr (), hfeatures);
lib/sbi/sbi_trap.c +18 -0
@@ -87,6 +87,7 @@ int sbi_trap_redirect(struct sbi_trap_regs *regs, struct sbi_trap_info *trap) {ulong hstatus, vsstatus, prev_mode; +bool elp = FALSE;#if __riscv_xlen ==32 bool prev_virt = (regs->mstatusH &MSTATUSH_MPV ) ? TRUE : FALSE;#else
@@ -100,6 +101,13 @@ int sbi_trap_redirect(struct sbi_trap_regs *regs,if (prev_mode !=PRV_S && prev_mode !=PRV_U )return SBI_ENOTSUPP ; +/* If extension has support for CFI, clear MPELP because redirecting to VS or (H)S */ +if (sbi_hart_has_extension (sbi_scratch_thishart_ptr (),SBI_HART_EXT_SSLPCFI )) { + elp = (regs->mstatus &MSTATUS_MPELP )? TRUE: FALSE; +/* Since redirecting, clear mpelp unconditionally */ + regs->mstatus &= ~MSTATUS_MPELP ; + } +/* If exceptions came from VS/VU-mode, redirect to VS-mode if * delegated in hedeleg */
@@ -153,6 +161,11 @@ int sbi_trap_redirect(struct sbi_trap_regs *regs,/* Get VS-mode SSTATUS CSR */ vsstatus =csr_read (CSR_VSSTATUS ); +/*if elp was set, set it back in vsstatus */ +if (elp) { + vsstatus |=MSTATUS_SPELP ; + } +/* Set SPP for VS-mode */ vsstatus &= ~SSTATUS_SPP ;if (prev_mode ==PRV_S )
@@ -193,6 +206,11 @@ int sbi_trap_redirect(struct sbi_trap_regs *regs,/* Clear SIE for S-mode */ regs->mstatus &= ~MSTATUS_SIE ; + +/* if elp was set, set it back in mstatus */ +if (elp) { + regs->mstatus |=MSTATUS_SPELP ; + } }return 0 ;
Signature · 1 line
2.25.1
PATCH 2/2 lib: sbi: zsslpcfi detection and elp cfi state reflect back in status
Quoted 50 lines · Deepak Gupta
This patch adds support for zsslpcfi detection in
sbi_hart.cIf zsslpcfi is detected, this turns on menvcfg.CFIzsslpcfi records status of cfi state in xstatus csr. Missing landing pad sets MPELP in mstatus. When SBI is redirecting back to S/VS/HS, SPELP is set in sstatus/vsstatus.
Signed-off-by Deepak Gupta <[email protected]>--- include/sbi/riscv_encoding.h | 2 ++ lib/sbi/sbi_hart.c | 21 +++++++++++++++++++++ lib/sbi/sbi_trap.c | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+)include/sbi/riscv_encoding.h +2
-0@@ -32,6 +32,8 @@#define MSTATUS_TVM _UL(0x00100000)#define MSTATUS_TW _UL(0x00200000)#define MSTATUS_TSR _UL(0x00400000) +#define MSTATUS_SPELP _UL(0x10000000) +#define MSTATUS_MPELP _UL(0x20000000)#define MSTATUS32_SD _UL(0x80000000)#if __riscv_xlen ==64 #define MSTATUS_UXL _ULL(0x0000000300000000)lib/sbi/sbi_hart.c +10
-0@@ -157,6 +157,16 @@ static void mstatus_init(struct sbi_scratch *scratch)#endif } +if (sbi_hart_has_extension (scratch,SBI_HART_EXT_SSLPCFI )) { +#if __riscv_xlen ==32 +unsigned long menvcfgh_val; + menvcfgh_val =csr_read (CSR_MENVCFGH ); + menvcfgh_val |=ENVCFGH_CFI ; +csr_write (CSR_MENVCFGH , menvcfgh_val); +#else + menvcfg_val |=ENVCFG_CFI ; +#endif + }csr_write (CSR_MENVCFG , menvcfg_val); }
lib/sbi/sbi_hart.c static int hart_detect_features(struct sbi_scratch *scratch) · line 568
Quoted 4 lines
lib/sbi/sbi_hart.c +1
-0@@ -555,6 +565,7 @@ static int hart_detect_features(struct sbi_scratch *scratch)sbi_scratch_offset_ptr (scratch, hart_features_offset);unsigned long val, oldval;int rc; +int ssp_exist, lplr_exist;
Use bool
lib/sbi/sbi_hart.c __mhpm_skip: · line 709
Quoted 10 lines
lib/sbi/sbi_hart.c +3
-0/* If hart features already detected then do nothing */ if (hfeatures->detected)@@ -693,6 +704,16 @@ __mhpm_skip: SBI_HART_EXT_SMSTATEEN, true); } +if (hfeatures->priv_version >=SBI_HART_PRIV_VER_1_12 ) { + val =csr_read_allowed (CSR_SSP , (unsigned long )&trap); + ssp_exist = trap.cause?0 :1 ;
need space around ? and :
use true/false instead of 1/0
Quoted 15 lines
lib/sbi/sbi_hart.c +7
-0+ val =csr_read_allowed (CSR_LPLR , (unsigned long )&trap); + lplr_exist = trap.cause?0 :1 ; +if (lplr_exist & ssp_exist) +__sbi_hart_update_extension (hfeatures, +SBI_HART_EXT_SSLPCFI , true); + } +/* Let platform populate extensions */ rc =sbi_platform_extensions_init (sbi_platform_thishart_ptr (), hfeatures);lib/sbi/sbi_trap.c +0
-0
lib/sbi/sbi_trap.c int sbi_trap_redirect(struct sbi_trap_regs *regs, · line 90
Quoted 4 lines
lib/sbi/sbi_trap.c +1
-0@@ -87,6 +87,7 @@ int sbi_trap_redirect(struct sbi_trap_regs *regs, struct sbi_trap_info *trap) {ulong hstatus, vsstatus, prev_mode; +bool elp = FALSE;
s/FALSE/false
Quoted 18 lines
lib/sbi/sbi_trap.c +7
-0#if __riscv_xlen ==32 bool prev_virt = (regs->mstatusH &MSTATUSH_MPV ) ? TRUE : FALSE;#else @@ -100,6 +101,13 @@ int sbi_trap_redirect(struct sbi_trap_regs *regs,if (prev_mode !=PRV_S && prev_mode !=PRV_U )return SBI_ENOTSUPP ; +/* If extension has support for CFI, clear MPELP because redirecting to VS or (H)S */ +if (sbi_hart_has_extension (sbi_scratch_thishart_ptr (),SBI_HART_EXT_SSLPCFI )) { + elp = (regs->mstatus &MSTATUS_MPELP )? TRUE: FALSE; +/* Since redirecting, clear mpelp unconditionally */ + regs->mstatus &= ~MSTATUS_MPELP ; + } +/* If exceptions came from VS/VU-mode, redirect to VS-mode if * delegated in hedeleg */
lib/sbi/sbi_trap.c int sbi_trap_redirect(struct sbi_trap_regs *regs, · line 164
Quoted 4 lines
lib/sbi/sbi_trap.c +1
-0@@ -153,6 +161,11 @@ int sbi_trap_redirect(struct sbi_trap_regs *regs,/* Get VS-mode SSTATUS CSR */ vsstatus =csr_read (CSR_VSSTATUS ); +/*if elp was set, set it back in vsstatus */
need space after /*
Quoted 8 lines
lib/sbi/sbi_trap.c +4
-0+if (elp) { + vsstatus |=MSTATUS_SPELP ; + } +/* Set SPP for VS-mode */ vsstatus &= ~SSTATUS_SPP ;if (prev_mode ==PRV_S )
lib/sbi/sbi_trap.c int sbi_trap_redirect(struct sbi_trap_regs *regs, · line 215
Quoted 10 lines
lib/sbi/sbi_trap.c +5
-0@@ -193,6 +206,11 @@ int sbi_trap_redirect(struct sbi_trap_regs *regs,/* Clear SIE for S-mode */ regs->mstatus &= ~MSTATUS_SIE ; + +/* if elp was set, set it back in mstatus */ +if (elp) { + regs->mstatus |=MSTATUS_SPELP ; + } }
Regards,
Bin
PATCH 2/2 lib: sbi: zsslpcfi detection and elp cfi state reflect back in status
Quoted 7 lines · Bin Meng
lib/sbi/sbi_hart.c __mhpm_skip: · line 709
Quoted 7 lines
lib/sbi/sbi_hart.c +3
-0@@ -693,6 +704,16 @@ __mhpm_skip: SBI_HART_EXT_SMSTATEEN, true); } +if (hfeatures->priv_version >=SBI_HART_PRIV_VER_1_12 ) { + val =csr_read_allowed (CSR_SSP , (unsigned long )&trap); + ssp_exist = trap.cause?0 :1 ;
ssp_exist = trap.cause == 0;
Signature · 3 lines
Andreas Schwab, SUSE Labs, schwab at suse.de GPG Key fingerprint = 0196 BAD8 1CE9 1970 F4BE 1748 E4D4 88E3 0EEA B9D7 "And now for something completely different."
PATCH 2/2 lib: sbi: zsslpcfi detection and elp cfi state reflect back in status
Thanks Andreas and Bin for your review comments.
Sent out v2 version of the patch.
-Deepak
Quoted 17 lines · Andreas Schwab
Quoted 7 lines · Bin Meng
lib/sbi/sbi_hart.c __mhpm_skip: · line 709
Quoted 7 lines
lib/sbi/sbi_hart.c +3
-0@@ -693,6 +704,16 @@ __mhpm_skip: SBI_HART_EXT_SMSTATEEN, true); } +if (hfeatures->priv_version >=SBI_HART_PRIV_VER_1_12 ) { + val =csr_read_allowed (CSR_SSP , (unsigned long )&trap); + ssp_exist = trap.cause?0 :1 ;ssp_exist = trap.cause == 0;--
Andreas Schwab, SUSE Labs, schwab at suse.de
GPG Key fingerprint = 0196 BAD8 1CE9 1970 F4BE 1748 E4D4 88E3 0EEA B9D7 "And now for something completely different."
PATCH 2/2 lib: sbi: zsslpcfi detection and elp cfi state reflect back in status
In reply to Bin MengQuoted 8 lines · Bin Meng
lib/sbi/sbi_trap.c int sbi_trap_redirect(struct sbi_trap_regs *regs, · line 90
Quoted 6 lines
lib/sbi/sbi_trap.c +1
-0@@ -87,6 +87,7 @@ int sbi_trap_redirect(struct sbi_trap_regs *regs, struct sbi_trap_info *trap) {ulong hstatus, vsstatus, prev_mode; +bool elp = FALSE;s/FALSE/false
I followed whatever rest of the code is following. In the same function
"
int sbi_trap_redirect(struct sbi_trap_regs *regs,
struct sbi_trap_info *trap)
{
ulong hstatus, vsstatus, prev_mode;
bool elp = FALSE;
#if __riscv_xlen == 32
bool prev_virt = (regs->mstatusH & MSTATUSH_MPV) ? TRUE : FALSE;
#else
bool prev_virt = (regs->mstatus & MSTATUS_MPV) ? TRUE : FALSE;
#endif
/* By default, we redirect to HS-mode */
bool next_virt = FALSE;"
I can change it to false but it'll look ugly because at other places we're using upper case FALSE.
Let me know what needs to be done.
Quoted 47 lines
Quoted 19 lines
lib/sbi/sbi_trap.c +7
-0#if __riscv_xlen ==32 bool prev_virt = (regs->mstatusH &MSTATUSH_MPV ) ? TRUE : FALSE;#else @@ -100,6 +101,13 @@ int sbi_trap_redirect(struct sbi_trap_regs *regs,if (prev_mode !=PRV_S && prev_mode !=PRV_U )return SBI_ENOTSUPP ; +/* If extension has support for CFI, clear MPELP because redirecting to VS or (H)S */ +if (sbi_hart_has_extension (sbi_scratch_thishart_ptr (),SBI_HART_EXT_SSLPCFI )) { + elp = (regs->mstatus &MSTATUS_MPELP )? TRUE: FALSE; +/* Since redirecting, clear mpelp unconditionally */ + regs->mstatus &= ~MSTATUS_MPELP ; + } +/* If exceptions came from VS/VU-mode, redirect to VS-mode if * delegated in hedeleg */ lib/sbi/sbi_trap.c int sbi_trap_redirect(struct sbi_trap_regs *regs, · line 164
Quoted 4 lines
lib/sbi/sbi_trap.c +1
-0@@ -153,6 +161,11 @@ int sbi_trap_redirect(struct sbi_trap_regs *regs,/* Get VS-mode SSTATUS CSR */ vsstatus =csr_read (CSR_VSSTATUS ); +/*if elp was set, set it back in vsstatus */ need space after /*
Quoted 8 lines
lib/sbi/sbi_trap.c +4
-0+if (elp) { + vsstatus |=MSTATUS_SPELP ; + } +/* Set SPP for VS-mode */ vsstatus &= ~SSTATUS_SPP ;if (prev_mode ==PRV_S )lib/sbi/sbi_trap.c int sbi_trap_redirect(struct sbi_trap_regs *regs, · line 215
Quoted 10 lines
lib/sbi/sbi_trap.c +5
-0@@ -193,6 +206,11 @@ int sbi_trap_redirect(struct sbi_trap_regs *regs,/* Clear SIE for S-mode */ regs->mstatus &= ~MSTATUS_SIE ; + +/* if elp was set, set it back in mstatus */ +if (elp) { + regs->mstatus |=MSTATUS_SPELP ; + } }Regards,
Bin
PATCH 2/2 lib: sbi: zsslpcfi detection and elp cfi state reflect back in status
Quoted 26 lines · Deepak Gupta
Quoted 8 lines · Bin Meng
lib/sbi/sbi_trap.c int sbi_trap_redirect(struct sbi_trap_regs *regs, · line 90
Quoted 6 lines
lib/sbi/sbi_trap.c +1
-0@@ -87,6 +87,7 @@ int sbi_trap_redirect(struct sbi_trap_regs *regs, struct sbi_trap_info *trap) {ulong hstatus, vsstatus, prev_mode; +bool elp = FALSE;s/FALSE/false
I followed whatever rest of the code is following. In the same function
"
int sbi_trap_redirect(structsbi_trap_regs*regs,
structsbi_trap_info*trap){ ulong hstatus, vsstatus, prev_mode; bool elp = FALSE; #if __riscv_xlen == 32 bool prev_virt = (regs->mstatusH & MSTATUSH_MPV) ? TRUE : FALSE; #else bool prev_virt = (regs->mstatus & MSTATUS_MPV) ? TRUE : FALSE; #endif /* By default, we redirect to HS-mode */ bool next_virt = FALSE;
Quoted 5 lines
"
I can change it to false but it'll look ugly because at other places we're using upper case FALSE.
Let me know what needs to be done.
No need to worry about other places, as I already sent one patch to change all other places.
Regards,
Bin
cover Support for zisslpcfi in opensbi
In reply to Deepak GuptaI forgot to include URLs to qemu and linux implementation of Zisslpcfi
This is where I am maintaining qemu implementation
https://github.com/deepak0414/qemu/tree/gh_Zisslpcfi-0.1
And kernel implementation here
https://github.com/deepak0414/linux-riscv-cfi/tree/Zisslpcfi-0.1_v6.1-rc2
Quoted 29 lines · Deepak Gupta
Zisslpcfi [1] extension extends risc-v architecture to mitigate against control-flow integrity attacks (ROP/JOP/COP).
Zisslpcfi uses bits (b23-b29) in (m/s/vs) status CSR for enabling cfi in lesser privileged mode and record cfi state. One such state is expected landing pad (ELP). If forward cfi is enabled, indirect call/jmp updates hart's ELP state (1bit) to true. ELP state is cleared only by a landing pad instruction else trap is delivered with ELP state recorded in sstatus CSR.
This two patch series adds following changes to opensbi
- Adds support in opensbi to detect zisslpcfi
- trap redirection updates ELP state accordingly[1] - https://github.com/riscv/riscv-cfi
Deepak Gupta (2):
include: adding support for zsslpcfi encodings
lib: sbi: zsslpcfi detection and elp cfi state reflect back in statusinclude/sbi/riscv_encoding.h | 6 ++++++ include/sbi/sbi_hart.h | 2 ++ lib/sbi/sbi_hart.c | 21 +++++++++++++++++++++ lib/sbi/sbi_trap.c | 18 ++++++++++++++++++ 4 files changed, 47 insertions(+)Signature · 1 line
2.25.1