public inbox for libc-alpha@sourceware.org
 help / color / mirror / Atom feed
From: Carlos O'Donell <carlos@redhat.com>
To: Adhemerval Zanella <adhemerval.zanella@linaro.org>,
	libc-alpha@sourceware.org
Cc: Siddhesh Poyarekar <siddhesh@gotplt.org>
Subject: Re: [PATCH v3 01/10] cdefs.h: Add clang fortify directives
Date: Tue, 20 Feb 2024 17:05:13 -0500	[thread overview]
Message-ID: <c5de7b0c-1281-4ad3-b845-4354e5fef1a1@redhat.com> (raw)
In-Reply-To: <20240208184622.332678-2-adhemerval.zanella@linaro.org>

On 2/8/24 13:46, Adhemerval Zanella wrote:
> For instance, the read wrapper is currently expanded as:

LGTM.

I read this, and I read the entire plan google doc from the original reporter.

It makes sense to me and splits the two implementations differently for both
compilers. I don't object to this strategy of slightly split implementations
and I think we need to be practical about what each compiler does well and
doesn't do well when implementing fortification in library headers.

Problems I see in the future that do not block this series:

- Where are the tests? We have 10 commits to add new clang fortification, but
  we don't provide any tests for fortification using glibc headers. We should
  try hard to avoid regression by providing test cases that can be compiled with
  glibc headers e.g. compile-only tests (which we need for linux kernel headers
  testing too) that produce expected results. This would also allow us to test
  glibc as a reverse-dependency for clang CI/CD to catch upstream changes that
  break fortification. This *almost* blocks this series, but since we've never
  had compile-time tests [1] I can't expect a series to add them.

- We already have duplicated warning messages and this adds more duplicated
  warning messages which can result in out-of-date warning messages. We can and
  should refactor the messages.

- Some macros say "clang" in the title when they could be generically implemented
  for any compiler. We can and should refactor the macros a little more as we
  review refactoring messages.

- If we want to accelerate and support the adoption of security focused features
  like _FORTIFY_SOURCE then we must standardize on something that library
  developers can use more readily on Linux. Anyone reading glibc sources for
  examples is going to have a very very steep learning curve. Either providing
  such functionality in gcc or glibc would be useful (similar to discussions of
  support for symbol versioning).

None of these should block this series, but I wanted to mention them as they were
things I thought about during the review.

Tested on x86_64 and i686 with gcc only.

Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Tested-by: Carlos O'Donell <carlos@redhat.com>

[1] https://inbox.sourceware.org/libc-alpha/575679A0.4090209@redhat.com/


>   extern __inline
>   __attribute__((__always_inline__))
>   __attribute__((__artificial__))
>   __attribute__((__warn_unused_result__))
>   ssize_t read (int __fd, void *__buf, size_t __nbytes)
>   {
>      return __glibc_safe_or_unknown_len (__nbytes,
>                                          sizeof (char),
>                                          __glibc_objsize0 (__buf))
>             ? __read_alias (__fd, __buf, __nbytes)
>             : __glibc_unsafe_len (__nbytes,
>                                   sizeof (char),
>                                   __glibc_objsize0 (__buf))
>               ? __read_chk_warn (__fd,
>                                  __buf,
>                                  __nbytes,
>                                  __builtin_object_size (__buf, 0))
>               : __read_chk (__fd,
>                             __buf,
>                             __nbytes,
>                             __builtin_object_size (__buf, 0));
>   }
> 
> The wrapper relies on __builtin_object_size call lowers to a constant at
> compile-time and many other operations in the wrapper depends on
> having a single, known value for parameters.   Because this is
> impossible to have for function parameters, the wrapper depends heavily
> on inlining to work and While this is an entirely viable approach on
> GCC, it is not fully reliable on clang.  This is because by the time llvm
> gets to inlining and optimizing, there is a minimal reliable source and
> type-level information available (more information on a more deep
> explanation on how to fortify wrapper works on clang [1]).
> 
> To allow the wrapper to work reliably and with the same functionality as
> with GCC, clang requires a different approach:
> 
>   * __attribute__((diagnose_if(c, “str”, “warning”))) which is a function
>     level attribute; if the compiler can determine that 'c' is true at
>     compile-time, it will emit a warning with the text 'str1'.  If it would
>     be better to emit an error, the wrapper can use "error" instead of
>     "warning".
> 
>   * __attribute__((overloadable)) which is also a function-level attribute;
>     and it allows C++-style overloading to occur on C functions.
> 
>   * __attribute__((pass_object_size(n))) which is a parameter-level
>     attribute; and it makes the compiler evaluate
>     __builtin_object_size(param, n) at each call site of the function
>     that has the parameter, and passes it in as a hidden parameter.
> 
>     This attribute has two side-effects that are key to how FORTIFY works:
> 
>     1. It can overload solely on pass_object_size (e.g. there are two
>        overloads of foo in
> 
>          void foo(char * __attribute__((pass_object_size(0))) c);
>          void foo(char *);
> 
>       (The one with pass_object_size attribute has precende over the
>       default one).
> 
>     2. A function with at least one pass_object_size parameter can never
>        have its address taken (and overload resolution respects this).
> 
> Thus the read wrapper can be implemented as follows, without
> hindering any fortify coverage compile and runtime:
> 
>   extern __inline
>   __attribute__((__always_inline__))
>   __attribute__((__artificial__))
>   __attribute__((__overloadable__))
>   __attribute__((__warn_unused_result__))
>   ssize_t read (int __fd,
>                  void *const __attribute__((pass_object_size (0))) __buf,
>                  size_t __nbytes)
>      __attribute__((__diagnose_if__ ((((__builtin_object_size (__buf, 0)) != -1ULL
>                                         && (__nbytes) > (__builtin_object_size (__buf, 0)) / (1))),
>                                      "read called with bigger length than size of the destination buffer",
>                                      "warning")))
>   {
>     return (__builtin_object_size (__buf, 0) == (size_t) -1)
>       ? __read_alias (__fd,
>                       __buf,
>                       __nbytes)
>       : __read_chk (__fd,
>                     __buf,
>                     __nbytes,
>                     __builtin_object_size (__buf, 0));
>   }
> 
> To avoid changing the current semantic for GCC, a set of macros is
> defined to enable the clang required attributes, along with some changes
> on internal macros to avoid the need to issue the symbol_chk symbols
> (which are done through the __diagnose_if__ attribute for clang).
> The read wrapper is simplified as:
> 
>   __fortify_function __attribute_overloadable__ __wur
>   ssize_t read (int __fd,
>                 __fortify_clang_overload_arg0 (void *, ,__buf),
>                 size_t __nbytes)
>        __fortify_clang_warning_only_if_bos0_lt (__nbytes, __buf,
>                                                 "read called with bigger length than "
>                                                 "size of the destination buffer")
> 
>   {
>     return __glibc_fortify (read, __nbytes, sizeof (char),
>                             __glibc_objsize0 (__buf),
>                             __fd, __buf, __nbytes);
>   }
> 
> There is no expected semantic or code change when using GCC.
> 
> Also, clang does not support __va_arg_pack, so variadic functions are
> expanded to call va_arg implementations.  The error function must not
> have bodies (address takes are expanded to nonfortified calls), and
> with the __fortify_function compiler might still create a body with the
> C++ mangling name (due to the overload attribute).  In this case, the
> function is defined with __fortify_function_error_function macro
> instead.
> 
> [1] https://docs.google.com/document/d/1DFfZDICTbL7RqS74wJVIJ-YnjQOj1SaoqfhbgddFYSM/edit
> 
> Checked on aarch64, armhf, x86_64, and i686.
> ---
>  misc/sys/cdefs.h | 151 ++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 149 insertions(+), 2 deletions(-)
> 
> diff --git a/misc/sys/cdefs.h b/misc/sys/cdefs.h
> index 520231dbea..62507044c8 100644
> --- a/misc/sys/cdefs.h
> +++ b/misc/sys/cdefs.h
> @@ -145,6 +145,14 @@
>  #endif
>  
>  
> +/* The overloadable attribute was added on clang 2.6. */
> +#if defined __clang_major__ \
> +    && (__clang_major__ + (__clang_minor__ >= 6) > 2)
> +# define __attribute_overloadable__ __attribute__((__overloadable__))
> +#else
> +# define __attribute_overloadable__
> +#endif

OK.

> +
>  /* Fortify support.  */
>  #define __bos(ptr) __builtin_object_size (ptr, __USE_FORTIFY_LEVEL > 1)
>  #define __bos0(ptr) __builtin_object_size (ptr, 0)
> @@ -187,27 +195,166 @@
>  						   __s, __osz))		      \
>     && !__glibc_safe_len_cond ((__SIZE_TYPE__) (__l), __s, __osz))
>  
> +/* To correctly instrument the fortify wrapper clang requires the
> +   pass_object_size attribute, and the attribute has the restriction that the
> +   argument needs to be 'const'.  Furthermore, to make it usable with C

OK.

> +   interfaces, clang provides the overload attribute, which provides a C++
> +   like function overload support.  The overloaded fortify wrapper with the
> +   pass_object_size attribute has precedence over the default symbol.
> +
> +   Also, clang does not support __va_arg_pack, so variadic functions are
> +   expanded to issue va_arg implementations. The error function must not have
> +   bodies (address takes are expanded to nonfortified calls), and with
> +   __fortify_function compiler might still create a body with the C++
> +   mangling name (due to the overload attribute).  In this case, the function
> +   is defined with __fortify_function_error_function macro instead.
> +
> +   The argument size check is also done with a clang-only attribute,
> +   __attribute__ ((__diagnose_if__ (...))), different than gcc which calls
> +   symbol_chk_warn alias with uses __warnattr attribute.
> +
> +   The pass_object_size was added on clang 4.0, __diagnose_if__ on 5.0,
> +   and pass_dynamic_object_size on 9.0.  */
> +#if defined __clang_major__ && __clang_major__ >= 5
> +# define __fortify_use_clang 1
> +
> +# define __fortify_function_error_function static __attribute__((__unused__))

