Give up on qsort for sk_FOO_sort

OpenSSL's API constraints are such that sk_FOO_sort must take a
comparison function of type
int (*cmp)(const FOO *const *a, const FOO *const *b)

However qsort expects a comparison function of type
int (*cmp)(const void *a, const void *b)

In C, it is UB to cast a function pointer to a different type and call
it, even if the underlying calling conventions are the same. Moreover,
as qsort doesn't take a context parameter on its comparisons, we cannot
do the usual convention with closures in C.

qsort_r and qsort_s would avoid this, but they are unusable. Too many
libcs don't have them, and those that do define them inconsistently. See
https://stackoverflow.com/a/39561369

It seems this UB has finally hit a sanitizer in fxbug.dev/128274.
Irritating as it is to not even have a working sort function, I think
the easiest option is to just give up on qsort. As we did with bsearch
in https://boringssl-review.googlesource.com/c/boringssl/+/35304, just
implement an in-place heap sort ourselves.

Bug: fxbug.dev/128274
Change-Id: I9de6b4018bf635da0d0c5a680bd7811d297b0bb3
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60507
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/crypto/stack/stack.c b/crypto/stack/stack.c
index a11533e..6b4f8bc 100644
--- a/crypto/stack/stack.c
+++ b/crypto/stack/stack.c
@@ -393,20 +393,52 @@
   return NULL;
 }
 
-#if defined(_MSC_VER)
-struct sort_compare_ctx {
-  OPENSSL_sk_call_cmp_func call_cmp_func;
-  OPENSSL_sk_cmp_func cmp_func;
-};
-
-static int sort_compare(void *ctx_v, const void *a, const void *b) {
-  struct sort_compare_ctx *ctx = ctx_v;
-  // |a| and |b| point to |void*| pointers which contain the actual values.
-  const void *const *a_ptr = a;
-  const void *const *b_ptr = b;
-  return ctx->call_cmp_func(ctx->cmp_func, *a_ptr, *b_ptr);
+static size_t parent_idx(size_t idx) {
+  assert(idx > 0);
+  return (idx - 1) / 2;
 }
-#endif
+
+static size_t left_idx(size_t idx) {
+  // The largest possible index is |PTRDIFF_MAX|, not |SIZE_MAX|. If
+  // |ptrdiff_t|, a signed type, is the same size as |size_t|, this cannot
+  // overflow.
+  assert(idx <= PTRDIFF_MAX);
+  static_assert(PTRDIFF_MAX <= (SIZE_MAX - 1) / 2, "2 * idx + 1 may oveflow");
+  return 2 * idx + 1;
+}
+
+// down_heap fixes the subtree rooted at |i|. |i|'s children must each satisfy
+// the heap property. Only the first |num| elements of |sk| are considered.
+static void down_heap(OPENSSL_STACK *sk, OPENSSL_sk_call_cmp_func call_cmp_func,
+                      size_t i, size_t num) {
+  assert(i < num && num <= sk->num);
+  for (;;) {
+    size_t left = left_idx(i);
+    if (left >= num) {
+      break;  // No left child.
+    }
+
+    // Swap |i| with the largest of its children.
+    size_t next = i;
+    if (call_cmp_func(sk->comp, sk->data[next], sk->data[left]) < 0) {
+      next = left;
+    }
+    size_t right = left + 1;  // Cannot overflow because |left < num|.
+    if (right < num &&
+        call_cmp_func(sk->comp, sk->data[next], sk->data[right]) < 0) {
+      next = right;
+    }
+
+    if (i == next) {
+      break;  // |i| is already larger than its children.
+    }
+
+    void *tmp = sk->data[i];
+    sk->data[i] = sk->data[next];
+    sk->data[next] = tmp;
+    i = next;
+  }
+}
 
 void OPENSSL_sk_sort(OPENSSL_STACK *sk,
                      OPENSSL_sk_call_cmp_func call_cmp_func) {
@@ -415,25 +447,25 @@
   }
 
   if (sk->num >= 2) {
-#if defined(_MSC_VER)
-    // MSVC's |qsort_s| is different from the C11 one.
-    // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/qsort-s?view=msvc-170
-    struct sort_compare_ctx ctx = {call_cmp_func, sk->comp};
-    qsort_s(sk->data, sk->num, sizeof(void *), sort_compare, &ctx);
-#else
-    // sk->comp is a function that takes pointers to pointers to elements, but
-    // qsort take a comparison function that just takes pointers to elements.
-    // However, since we're passing an array of pointers to qsort, we can just
-    // cast the comparison function and everything works.
-    //
-    // TODO(davidben): This is undefined behavior, but the call is in libc so,
-    // e.g., CFI does not notice. |qsort| is missing a void* parameter in its
-    // callback, while no one defines |qsort_r| or |qsort_s| consistently. See
+    // |qsort| lacks a context parameter in the comparison function for us to
+    // pass in |call_cmp_func| and |sk->comp|. While we could cast |sk->comp| to
+    // the expected type, it is undefined behavior in C can trip sanitizers.
+    // |qsort_r| and |qsort_s| avoid this, but using them is impractical. See
     // https://stackoverflow.com/a/39561369
-    int (*comp_func)(const void *, const void *) =
-        (int (*)(const void *, const void *))(sk->comp);
-    qsort(sk->data, sk->num, sizeof(void *), comp_func);
-#endif
+    //
+    // Use our own heap sort instead. This is not performance-sensitive, so we
+    // optimize for simplicity and size. First, build a max-heap in place.
+    for (size_t i = parent_idx(sk->num - 1); i < sk->num; i--) {
+      down_heap(sk, call_cmp_func, i, sk->num);
+    }
+
+    // Iteratively remove the maximum element to populate the result in reverse.
+    for (size_t i = sk->num - 1; i > 0; i--) {
+      void *tmp = sk->data[0];
+      sk->data[0] = sk->data[i];
+      sk->data[i] = tmp;
+      down_heap(sk, call_cmp_func, 0, i);
+    }
   }
   sk->sorted = 1;
 }
diff --git a/crypto/stack/stack_test.cc b/crypto/stack/stack_test.cc
index 7900fc9..228182b 100644
--- a/crypto/stack/stack_test.cc
+++ b/crypto/stack/stack_test.cc
@@ -24,6 +24,7 @@
 #include <gtest/gtest.h>
 
 #include <openssl/mem.h>
+#include <openssl/rand.h>
 
 
 // Define a custom stack type for testing.
@@ -481,3 +482,38 @@
   sk_TEST_INT_set_cmp_func(sk.get(), nullptr);
   EXPECT_FALSE(sk_TEST_INT_is_sorted(sk.get()));
 }
+
+TEST(StackTest, Sort) {
+  constexpr size_t kMaxLength = 100;
+  constexpr int kIterations = 500;
+  for (size_t len = 0; len < kMaxLength; len++) {
+    SCOPED_TRACE(len);
+    for (int iter = 0; iter < kIterations; iter++) {
+      // Make a random input list.
+      std::vector<int> vec(len);
+      RAND_bytes(reinterpret_cast<uint8_t *>(vec.data()),
+                 sizeof(int) * vec.size());
+      SCOPED_TRACE(testing::PrintToString(vec));
+
+      // Convert it to a |STACK_OF(TEST_INT)|.
+      bssl::UniquePtr<STACK_OF(TEST_INT)> sk(sk_TEST_INT_new(compare));
+      ASSERT_TRUE(sk);
+      for (int v : vec) {
+        auto value = TEST_INT_new(v);
+        ASSERT_TRUE(value);
+        ASSERT_TRUE(bssl::PushToStack(sk.get(), std::move(value)));
+      }
+
+      // Sort it with our sort implementation.
+      sk_TEST_INT_sort(sk.get());
+      std::vector<int> result;
+      for (const TEST_INT *v : sk.get()) {
+        result.push_back(*v);
+      }
+
+      // The result must match the STL's version.
+      std::sort(vec.begin(), vec.end());
+      EXPECT_EQ(vec, result);
+    }
+  }
+}