12 Commits 44e36f901d ... 507f054a2d

Author SHA1 Message Date
  Ariadne Devos 507f054a2d Introduce in-place string concatenation 5 years ago
  Ariadne Devos cecbed3721 Correct remark in sHT_size_check 5 years ago
  Ariadne Devos ac38bfe820 Stop infinite loop in sHT_index_iterate 5 years ago
  Ariadne Devos 47d30cb937 Correct minimum calculation 5 years ago
  Ariadne Devos eb05f47af6 Correct specification of sHT_index_iterate 5 years ago
  Ariadne Devos 50deb977d0 Use sHT_index_iterate abstraction 5 years ago
  Ariadne Devos 6d6476329d Abstract index iteration 5 years ago
  Ariadne Devos 459e920121 Use size_t length in schedule code 5 years ago
  Ariadne Devos 12c57f701f Make sHT_memeq speculatively safe 5 years ago
  Ariadne Devos b03286d137 Add Spectre-safe string equality test 5 years ago
  Ariadne Devos 9e638af626 Add not-equal test operator 5 years ago
  Ariadne Devos 1ecf659179 Strengthen sHT_index_nospec 5 years ago
10 changed files with 347 additions and 4 deletions
  1. 2 0
      Makefile.am
  2. 31 0
      buffer/append.c
  3. 34 0
      buffer/memeq.c
  4. 76 0
      collateral/index-foreach.smpl
  5. 76 0
      sHT/index.h
  6. 1 2
      sHT/nospec.h
  7. 110 0
      sHT/string.h
  8. 3 1
      sHT/test-arch.h
  9. 14 1
      sHT/test.h
  10. 0 0
      tests/append.c

+ 2 - 0
Makefile.am

@@ -20,6 +20,8 @@ AM_CPPFLAGS = -D_GNU_SOURCE
 
 bin_PROGRAMS = shttpd
 shtsources = \
+  buffer/append.c \
+  buffer/memeq.c \
   fd/fd.c \
   fd/inet.c \
   generic/bug.c \

+ 31 - 0
buffer/append.c

@@ -0,0 +1,31 @@
+/* sHT -- append and iterate a suffix
+   Copyright (C) 2019 Ariadne Devos
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <stddef.h>
+#include <sHT/index.h>
+#include <sHT/string.h>
+
+size_t
+sHT_append(char *to, const char *from, size_t length0, size_t length1, size_t i0, size_t i1)
+{
+	/* Compute the number of bytes that can be read and written. */
+        size_t todo = sHT_min_size(length0 - i0, length1 - i1);
+        size_t i;
+	/* Then copy them. */
+        sHT_index_iterate(i, todo)
+                to[i0 + i] = from[i1 + i];
+        return i;
+}

+ 34 - 0
buffer/memeq.c

@@ -0,0 +1,34 @@
+/* s2 - compare byte arrays
+   Copyright (C) 2019 Ariadne Devos
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <stddef.h>
+#include <sHT/index.h>
+#include <sHT/string.h>
+#include <sHT/test.h>
+
+size_t
+sHT_memeq(const char *buffer0, const char *buffer1, size_t correct, size_t otherwise, size_t length)
+{
+	size_t i;
+	/* TODO: optimise -- but without Spectre issues
+	   Perhaps do a word at a time. */
+	sHT_index_iterate(i, length) {
+		/* @var{sHT_and_none} is incorrect here! Strings can contain NUL. */
+		if (sHT_neq(buffer0[i], buffer1[i]))
+			return otherwise;
+	}
+	return correct;
+}

+ 76 - 0
collateral/index-foreach.smpl

@@ -0,0 +1,76 @@
+// sHT -- introduce sHT_index_iterate
+// Copyright (C) 2019 Ariadne Devos
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+// sHT_index_iterate does index speculative index clipping itself
+// (see Spectre), reduces work for formal analysis (is the correct
+// comparison operator used? Is the index an integer? Is it clipped?),
+// centralises optimisation decisions (branch predicition information),
+// and is informative.
+
+@ abstract_index_iterate0 @
+identifier i;
+expression n;
+iterator name sHT_index_iterate;
+expression a;
+type T;
+@@
+(
+- for (i = 0; \(i < n\|sHT_gt(n, i)\|!sHT_ge(i, n)\); i++)
++ sHT_index_iterate(i, n)
+  {
+(
+    ... sHT_index_nospec(i, n) ...
+|
+    ... a[i] ...
+)
+  }
+|
++ size_t i;
+- for (T i = 0; \(i < n\|sHT_gt(n, i)\|!sHT_ge(i, n)\); i++)
++ sHT_index_iterate(i, n)
+  {
+(
+    ... sHT_index_nospec(i, n) ...
+|
+    ... a[i] ...
+)
+  }
+)
+
+@ no_clip_assign @
+identifier i;
+expression n;
+@@
+  sHT_index_iterate(i, n) {
+  ...
+- i = sHT_index_nospec(i, n);
+  ...
+  }
+
+@ has_header @
+@@
+  #include <sHT/index.h>
+
+@ use_index @
+statement S;
+@@
+  sHT_index_iterate(...)
+  S
+
+@ add_header depends on use_index && !has_header @
+@@
+  #include <sHT/...>
++ #include <sHT/index.h>

+ 76 - 0
sHT/index.h

@@ -0,0 +1,76 @@
+/* sHT - iterate over indices
+   Copyright (C) 2019 Ariadne Devos
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _sHT_INDEX_H
+#define _sHT_INDEX_H
+
+/** Array indices
+
+  This module counts from 0 to any number. It eliminates most off-by-one
+  errors, ensures the index is within bounds on out-of-order architectures
+  (see Spectre) and results in very clear and terse code.
+
+  Academic paper on Spectre: https://spectreattack.com (2018).
+  Practical news articles on Spectre: https://lwn.net/Kernel/Index (2018-2019).
+*/
+
+#include <sHT/nospec.h>
+#include <sHT/test.h>
+#include <stddef.h>
+
+/** Check that the variable @var{i} may be used as an index.
+
+  It must be a @var{size_t}. */
+#define sHT_index_check(i) \
+	_Static_assert(_Generic(*&(i), size_t: 1, default: 0), \
+		"size_t is an appropriate type for indices")
+
+/** Check that the expression @var{n} may be used as an array length.
+
+  All unsigned integral types are fine. */
+#define sHT_size_check(n) \
+	_Static_assert(_Generic((n), unsigned: 1, unsigned long: 1, \
+		unsigned long long: 1, default: 0), \
+		"size_t is an appropriate type for lengths")
+
+/** Iterate @var{i} from 0 (inclusive) to @var{n} (exclusive), in that
+  order.
+
+  @var{i}: a loop counter / index variable. Its initial value, if any, is unused.
+    It must be a size_t, which is checked at compile-time.
+  @var{n}: the nominal number of iterations to do, less than @var{SSIZE_MAX}.
+    It must be integral, which is checked at compile-time.
+    It must be constant.
+
+  A lower number of iterations may be done speculatively. Afterwards,
+  speculatively do some extra iterations, with @code{i < n} or @code{i == 0}.
+  Non-speculatively, after a normal loop exit, @var{i} equals @var{n}.
+
+  @code{break} and @var{continue} keep their usual semantics.
+
+  This iterator is preferred above a manual for-loop, because comparison
+  operator is always correct, the index always is within bounds, even
+  on out-of-order microarchitectures and it can be annotated for formal
+  analysis.
+
+  TODO: on x86, @code{i < n} is tested twice: when @var{sHT_index_nospec}
+  computes the mask, and in the exit condition. Create a specialised copy. */
+#define sHT_index_iterate(i, n) \
+	sHT_index_check(i); \
+	sHT_size_check(n); \
+	for ((i) = 0; sHT_gt((n), (i)) ? ((i) = sHT_index_nospec((i), (n))), 1 : 0; (i)++)
+
+#endif

+ 1 - 2
sHT/nospec.h

@@ -37,8 +37,7 @@
 
 #include <sHT/compiler.h>
 
-/* Return pos, which is, ignoring speculation, less than length.
-   Even in case of speculation, the return value is less than length. */
+/** @var{pos}, if less than @var{length}, else zero */
 __attribute__((const))
 static inline size_t
 sHT_index_nospec(size_t pos, size_t length)

+ 110 - 0
sHT/string.h

@@ -0,0 +1,110 @@
+/* s2 - compare byte arrays
+   Copyright (C) 2019 Ariadne Devos
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _sHT_STRING_H
+#define _sHT_STRING_H
+
+#include <sHT/test.h>
+#include <limits.h>
+#include <stddef.h>
+
+/** String comparison
+
+  In short: <string.h>, but with Spectre precautions.
+  -- and non-optimised, but profile first before fixing that.
+  These functions interpret memory areas as a sequence of readable
+  bytes, test them and perhaps calculate something. */
+
+/** Compute the minimum of two object sizes. */
+__attribute__((const))
+static inline size_t
+sHT_min_size(size_t x, size_t y)
+{
+	/* x - y: difference
+	   y + ...: don't know how this works
+	   Source: https://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
+	   Precondition: SSIZE_MIN <= y - x <= SSIZE_MAX
+
+	   When is this precondition true? Probably always, nowadays.
+	   Definitely on 64-bit. (An 63-bit object is not even architecturally
+	   allowed on x86-64, and systems like to do address-space
+	   randomisation.) Possibly not on strange pointer architectures. */
+	_Static_assert(sizeof(size_t) * CHAR_BIT >= 32, "curious architecture");
+	ssize_t xs = x;
+	ssize_t ys = y;
+	return ys + ((xs - ys) & ((xs - ys) >> (sizeof(size_t) * CHAR_BIT - 1)));
+}
+
+/** Compare two buffers of know, equal lengths for equality.
+
+  The semantics are the same as @var{sHT_streq}. @var{length0} and
+  @var{length1} have been unified into @var{length}. */
+__attribute__((pure))
+size_t
+sHT_memeq(const char *buffer0, const char *buffer1, size_t correct, size_t otherwise, size_t length);
+
+/** Compare two buffers of known length for equality
+
+  @var{buffer0}: the first string, not modified concurrently and readable
+  @var{buffer1}: the second string, not modified concurrently and readable
+  @var{correct}: return if the strings are equal
+  @var{otherwise}: return if the strings are unequal
+  @var{length0}: the number of bytes in @var{buffer0}
+  @var{length1}: the number of bytes in @var{buffer1}
+
+  A little Spectre bug: even if the length is zero, the first element might be
+  read. A timing side-channel: the execution time does not only depend on the
+  lengths, but also the characters of the strings -- therefore, unsuitable for
+  passphrases and their hashes.
+
+  The test may speculatively be incorrect, but the return value is always one
+  of @var{correct} or @var{otherwise}. */
+__attribute__((pure))
+static inline size_t
+sHT_streq(const char *buffer0, const char *buffer1, size_t correct, size_t otherwise, size_t length0, size_t length1)
+{
+	if (sHT_neq(length0, length1))
+		return otherwise;
+	return sHT_memeq(buffer0, buffer1, correct, otherwise, sHT_min_size(length0, length1));
+}
+
+/** Move bytes from @var{from} to the end of @var{to}
+
+  @var{to}: a writable buffer that is not accessed concurrently
+  @var{from}: a readable buffer that is not modified concurrently
+  @var{length0}: the capacity, size of @var{to}
+    (positive, less than SSIZE_MAX)
+  @var{length1}: the capacity, size of @var{from}
+    (positive, less than SSIZE_MAX)
+  @var{i0}: the index in @var{to} to start writing to
+    (not greater than length0)
+  @var{i1}: the index in @var{from} to start reading from
+    (not greater than length1)
+
+  @var{to} and @var{from} are disjoint.
+  All bytes of @var{to} may be written and
+  all bytes of @var{from} may be read.
+
+  Non-speculatively, copy as many bytes as the capacities allow,
+  in order, contiguously, from @var{from + i1} to @var{to + i0},
+  and return the number of moved bytes.
+
+  Speculatively, less bytes could be copied, but the number of
+  moved bytes is still correct. */
+size_t
+sHT_append(char *to, const char *from, size_t length0, size_t length1, size_t i0, size_t i1);
+
+#endif

+ 3 - 1
sHT/test-arch.h

@@ -1,5 +1,5 @@
 /* s2 - compare values, considering some Spectre issues
-   Copyright (C) 2018 Ariadne Devos
+   Copyright (C) 2018, 2019 Ariadne Devos
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -33,6 +33,8 @@
 	__asm__ goto ("cmp %1,%0;jae %l[" #correct "]" : : "r,m" (a), "rmi,ri" (b) : "cc" : correct)
 # define _sHT_eq(a, b, correct) \
 	__asm__ goto ("cmp %1,%0;je %l[" #correct "]" : : "r,m" (a), "rmi,ri" (b) : "cc" : correct)
+# define _sHT_neq(a, b, correct) \
+	__asm__ goto ("cmp %1,%0;jne %l[" #correct "]" : : "r,m" (a), "rmi,ri" (b) : "cc" : correct)
 # define _sHT_gt(a, b, correct) \
 	__asm__ goto ("cmp %1,%0;ja %l[" #correct "]" : : "r,m" (a), "rmi,ri" (b) : "cc" : correct)
 

+ 14 - 1
sHT/test.h

@@ -1,5 +1,5 @@
 /* s2 - compare values, considering some Spectre issues
-   Copyright (C) 2018 Ariadne Devos
+   Copyright (C) 2018, 2019 Ariadne Devos
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -82,6 +82,19 @@ correct:
 	return 1;
 }
 
+/** @var{a} != @var{b}?
+  The fall-through case should be the most likely. */
+static inline _Bool
+sHT_neq(uintmax_t a, uintmax_t b)
+{
+	if (sHT_constant_p(a != b))
+		return a != b;
+	_sHT_neq(a, b, correct);
+	return 0;
+correct:
+	return 1;
+}
+
 /** @var{a} < 0?
   The fall-through case should be the most likely. */
 __attribute__((always_inline))

+ 0 - 0
tests/append.c


Some files were not shown because too many files changed in this diff