OK.

> +
> +# define __fortify_clang_pass_object_size_n(n) \
> +  __attribute__ ((__pass_object_size__ (n)))
> +# define __fortify_clang_pass_object_size0 \
> +  __fortify_clang_pass_object_size_n (0)
> +# define __fortify_clang_pass_object_size \
> +  __fortify_clang_pass_object_size_n (__USE_FORTIFY_LEVEL > 1)
> +
> +# if __clang_major__ >= 9
> +#  define __fortify_clang_pass_dynamic_object_size_n(n) \
> +  __attribute__ ((__pass_dynamic_object_size__ (n)))
> +#  define __fortify_clang_pass_dynamic_object_size0 \
> +  __fortify_clang_pass_dynamic_object_size_n (0)
> +#  define __fortify_clang_pass_dynamic_object_size \
> +  __fortify_clang_pass_dynamic_object_size_n (1)
> +# else
> +#  define __fortify_clang_pass_dynamic_object_size_n(n)
> +#  define __fortify_clang_pass_dynamic_object_size0
> +#  define __fortify_clang_pass_dynamic_object_size

OK.

> +# endif

OK.

> +
> +# define __fortify_clang_bos_static_lt_impl(bos_val, n, s) \
> +  ((bos_val) != -1ULL && (n) > (bos_val) / (s))
> +# define __fortify_clang_bos_static_lt2(__n, __e, __s) \
> +  __fortify_clang_bos_static_lt_impl (__bos (__e), __n, __s)
> +# define __fortify_clang_bos_static_lt(__n, __e) \
> +  __fortify_clang_bos_static_lt2 (__n, __e, 1)
> +# define __fortify_clang_bos0_static_lt2(__n, __e, __s) \
> +  __fortify_clang_bos_static_lt_impl (__bos0 (__e), __n, __s)
> +# define __fortify_clang_bos0_static_lt(__n, __e) \
> +  __fortify_clang_bos0_static_lt2 (__n, __e, 1)
> +
> +# define __fortify_clang_bosn_args(bos_fn, n, buf, div, complaint) \
> +  (__fortify_clang_bos_static_lt_impl (bos_fn (buf), n, div)), (complaint), \
> +  "warning"
> +
> +# define __fortify_clang_warning(__c, __msg) \
> +  __attribute__ ((__diagnose_if__ ((__c), (__msg), "warning")))
> +# define __fortify_clang_warning_only_if_bos0_lt(n, buf, complaint) \
> +  __attribute__ ((__diagnose_if__ \
> +		  (__fortify_clang_bosn_args (__bos0, n, buf, 1, complaint))))
> +# define __fortify_clang_warning_only_if_bos0_lt2(n, buf, div, complaint) \
> +  __attribute__ ((__diagnose_if__ \
> +		  (__fortify_clang_bosn_args (__bos0, n, buf, div, complaint))))
> +# define __fortify_clang_warning_only_if_bos_lt(n, buf, complaint) \
> +  __attribute__ ((__diagnose_if__ \
> +		  (__fortify_clang_bosn_args (__bos, n, buf, 1, complaint))))
> +# define __fortify_clang_warning_only_if_bos_lt2(n, buf, div, complaint) \
> +  __attribute__ ((__diagnose_if__ \
> +		  (__fortify_clang_bosn_args (__bos, n, buf, div, complaint))))
> +
> +# if __USE_FORTIFY_LEVEL == 3
> +#  define __fortify_clang_overload_arg(__type, __attr, __name) \
> +  __type __attr const __fortify_clang_pass_dynamic_object_size __name
> +#  define __fortify_clang_overload_arg0(__type, __attr, __name) \
> +  __type __attr const __fortify_clang_pass_dynamic_object_size0 __name
> +# else
> +#  define __fortify_clang_overload_arg(__type, __attr, __name) \
> +  __type __attr const __fortify_clang_pass_object_size __name
> +#  define __fortify_clang_overload_arg0(__type, __attr, __name) \
> +  __type __attr const __fortify_clang_pass_object_size0 __name
> +# endif
> +
> +# define __fortify_clang_mul_may_overflow(size, n) \
> +  ((size | n) >= (((size_t)1) << (8 * sizeof (size_t) / 2)))
> +
> +# define __fortify_clang_size_too_small(__bos, __dest, __len) \
> +  (__bos (__dest) != (size_t) -1 && __bos (__dest) < __len)
> +# define __fortify_clang_warn_if_src_too_large(__dest, __src) \
> +  __fortify_clang_warning (__fortify_clang_size_too_small (__glibc_objsize, \
> +							   __dest, \
> +							   __builtin_strlen (__src) + 1), \
> +			   "destination buffer will always be overflown by source")
> +# define __fortify_clang_warn_if_dest_too_small(__dest, __len) \
> +  __fortify_clang_warning (__fortify_clang_size_too_small (__glibc_objsize, \
> +                                                           __dest, \
> +                                                           __len), \
> +                           "function called with bigger length than the destination buffer")
> +# define __fortify_clang_warn_if_dest_too_small0(__dest, __len) \
> +  __fortify_clang_warning (__fortify_clang_size_too_small (__glibc_objsize0, \
> +                                                           __dest, \
> +                                                           __len), \
> +                           "function called with bigger length than the destination buffer")
> +#else
> +# define __fortify_use_clang 0
> +# define __fortify_clang_warning(__c, __msg)
> +# define __fortify_clang_warning_only_if_bos0_lt(__n, __buf, __complaint)
> +# define __fortify_clang_warning_only_if_bos0_lt2(__n, __buf, __div, complaint)
> +# define __fortify_clang_warning_only_if_bos_lt(__n, __buf, __complaint)
> +# define __fortify_clang_warning_only_if_bos_lt2(__n, __buf, div, __complaint)

OK. 

> +# define __fortify_clang_overload_arg(__type, __attr, __name) \
> + __type __attr __name
> +# define __fortify_clang_overload_arg0(__type, __attr, __name) \
> +  __fortify_clang_overload_arg (__type, __attr, __name)

OK. These 2 need to implement a default __type __attr __name.

> +# define __fortify_clang_warn_if_src_too_large(__dest, __src)
> +# define __fortify_clang_warn_if_dest_too_small(__dest, __len)
> +# define __fortify_clang_warn_if_dest_too_small0(__dest, __len)
> +#endif

OK.

> +
> +
>  /* Fortify function f.  __f_alias, __f_chk and __f_chk_warn must be
>     declared.  */
>  
> -#define __glibc_fortify(f, __l, __s, __osz, ...) \
> +#if !__fortify_use_clang
> +# define __glibc_fortify(f, __l, __s, __osz, ...) \
>    (__glibc_safe_or_unknown_len (__l, __s, __osz)			      \
>     ? __ ## f ## _alias (__VA_ARGS__)					      \
>     : (__glibc_unsafe_len (__l, __s, __osz)				      \
>        ? __ ## f ## _chk_warn (__VA_ARGS__, __osz)			      \
>        : __ ## f ## _chk (__VA_ARGS__, __osz)))
> +#else
> +# define __glibc_fortify(f, __l, __s, __osz, ...) \
> +  (__osz == (__SIZE_TYPE__) -1)						      \
> +   ? __ ## f ## _alias (__VA_ARGS__)					      \
> +   : __ ## f ## _chk (__VA_ARGS__, __osz)

OK.

> +#endif
>  
>  /* Fortify function f, where object size argument passed to f is the number of
>     elements and not total size.  */
>  
> -#define __glibc_fortify_n(f, __l, __s, __osz, ...) \
> +#if !__fortify_use_clang
> +# define __glibc_fortify_n(f, __l, __s, __osz, ...) \
>    (__glibc_safe_or_unknown_len (__l, __s, __osz)			      \
>     ? __ ## f ## _alias (__VA_ARGS__)					      \
>     : (__glibc_unsafe_len (__l, __s, __osz)				      \
>        ? __ ## f ## _chk_warn (__VA_ARGS__, (__osz) / (__s))		      \
>        : __ ## f ## _chk (__VA_ARGS__, (__osz) / (__s))))
> +# else
> +# define __glibc_fortify_n(f, __l, __s, __osz, ...) \
> +  (__osz == (__SIZE_TYPE__) -1)						      \
> +   ? __ ## f ## _alias (__VA_ARGS__)					      \
> +   : __ ## f ## _chk (__VA_ARGS__, (__osz) / (__s))

OK.

>  #endif
>  
> +#endif /* __USE_FORTIFY_LEVEL > 0 */

OK. I wondered about the lack of #-indending here but we seem to avoid indenting for the
initial fortification leve.

> +
>  #if __GNUC_PREREQ (4,3)
>  # define __warnattr(msg) __attribute__((__warning__ (msg)))
>  # define __errordecl(name, msg) \

-- 
Cheers,
Carlos.


  parent reply	other threads:[~2024-02-20 22:05 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-02-08 18:46 [PATCH v3 00/10] Improve fortify support with clang Adhemerval Zanella
2024-02-08 18:46 ` [PATCH v3 01/10] cdefs.h: Add clang fortify directives Adhemerval Zanella
2024-02-20 19:48   ` Siddhesh Poyarekar
2024-02-21  5:48     ` Sam James
2024-02-22 18:21     ` Adhemerval Zanella Netto
2024-02-22 19:41       ` Siddhesh Poyarekar
2024-02-20 22:05   ` Carlos O'Donell [this message]
2024-02-20 22:45     ` Joseph Myers
2024-02-22 18:39     ` Adhemerval Zanella Netto
2024-02-08 18:46 ` [PATCH v3 02/10] libio: Improve fortify with clang Adhemerval Zanella
2024-02-20 22:06   ` Carlos O'Donell
2024-02-22 18:41     ` Adhemerval Zanella Netto
2024-02-08 18:46 ` [PATCH v3 03/10] string: " Adhemerval Zanella
2024-02-20 22:06   ` Carlos O'Donell
2024-02-08 18:46 ` [PATCH v3 04/10] stdlib: " Adhemerval Zanella
2024-02-20 22:05   ` Carlos O'Donell
2024-02-22 18:45     ` Adhemerval Zanella Netto
2024-02-22 19:24     ` Adhemerval Zanella Netto
2024-02-26 14:07       ` Carlos O'Donell
2024-02-08 18:46 ` [PATCH v3 05/10] unistd: " Adhemerval Zanella
2024-02-20 22:06   ` Carlos O'Donell
2024-02-08 18:46 ` [PATCH v3 06/10] socket: " Adhemerval Zanella
2024-02-21 13:20   ` Carlos O'Donell
2024-02-08 18:46 ` [PATCH v3 07/10] syslog: " Adhemerval Zanella
2024-02-20 22:05   ` Carlos O'Donell
2024-02-08 18:46 ` [PATCH v3 08/10] wcsmbs: " Adhemerval Zanella
2024-02-20 22:05   ` Carlos O'Donell
2024-02-08 18:46 ` [PATCH v3 09/10] debug: Improve fcntl.h fortify warnings " Adhemerval Zanella
2024-02-20 22:05   ` Carlos O'Donell
2024-02-08 18:46 ` [PATCH v3 10/10] debug: Improve mqueue.h " Adhemerval Zanella
2024-02-20 22:05   ` Carlos O'Donell
2024-02-20 13:17 ` [PATCH v3 00/10] Improve fortify support " Adhemerval Zanella Netto

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=c5de7b0c-1281-4ad3-b845-4354e5fef1a1@redhat.com \
    --to=carlos@redhat.com \
    --cc=adhemerval.zanella@linaro.org \
    --cc=libc-alpha@sourceware.org \
    --cc=siddhesh@gotplt.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).