Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
adam.huang
Arm Trusted Firmware
Commits
ed756252
Commit
ed756252
authored
Apr 06, 2017
by
davidcunado-arm
Committed by
GitHub
Apr 06, 2017
Browse files
Merge pull request #886 from dp-arm/dp/stack-protector
Add support for GCC stack protection
parents
90e0ffd3
e6d2aea1
Changes
29
Show whitespace changes
Inline
Side-by-side
Makefile
View file @
ed756252
...
...
@@ -246,6 +246,12 @@ endif
# over the sources.
endif
################################################################################
# Include libraries' Makefile that are used in all BL
################################################################################
include
lib/stack_protector/stack_protector.mk
################################################################################
# Include the platform specific Makefile after the SPD Makefile (the platform
...
...
bl1/bl1.ld.S
View file @
ed756252
...
...
@@ -111,14 +111,20 @@ SECTIONS
ASSERT
(
__CPU_OPS_END__
>
__CPU_OPS_START__
,
"
cpu_ops
not
defined
for
this
platform
.
")
.
=
BL1_RW_BASE
;
ASSERT
(
BL1_RW_BASE
==
ALIGN
(
4096
),
"
BL1_RW_BASE
address
is
not
aligned
on
a
page
boundary
.
")
/
*
*
The
.
data
section
gets
copied
from
ROM
to
RAM
at
runtime
.
*
Its
LMA
must
be
16
-
byte
aligned
.
*
Its
LMA
should
be
16
-
byte
aligned
to
allow
efficient
copying
of
16
-
bytes
*
aligned
regions
in
it
.
*
Its
VMA
must
be
page
-
aligned
as
it
marks
the
first
read
/
write
page
.
*
*
It
must
be
placed
at
a
lower
address
than
the
stacks
if
the
stack
*
protector
is
enabled
.
Alternatively
,
the
.
data
.
stack_protector_canary
*
section
can
be
placed
independently
of
the
main
.
data
section
.
*/
.
=
BL1_RW_BASE
;
ASSERT
(.
==
ALIGN
(
4096
),
"
BL1_RW_BASE
address
is
not
aligned
on
a
page
boundary
.
")
.
data
.
:
ALIGN
(
16
)
{
__DATA_RAM_START__
=
.
;
*(.
data
*)
...
...
bl2/aarch32/bl2_entrypoint.S
View file @
ed756252
/*
*
Copyright
(
c
)
2016
,
ARM
Limited
and
Contributors
.
All
rights
reserved
.
*
Copyright
(
c
)
2016
-
2017
,
ARM
Limited
and
Contributors
.
All
rights
reserved
.
*
*
Redistribution
and
use
in
source
and
binary
forms
,
with
or
without
*
modification
,
are
permitted
provided
that
the
following
conditions
are
met
:
...
...
@@ -121,6 +121,15 @@ func bl2_entrypoint
*/
bl
plat_set_my_stack
/
*
---------------------------------------------
*
Initialize
the
stack
protector
canary
before
*
any
C
code
is
called
.
*
---------------------------------------------
*/
#if STACK_PROTECTOR_ENABLED
bl
update_stack_protector_canary
#endif
/
*
---------------------------------------------
*
Perform
early
platform
setup
&
platform
*
specific
early
arch
.
setup
e
.
g
.
mmu
setup
...
...
bl2/aarch64/bl2_entrypoint.S
View file @
ed756252
...
...
@@ -112,6 +112,15 @@ func bl2_entrypoint
*/
bl
plat_set_my_stack
/
*
---------------------------------------------
*
Initialize
the
stack
protector
canary
before
*
any
C
code
is
called
.
*
---------------------------------------------
*/
#if STACK_PROTECTOR_ENABLED
bl
update_stack_protector_canary
#endif
/
*
---------------------------------------------
*
Perform
early
platform
setup
&
platform
*
specific
early
arch
.
setup
e
.
g
.
mmu
setup
...
...
bl2/bl2.ld.S
View file @
ed756252
...
...
@@ -99,6 +99,11 @@ SECTIONS
*/
__RW_START__
=
.
;
/
*
*
.
data
must
be
placed
at
a
lower
address
than
the
stacks
if
the
stack
*
protector
is
enabled
.
Alternatively
,
the
.
data
.
stack_protector_canary
*
section
can
be
placed
independently
of
the
main
.
data
section
.
*/
.
data
.
:
{
__DATA_START__
=
.
;
*(.
data
*)
...
...
bl2u/aarch64/bl2u_entrypoint.S
View file @
ed756252
...
...
@@ -106,6 +106,15 @@ func bl2u_entrypoint
*/
bl
plat_set_my_stack
/
*
---------------------------------------------
*
Initialize
the
stack
protector
canary
before
*
any
C
code
is
called
.
*
---------------------------------------------
*/
#if STACK_PROTECTOR_ENABLED
bl
update_stack_protector_canary
#endif
/
*
---------------------------------------------
*
Perform
early
platform
setup
&
platform
*
specific
early
arch
.
setup
e
.
g
.
mmu
setup
...
...
bl2u/bl2u.ld.S
View file @
ed756252
...
...
@@ -86,6 +86,11 @@ SECTIONS
*/
__RW_START__
=
.
;
/
*
*
.
data
must
be
placed
at
a
lower
address
than
the
stacks
if
the
stack
*
protector
is
enabled
.
Alternatively
,
the
.
data
.
stack_protector_canary
*
section
can
be
placed
independently
of
the
main
.
data
section
.
*/
.
data
.
:
{
__DATA_START__
=
.
;
*(.
data
*)
...
...
bl31/bl31.ld.S
View file @
ed756252
...
...
@@ -140,6 +140,11 @@ SECTIONS
*/
__RW_START__
=
.
;
/
*
*
.
data
must
be
placed
at
a
lower
address
than
the
stacks
if
the
stack
*
protector
is
enabled
.
Alternatively
,
the
.
data
.
stack_protector_canary
*
section
can
be
placed
independently
of
the
main
.
data
section
.
*/
.
data
.
:
{
__DATA_START__
=
.
;
*(.
data
*)
...
...
bl32/tsp/aarch64/tsp_entrypoint.S
View file @
ed756252
...
...
@@ -138,6 +138,15 @@ func tsp_entrypoint
*/
bl
plat_set_my_stack
/
*
---------------------------------------------
*
Initialize
the
stack
protector
canary
before
*
any
C
code
is
called
.
*
---------------------------------------------
*/
#if STACK_PROTECTOR_ENABLED
bl
update_stack_protector_canary
#endif
/
*
---------------------------------------------
*
Perform
early
platform
setup
&
platform
*
specific
early
arch
.
setup
e
.
g
.
mmu
setup
...
...
docs/porting-guide.md
View file @
ed756252
...
...
@@ -920,6 +920,20 @@ kept aside to pass trusted firmware related information that next BL image
needs. This function is currently invoked in BL2 to pass this information to
the next BL image, when LOAD_IMAGE_V2 is enabled.
### Function : plat_get_stack_protector_canary()
Argument : void
Return : u_register_t
This function returns a random value that is used to initialize the canary used
when the stack protector is enabled with ENABLE_STACK_PROTECTOR. A predictable
value will weaken the protection as the attacker could easily write the right
value as part of the attack most of the time. Therefore, it should return a
true random number.
Note: For the protection to be effective, the global data need to be placed at
a lower address than the stack bases. Failure to do so would allow an attacker
to overwrite the canary as part of the stack buffer overflow attack.
### Function : plat_flush_next_bl_params()
Argument : void
...
...
docs/user-guide.md
View file @
ed756252
...
...
@@ -301,6 +301,14 @@ performed.
Currently, only PSCI is instrumented. Enabling this option enables
the
`ENABLE_PMF`
build option as well. Default is 0.
*
`ENABLE_STACK_PROTECTOR`
: String option to enable the stack protection
checks in GCC. Allowed values are "all", "strong" and "0" (default).
"strong" is the recommended stack protection level if this feature is
desired. 0 disables the stack protection. For all values other than 0, the
`plat_get_stack_protector_canary()`
platform hook needs to be implemented.
The value is passed as the last component of the option
`-fstack-protector-$ENABLE_STACK_PROTECTOR`
.
*
`ERROR_DEPRECATED`
: This option decides whether to treat the usage of
deprecated platform APIs, helper functions or drivers within Trusted
Firmware as error. It can take the value 1 (flag the use of deprecated
...
...
include/common/aarch32/el3_common_macros.S
View file @
ed756252
/*
*
Copyright
(
c
)
2016
,
ARM
Limited
and
Contributors
.
All
rights
reserved
.
*
Copyright
(
c
)
2016
-
2017
,
ARM
Limited
and
Contributors
.
All
rights
reserved
.
*
*
Redistribution
and
use
in
source
and
binary
forms
,
with
or
without
*
modification
,
are
permitted
provided
that
the
following
conditions
are
met
:
...
...
@@ -278,6 +278,12 @@
*
---------------------------------------------------------------------
*/
bl
plat_set_my_stack
#if STACK_PROTECTOR_ENABLED
.
if
\
_init_c_runtime
bl
update_stack_protector_canary
.
endif
/*
_init_c_runtime
*/
#endif
.
endm
#endif /* __EL3_COMMON_MACROS_S__ */
include/common/aarch64/el3_common_macros.S
View file @
ed756252
...
...
@@ -283,6 +283,12 @@
*
---------------------------------------------------------------------
*/
bl
plat_set_my_stack
#if STACK_PROTECTOR_ENABLED
.
if
\
_init_c_runtime
bl
update_stack_protector_canary
.
endif
/*
_init_c_runtime
*/
#endif
.
endm
#endif /* __EL3_COMMON_MACROS_S__ */
include/common/debug.h
View file @
ed756252
/*
* Copyright (c) 2013-201
6
, ARM Limited and Contributors. All rights reserved.
* Copyright (c) 2013-201
7
, ARM Limited and Contributors. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
...
...
@@ -84,6 +84,9 @@
void
__dead2
do_panic
(
void
);
#define panic() do_panic()
/* Function called when stack protection check code detects a corrupted stack */
void
__dead2
__stack_chk_fail
(
void
);
void
tf_printf
(
const
char
*
fmt
,
...)
__printflike
(
1
,
2
);
#endif
/* __ASSEMBLY__ */
...
...
include/lib/utils.h
View file @
ed756252
...
...
@@ -42,6 +42,20 @@
#define BIT(nr) (1UL << (nr))
#define MIN(x, y) __extension__ ({ \
__typeof__(x) _x = (x); \
__typeof__(y) _y = (y); \
(void)(&_x == &_y); \
_x < _y ? _x : _y; \
})
#define MAX(x, y) __extension__ ({ \
__typeof__(x) _x = (x); \
__typeof__(y) _y = (y); \
(void)(&_x == &_y); \
_x > _y ? _x : _y; \
})
/*
* The round_up() macro rounds up a value to the given boundary in a
* type-agnostic yet type-safe manner. The boundary must be a power of two.
...
...
include/plat/common/platform.h
View file @
ed756252
...
...
@@ -72,6 +72,16 @@ uintptr_t plat_get_ns_image_entrypoint(void);
unsigned
int
plat_my_core_pos
(
void
);
int
plat_core_pos_by_mpidr
(
u_register_t
mpidr
);
#if STACK_PROTECTOR_ENABLED
/*
* Return a new value to be used for the stack protection's canary.
*
* Ideally, this value is a random number that is impossible to predict by an
* attacker.
*/
u_register_t
plat_get_stack_protector_canary
(
void
);
#endif
/* STACK_PROTECTOR_ENABLED */
/*******************************************************************************
* Mandatory interrupt management functions
******************************************************************************/
...
...
@@ -326,7 +336,7 @@ int platform_setup_pm(const plat_pm_ops_t **);
unsigned
int
plat_get_aff_count
(
unsigned
int
,
unsigned
long
);
unsigned
int
plat_get_aff_state
(
unsigned
int
,
unsigned
long
);
#else
#else
/* __ENABLE_PLAT_COMPAT__ */
/*
* The below function enable Trusted Firmware components like SPDs which
* haven't migrated to the new platform API to compile on platforms which
...
...
@@ -335,4 +345,6 @@ unsigned int plat_get_aff_state(unsigned int, unsigned long);
unsigned
int
platform_get_core_pos
(
unsigned
long
mpidr
)
__deprecated
;
#endif
/* __ENABLE_PLAT_COMPAT__ */
#endif
/* __PLATFORM_H__ */
lib/stack_protector/aarch32/asm_stack_protector.S
0 → 100644
View file @
ed756252
/*
*
Copyright
(
c
)
2017
,
ARM
Limited
and
Contributors
.
All
rights
reserved
.
*
*
Redistribution
and
use
in
source
and
binary
forms
,
with
or
without
*
modification
,
are
permitted
provided
that
the
following
conditions
are
met
:
*
*
Redistributions
of
source
code
must
retain
the
above
copyright
notice
,
this
*
list
of
conditions
and
the
following
disclaimer
.
*
*
Redistributions
in
binary
form
must
reproduce
the
above
copyright
notice
,
*
this
list
of
conditions
and
the
following
disclaimer
in
the
documentation
*
and
/
or
other
materials
provided
with
the
distribution
.
*
*
Neither
the
name
of
ARM
nor
the
names
of
its
contributors
may
be
used
*
to
endorse
or
promote
products
derived
from
this
software
without
specific
*
prior
written
permission
.
*
*
THIS
SOFTWARE
IS
PROVIDED
BY
THE
COPYRIGHT
HOLDERS
AND
CONTRIBUTORS
"AS IS"
*
AND
ANY
EXPRESS
OR
IMPLIED
WARRANTIES
,
INCLUDING
,
BUT
NOT
LIMITED
TO
,
THE
*
IMPLIED
WARRANTIES
OF
MERCHANTABILITY
AND
FITNESS
FOR
A
PARTICULAR
PURPOSE
*
ARE
DISCLAIMED
.
IN
NO
EVENT
SHALL
THE
COPYRIGHT
HOLDER
OR
CONTRIBUTORS
BE
*
LIABLE
FOR
ANY
DIRECT
,
INDIRECT
,
INCIDENTAL
,
SPECIAL
,
EXEMPLARY
,
OR
*
CONSEQUENTIAL
DAMAGES
(
INCLUDING
,
BUT
NOT
LIMITED
TO
,
PROCUREMENT
OF
*
SUBSTITUTE
GOODS
OR
SERVICES
; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
*
INTERRUPTION
)
HOWEVER
CAUSED
AND
ON
ANY
THEORY
OF
LIABILITY
,
WHETHER
IN
*
CONTRACT
,
STRICT
LIABILITY
,
OR
TORT
(
INCLUDING
NEGLIGENCE
OR
OTHERWISE
)
*
ARISING
IN
ANY
WAY
OUT
OF
THE
USE
OF
THIS
SOFTWARE
,
EVEN
IF
ADVISED
OF
THE
*
POSSIBILITY
OF
SUCH
DAMAGE
.
*/
#include <arch.h>
#include <asm_macros.S>
#include <assert_macros.S>
.
globl
update_stack_protector_canary
/*
-----------------------------------------------------------------------
*
void
update_stack_protector_canary
(
void
)
*
*
Change
the
value
of
the
canary
used
for
stack
smashing
attacks
protection
.
*
Note
:
This
must
be
called
when
it
is
safe
to
call
C
code
,
but
this
cannot
be
*
called
by
C
code
.
Doing
this
will
make
the
check
fail
when
the
calling
*
function
returns
.
*
-----------------------------------------------------------------------
*/
func
update_stack_protector_canary
/
*
Use
r4
as
it
is
callee
-
saved
*/
mov
r4
,
lr
bl
plat_get_stack_protector_canary
/
*
Update
the
canary
with
the
returned
value
*/
ldr
r1
,
=
__stack_chk_guard
str
r0
,
[
r1
]
bx
r4
endfunc
update_stack_protector_canary
lib/stack_protector/aarch64/asm_stack_protector.S
0 → 100644
View file @
ed756252
/*
*
Copyright
(
c
)
2017
,
ARM
Limited
and
Contributors
.
All
rights
reserved
.
*
*
Redistribution
and
use
in
source
and
binary
forms
,
with
or
without
*
modification
,
are
permitted
provided
that
the
following
conditions
are
met
:
*
*
Redistributions
of
source
code
must
retain
the
above
copyright
notice
,
this
*
list
of
conditions
and
the
following
disclaimer
.
*
*
Redistributions
in
binary
form
must
reproduce
the
above
copyright
notice
,
*
this
list
of
conditions
and
the
following
disclaimer
in
the
documentation
*
and
/
or
other
materials
provided
with
the
distribution
.
*
*
Neither
the
name
of
ARM
nor
the
names
of
its
contributors
may
be
used
*
to
endorse
or
promote
products
derived
from
this
software
without
specific
*
prior
written
permission
.
*
*
THIS
SOFTWARE
IS
PROVIDED
BY
THE
COPYRIGHT
HOLDERS
AND
CONTRIBUTORS
"AS IS"
*
AND
ANY
EXPRESS
OR
IMPLIED
WARRANTIES
,
INCLUDING
,
BUT
NOT
LIMITED
TO
,
THE
*
IMPLIED
WARRANTIES
OF
MERCHANTABILITY
AND
FITNESS
FOR
A
PARTICULAR
PURPOSE
*
ARE
DISCLAIMED
.
IN
NO
EVENT
SHALL
THE
COPYRIGHT
HOLDER
OR
CONTRIBUTORS
BE
*
LIABLE
FOR
ANY
DIRECT
,
INDIRECT
,
INCIDENTAL
,
SPECIAL
,
EXEMPLARY
,
OR
*
CONSEQUENTIAL
DAMAGES
(
INCLUDING
,
BUT
NOT
LIMITED
TO
,
PROCUREMENT
OF
*
SUBSTITUTE
GOODS
OR
SERVICES
; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
*
INTERRUPTION
)
HOWEVER
CAUSED
AND
ON
ANY
THEORY
OF
LIABILITY
,
WHETHER
IN
*
CONTRACT
,
STRICT
LIABILITY
,
OR
TORT
(
INCLUDING
NEGLIGENCE
OR
OTHERWISE
)
*
ARISING
IN
ANY
WAY
OUT
OF
THE
USE
OF
THIS
SOFTWARE
,
EVEN
IF
ADVISED
OF
THE
*
POSSIBILITY
OF
SUCH
DAMAGE
.
*/
#include <arch.h>
#include <asm_macros.S>
#include <assert_macros.S>
.
globl
update_stack_protector_canary
/*
-----------------------------------------------------------------------
*
void
update_stack_protector_canary
(
void
)
*
*
Change
the
value
of
the
canary
used
for
stack
smashing
attacks
protection
.
*
Note
:
This
must
be
called
when
it
is
safe
to
call
C
code
,
but
this
cannot
be
*
called
by
C
code
.
Doing
this
will
make
the
check
fail
when
the
calling
*
function
returns
.
*
-----------------------------------------------------------------------
*/
func
update_stack_protector_canary
/
*
Use
x19
as
it
is
callee
-
saved
*/
mov
x19
,
x30
bl
plat_get_stack_protector_canary
/
*
Update
the
canary
with
the
returned
value
*/
adrp
x1
,
__stack_chk_guard
str
x0
,
[
x1
,
#
:
lo12
:
__stack_chk_guard
]
ret
x19
endfunc
update_stack_protector_canary
lib/stack_protector/stack_protector.c
0 → 100644
View file @
ed756252
/*
* Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of ARM nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <debug.h>
#include <platform.h>
#include <stdint.h>
/*
* Canary value used by the compiler runtime checks to detect stack corruption.
*
* Force the canary to be in .data to allow predictable memory layout relatively
* to the stacks.
*/
u_register_t
__attribute__
((
section
(
".data.stack_protector_canary"
)))
__stack_chk_guard
=
(
u_register_t
)
3288484550995823360ULL
;
/*
* Function called when the stack's canary check fails, which means the stack
* was corrupted. It must not return.
*/
void
__dead2
__stack_chk_fail
(
void
)
{
#if DEBUG
ERROR
(
"Stack corruption detected
\n
"
);
#endif
panic
();
}
lib/stack_protector/stack_protector.mk
0 → 100644
View file @
ed756252
#
# Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# Neither the name of ARM nor the names of its contributors may be used
# to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Boolean macro to be used in C code
STACK_PROTECTOR_ENABLED
:=
0
ifneq
(${ENABLE_STACK_PROTECTOR},0)
STACK_PROTECTOR_ENABLED
:=
1
BL_COMMON_SOURCES
+=
lib/stack_protector/stack_protector.c
\
lib/stack_protector/
${ARCH}
/asm_stack_protector.S
TF_CFLAGS
+=
-fstack-protector-
${ENABLE_STACK_PROTECTOR}
endif
$(eval
$(call
add_define,STACK_PROTECTOR_ENABLED))
Prev
1
2
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment