AddressSanitizer: stack-buffer-underflow in JSC::Probe::Page::Page
authormark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 14 Sep 2017 23:08:54 +0000 (23:08 +0000)
committermark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 14 Sep 2017 23:08:54 +0000 (23:08 +0000)
https://bugs.webkit.org/show_bug.cgi?id=176874
<rdar://problem/34436415>

Reviewed by Saam Barati.

Source/JavaScriptCore:

1. Make Probe::Stack play nice with ASan by:

   a. using a local memcpy implementation that suppresses ASan on ASan builds.
      We don't want to use std:memcpy() which validates stack memory because
      we are intentionally copying stack memory beyond the current frame.

   b. changing Stack::s_chunkSize to equal sizeof(uintptr_t) on ASan builds.
      This ensures that Page::flushWrites() only writes stack memory that was
      modified by a probe.  The probes should only modify stack memory that
      belongs to JSC stack data structures.  We don't want to inadvertently
      modify adjacent words that may belong to ASan (which may happen if
      s_chunkSize is larger than sizeof(uintptr_t)).

   c. fixing a bug in Page dirtyBits management for when the size of the value to
      write is greater than s_chunkSize.  The fix in generic, but in practice,
      this currently only manifests on 32-bit ASan builds because
      sizeof(uintptr_t) and s_chunkSize are 32-bit, and we may write 64-bit
      values.

   d. making Page::m_dirtyBits 64 bits always.  This maximizes the number of
      s_chunksPerPage we can have even on ASan builds.

2. Fixed the bottom most Probe::Context and Probe::Stack get/set methods to use
   std::memcpy to avoid strict aliasing issues.

3. Optimized the implementation of Page::physicalAddressFor().

4. Optimized the implementation of Stack::set() in the recording of the low
   watermark.  We just record the lowest raw pointer now, and only compute the
   alignment to its chuck boundary later when the low watermark is requested.

5. Changed a value in testmasm to make the test less vulnerable to rounding issues.

No new test needed because this is already covered by testmasm with ASan enabled.

* assembler/ProbeContext.h:
(JSC::Probe::CPUState::gpr const):
(JSC::Probe::CPUState::spr const):
(JSC::Probe::Context::gpr):
(JSC::Probe::Context::spr):
(JSC::Probe::Context::fpr):
(JSC::Probe::Context::gprName):
(JSC::Probe::Context::sprName):
(JSC::Probe::Context::fprName):
(JSC::Probe::Context::gpr const):
(JSC::Probe::Context::spr const):
(JSC::Probe::Context::fpr const):
(JSC::Probe::Context::pc):
(JSC::Probe::Context::fp):
(JSC::Probe::Context::sp):
(JSC::Probe:: const): Deleted.
* assembler/ProbeStack.cpp:
(JSC::Probe::copyStackPage):
(JSC::Probe::Page::Page):
(JSC::Probe::Page::flushWrites):
* assembler/ProbeStack.h:
(JSC::Probe::Page::get):
(JSC::Probe::Page::set):
(JSC::Probe::Page::dirtyBitFor):
(JSC::Probe::Page::physicalAddressFor):
(JSC::Probe::Stack::lowWatermark):
(JSC::Probe::Stack::get):
(JSC::Probe::Stack::set):
* assembler/testmasm.cpp:
(JSC::testProbeModifiesStackValues):

Source/WTF:

Added a convenience version of roundUpToMultipleOf() so that it can be applied to
pointers without the client having to cast explicitly.

* wtf/StdLibExtras.h:
(WTF::roundUpToMultipleOf):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@222058 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/assembler/ProbeContext.h
Source/JavaScriptCore/assembler/ProbeStack.cpp
Source/JavaScriptCore/assembler/ProbeStack.h
Source/JavaScriptCore/assembler/testmasm.cpp
Source/WTF/ChangeLog
Source/WTF/wtf/StdLibExtras.h

index 8df5a5a..de07d19 100644 (file)
@@ -1,3 +1,77 @@
+2017-09-14  Mark Lam  <mark.lam@apple.com>
+
+        AddressSanitizer: stack-buffer-underflow in JSC::Probe::Page::Page
+        https://bugs.webkit.org/show_bug.cgi?id=176874
+        <rdar://problem/34436415>
+
+        Reviewed by Saam Barati.
+
+        1. Make Probe::Stack play nice with ASan by:
+
+           a. using a local memcpy implementation that suppresses ASan on ASan builds.
+              We don't want to use std:memcpy() which validates stack memory because
+              we are intentionally copying stack memory beyond the current frame.
+
+           b. changing Stack::s_chunkSize to equal sizeof(uintptr_t) on ASan builds.
+              This ensures that Page::flushWrites() only writes stack memory that was
+              modified by a probe.  The probes should only modify stack memory that
+              belongs to JSC stack data structures.  We don't want to inadvertently
+              modify adjacent words that may belong to ASan (which may happen if
+              s_chunkSize is larger than sizeof(uintptr_t)).
+
+           c. fixing a bug in Page dirtyBits management for when the size of the value to
+              write is greater than s_chunkSize.  The fix in generic, but in practice,
+              this currently only manifests on 32-bit ASan builds because
+              sizeof(uintptr_t) and s_chunkSize are 32-bit, and we may write 64-bit
+              values.
+
+           d. making Page::m_dirtyBits 64 bits always.  This maximizes the number of
+              s_chunksPerPage we can have even on ASan builds.
+
+        2. Fixed the bottom most Probe::Context and Probe::Stack get/set methods to use
+           std::memcpy to avoid strict aliasing issues.
+
+        3. Optimized the implementation of Page::physicalAddressFor().
+
+        4. Optimized the implementation of Stack::set() in the recording of the low
+           watermark.  We just record the lowest raw pointer now, and only compute the
+           alignment to its chuck boundary later when the low watermark is requested.
+
+        5. Changed a value in testmasm to make the test less vulnerable to rounding issues.
+
+        No new test needed because this is already covered by testmasm with ASan enabled.
+
+        * assembler/ProbeContext.h:
+        (JSC::Probe::CPUState::gpr const):
+        (JSC::Probe::CPUState::spr const):
+        (JSC::Probe::Context::gpr):
+        (JSC::Probe::Context::spr):
+        (JSC::Probe::Context::fpr):
+        (JSC::Probe::Context::gprName):
+        (JSC::Probe::Context::sprName):
+        (JSC::Probe::Context::fprName):
+        (JSC::Probe::Context::gpr const):
+        (JSC::Probe::Context::spr const):
+        (JSC::Probe::Context::fpr const):
+        (JSC::Probe::Context::pc):
+        (JSC::Probe::Context::fp):
+        (JSC::Probe::Context::sp):
+        (JSC::Probe:: const): Deleted.
+        * assembler/ProbeStack.cpp:
+        (JSC::Probe::copyStackPage):
+        (JSC::Probe::Page::Page):
+        (JSC::Probe::Page::flushWrites):
+        * assembler/ProbeStack.h:
+        (JSC::Probe::Page::get):
+        (JSC::Probe::Page::set):
+        (JSC::Probe::Page::dirtyBitFor):
+        (JSC::Probe::Page::physicalAddressFor):
+        (JSC::Probe::Stack::lowWatermark):
+        (JSC::Probe::Stack::get):
+        (JSC::Probe::Stack::set):
+        * assembler/testmasm.cpp:
+        (JSC::testProbeModifiesStackValues):
+
 2017-09-14  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [JSC] Disable Arity Fixup Inlining until crash in facebook.com is fixed
index caa52ba..e952eec 100644 (file)
@@ -45,14 +45,8 @@ struct CPUState {
     inline uintptr_t& spr(SPRegisterID);
     inline double& fpr(FPRegisterID);
 
-    template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
-    T gpr(RegisterID) const;
-    template<typename T, typename std::enable_if<std::is_pointer<T>::value>::type* = nullptr>
-    T gpr(RegisterID) const;
-    template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
-    T spr(SPRegisterID) const;
-    template<typename T, typename std::enable_if<std::is_pointer<T>::value>::type* = nullptr>
-    T spr(SPRegisterID) const;
+    template<typename T> T gpr(RegisterID) const;
+    template<typename T> T spr(SPRegisterID) const;
     template<typename T> T fpr(FPRegisterID) const;
 
     void*& pc();
@@ -85,32 +79,24 @@ inline double& CPUState::fpr(FPRegisterID id)
     return fprs[id];
 }
 
-template<typename T, typename std::enable_if<std::is_integral<T>::value>::type*>
-T CPUState::gpr(RegisterID id) const
-{
-    CPUState* cpu = const_cast<CPUState*>(this);
-    return static_cast<T>(cpu->gpr(id));
-}
-
-template<typename T, typename std::enable_if<std::is_pointer<T>::value>::type*>
+template<typename T>
 T CPUState::gpr(RegisterID id) const
 {
     CPUState* cpu = const_cast<CPUState*>(this);
-    return reinterpret_cast<T>(cpu->gpr(id));
+    auto& from = cpu->gpr(id);
+    typename std::remove_const<T>::type to { };
+    std::memcpy(&to, &from, sizeof(to)); // Use std::memcpy to avoid strict aliasing issues.
+    return to;
 }
 
-template<typename T, typename std::enable_if<std::is_integral<T>::value>::type*>
-T CPUState::spr(SPRegisterID id) const
-{
-    CPUState* cpu = const_cast<CPUState*>(this);
-    return static_cast<T>(cpu->spr(id));
-}
-
-template<typename T, typename std::enable_if<std::is_pointer<T>::value>::type*>
+template<typename T>
 T CPUState::spr(SPRegisterID id) const
 {
     CPUState* cpu = const_cast<CPUState*>(this);
-    return reinterpret_cast<T>(cpu->spr(id));
+    auto& from = cpu->spr(id);
+    typename std::remove_const<T>::type to { };
+    std::memcpy(&to, &from, sizeof(to)); // Use std::memcpy to avoid strict aliasing issues.
+    return to;
 }
 
 template<typename T>
@@ -210,20 +196,24 @@ public:
         , cpu(state->cpu)
     { }
 
-    uintptr_t& gpr(RegisterID id) { return m_state->cpu.gpr(id); }
-    uintptr_t& spr(SPRegisterID id) { return m_state->cpu.spr(id); }
-    double& fpr(FPRegisterID id) { return m_state->cpu.fpr(id); }
-    const char* gprName(RegisterID id) { return m_state->cpu.gprName(id); }
-    const char* sprName(SPRegisterID id) { return m_state->cpu.sprName(id); }
-    const char* fprName(FPRegisterID id) { return m_state->cpu.fprName(id); }
+    uintptr_t& gpr(RegisterID id) { return cpu.gpr(id); }
+    uintptr_t& spr(SPRegisterID id) { return cpu.spr(id); }
+    double& fpr(FPRegisterID id) { return cpu.fpr(id); }
+    const char* gprName(RegisterID id) { return cpu.gprName(id); }
+    const char* sprName(SPRegisterID id) { return cpu.sprName(id); }
+    const char* fprName(FPRegisterID id) { return cpu.fprName(id); }
+
+    template<typename T> T gpr(RegisterID id) const { return cpu.gpr<T>(id); }
+    template<typename T> T spr(SPRegisterID id) const { return cpu.spr<T>(id); }
+    template<typename T> T fpr(FPRegisterID id) const { return cpu.fpr<T>(id); }
 
-    void*& pc() { return m_state->cpu.pc(); }
-    void*& fp() { return m_state->cpu.fp(); }
-    void*& sp() { return m_state->cpu.sp(); }
+    void*& pc() { return cpu.pc(); }
+    void*& fp() { return cpu.fp(); }
+    void*& sp() { return cpu.sp(); }
 
-    template<typename T> T pc() { return m_state->cpu.pc<T>(); }
-    template<typename T> T fp() { return m_state->cpu.fp<T>(); }
-    template<typename T> T sp() { return m_state->cpu.sp<T>(); }
+    template<typename T> T pc() { return cpu.pc<T>(); }
+    template<typename T> T fp() { return cpu.fp<T>(); }
+    template<typename T> T sp() { return cpu.sp<T>(); }
 
     Stack& stack()
     {
index 37484b3..96eadac 100644 (file)
 #include "ProbeStack.h"
 
 #include <memory>
+#include <wtf/StdLibExtras.h>
 
 #if ENABLE(MASM_PROBE)
 
 namespace JSC {
 namespace Probe {
 
+#if ASAN_ENABLED
+// FIXME: we should consider using the copy function for both ASan and non-ASan builds.
+// https://bugs.webkit.org/show_bug.cgi?id=176961
+SUPPRESS_ASAN
+static void copyStackPage(void* dst, void* src, size_t size)
+{
+    ASSERT(roundUpToMultipleOf<sizeof(uintptr_t)>(dst) == dst);
+    ASSERT(roundUpToMultipleOf<sizeof(uintptr_t)>(src) == src);
+    
+    uintptr_t* dstPointer = reinterpret_cast<uintptr_t*>(dst);
+    uintptr_t* srcPointer = reinterpret_cast<uintptr_t*>(src);
+    for (; size; size -= sizeof(uintptr_t))
+        *dstPointer++ = *srcPointer++;
+}
+#else
+#define copyStackPage(dst, src, size) std::memcpy(dst, src, size);
+#endif
+
 Page::Page(void* baseAddress)
     : m_baseLogicalAddress(baseAddress)
+    , m_physicalAddressOffset(reinterpret_cast<uint8_t*>(&m_buffer) - reinterpret_cast<uint8_t*>(baseAddress))
 {
-    memcpy(&m_buffer, baseAddress, s_pageSize);
+    copyStackPage(&m_buffer, baseAddress, s_pageSize);
 }
 
 void Page::flushWrites()
 {
-    uintptr_t dirtyBits = m_dirtyBits;
+    uint64_t dirtyBits = m_dirtyBits;
     size_t offset = 0;
     while (dirtyBits) {
         // Find start.
@@ -56,7 +76,7 @@ void Page::flushWrites()
             size_t size = offset - startOffset;
             uint8_t* src = reinterpret_cast<uint8_t*>(&m_buffer) + startOffset;
             uint8_t* dst = reinterpret_cast<uint8_t*>(m_baseLogicalAddress) + startOffset;
-            memcpy(dst, src, size);
+            copyStackPage(dst, src, size);
         }
         dirtyBits = dirtyBits >> 1;
         offset += s_chunkSize;
index 593da33..ec7ae94 100644 (file)
@@ -25,6 +25,7 @@
 
 #pragma once
 
+#include "CPU.h"
 #include <wtf/HashMap.h>
 #include <wtf/StdLibExtras.h>
 #include <wtf/Threading.h>
@@ -56,14 +57,35 @@ public:
     template<typename T>
     T get(void* logicalAddress)
     {
-        return *physicalAddressFor<T*>(logicalAddress);
+        void* from = physicalAddressFor(logicalAddress);
+        typename std::remove_const<T>::type to { };
+        std::memcpy(&to, from, sizeof(to)); // Use std::memcpy to avoid strict aliasing issues.
+        return to;
+    }
+    template<typename T>
+    T get(void* logicalBaseAddress, ptrdiff_t offset)
+    {
+        return get<T>(reinterpret_cast<uint8_t*>(logicalBaseAddress) + offset);
     }
 
     template<typename T>
     void set(void* logicalAddress, T value)
     {
-        m_dirtyBits |= dirtyBitFor(logicalAddress);
-        *physicalAddressFor<T*>(logicalAddress) = value;
+        if (sizeof(T) <= s_chunkSize)
+            m_dirtyBits |= dirtyBitFor(logicalAddress);
+        else {
+            size_t numberOfChunks = roundUpToMultipleOf<sizeof(T)>(s_chunkSize) / s_chunkSize;
+            uint8_t* dirtyAddress = reinterpret_cast<uint8_t*>(logicalAddress);
+            for (size_t i = 0; i < numberOfChunks; ++i, dirtyAddress += s_chunkSize)
+                m_dirtyBits |= dirtyBitFor(dirtyAddress);
+        }
+        void* to = physicalAddressFor(logicalAddress);
+        std::memcpy(to, &value, sizeof(T)); // Use std::memcpy to avoid strict aliasing issues.
+    }
+    template<typename T>
+    void set(void* logicalBaseAddress, ptrdiff_t offset, T value)
+    {
+        set<T>(reinterpret_cast<uint8_t*>(logicalBaseAddress) + offset, value);
     }
 
     bool hasWritesToFlush() const { return !!m_dirtyBits; }
@@ -74,39 +96,48 @@ public:
     }
 
 private:
-    uintptr_t dirtyBitFor(void* logicalAddress)
+    uint64_t dirtyBitFor(void* logicalAddress)
     {
         uintptr_t offset = reinterpret_cast<uintptr_t>(logicalAddress) & s_pageMask;
-        return static_cast<uintptr_t>(1) << (offset >> s_chunkSizeShift);
+        return static_cast<uint64_t>(1) << (offset >> s_chunkSizeShift);
     }
 
-    template<typename T, typename = typename std::enable_if<std::is_pointer<T>::value>::type>
-    T physicalAddressFor(void* logicalAddress)
+    void* physicalAddressFor(void* logicalAddress)
     {
-        uintptr_t offset = reinterpret_cast<uintptr_t>(logicalAddress) & s_pageMask;
-        void* physicalAddress = reinterpret_cast<uint8_t*>(&m_buffer) + offset;
-        return reinterpret_cast<T>(physicalAddress);
+        return reinterpret_cast<uint8_t*>(logicalAddress) + m_physicalAddressOffset;
     }
 
     void flushWrites();
 
     void* m_baseLogicalAddress { nullptr };
-    uintptr_t m_dirtyBits { 0 };
-
+    ptrdiff_t m_physicalAddressOffset;
+    uint64_t m_dirtyBits { 0 };
+
+#if ASAN_ENABLED
+    // The ASan stack may contain poisoned words that may be manipulated at ASan's discretion.
+    // We would never touch those words anyway, but let's ensure that the page size is set
+    // such that the chunk size is guaranteed to be exactly sizeof(uintptr_t) so that we won't
+    // inadvertently overwrite one of ASan's words on the stack when we copy back the dirty
+    // chunks.
+    // FIXME: we should consider using the same page size for both ASan and non-ASan builds.
+    // https://bugs.webkit.org/show_bug.cgi?id=176961
+    static constexpr size_t s_pageSize = 64 * sizeof(uintptr_t); // because there are 64 bits in m_dirtyBits.
+#else // not ASAN_ENABLED
     static constexpr size_t s_pageSize = 1024;
+#endif // ASAN_ENABLED
     static constexpr uintptr_t s_pageMask = s_pageSize - 1;
-    static constexpr size_t s_chunksPerPage = sizeof(uintptr_t) * 8; // sizeof(m_dirtyBits) in bits.
+    static constexpr size_t s_chunksPerPage = sizeof(uint64_t) * 8; // number of bits in m_dirtyBits.
     static constexpr size_t s_chunkSize = s_pageSize / s_chunksPerPage;
     static constexpr uintptr_t s_chunkMask = s_chunkSize - 1;
-#if USE(JSVALUE64)
+#if ASAN_ENABLED
+    static_assert(s_chunkSize == sizeof(uintptr_t), "bad chunkSizeShift");
+    static constexpr size_t s_chunkSizeShift = is64Bit() ? 3 : 2;
+#else // no ASAN_ENABLED
     static constexpr size_t s_chunkSizeShift = 4;
-#else
-    static constexpr size_t s_chunkSizeShift = 5;
-#endif
+#endif // ASAN_ENABLED
     static_assert(s_pageSize > s_chunkSize, "bad pageSize or chunkSize");
     static_assert(s_chunkSize == (1 << s_chunkSizeShift), "bad chunkSizeShift");
 
-
     typedef typename std::aligned_storage<s_pageSize, std::alignment_of<uintptr_t>::value>::type Buffer;
     Buffer m_buffer;
 };
@@ -120,40 +151,40 @@ public:
     { }
     Stack(Stack&& other);
 
-    void* lowWatermark() { return m_lowWatermark; }
+    void* lowWatermark()
+    {
+        // We use the chunkAddress for the low watermark because we'll be doing write backs
+        // to the stack in increments of chunks. Hence, we'll treat the lowest address of
+        // the chunk as the low watermark of any given set address.
+        return Page::chunkAddressFor(m_lowWatermark);
+    }
 
     template<typename T>
-    typename std::enable_if<!std::is_same<double, typename std::remove_cv<T>::type>::value, T>::type get(void* address)
+    T get(void* address)
     {
         Page* page = pageFor(address);
         return page->get<T>(address);
     }
+    template<typename T>
+    T get(void* logicalBaseAddress, ptrdiff_t offset)
+    {
+        return get<T>(reinterpret_cast<uint8_t*>(logicalBaseAddress) + offset);
+    }
 
-    template<typename T, typename = typename std::enable_if<!std::is_same<double, typename std::remove_cv<T>::type>::value>::type>
+    template<typename T>
     void set(void* address, T value)
     {
         Page* page = pageFor(address);
         page->set<T>(address, value);
 
-        // We use the chunkAddress for the low watermark because we'll be doing write backs
-        // to the stack in increments of chunks. Hence, we'll treat the lowest address of
-        // the chunk as the low watermark of any given set address.
-        void* chunkAddress = Page::chunkAddressFor(address);
-        if (chunkAddress < m_lowWatermark)
-            m_lowWatermark = chunkAddress;
+        if (address < m_lowWatermark)
+            m_lowWatermark = address;
     }
 
     template<typename T>
-    typename std::enable_if<std::is_same<double, typename std::remove_cv<T>::type>::value, T>::type get(void* address)
-    {
-        Page* page = pageFor(address);
-        return bitwise_cast<double>(page->get<uint64_t>(address));
-    }
-
-    template<typename T, typename = typename std::enable_if<std::is_same<double, typename std::remove_cv<T>::type>::value>::type>
-    void set(void* address, double value)
+    void set(void* logicalBaseAddress, ptrdiff_t offset, T value)
     {
-        set<uint64_t>(address, bitwise_cast<uint64_t>(value));
+        set<T>(reinterpret_cast<uint8_t*>(logicalBaseAddress) + offset, value);
     }
 
     JS_EXPORT_PRIVATE Page* ensurePageFor(void* address);
index 2b238b7..f11610b 100644 (file)
@@ -600,7 +600,7 @@ void testProbeModifiesStackValues()
             // Fill the stack with values.
             uintptr_t* p = reinterpret_cast<uintptr_t*>(newSP);
             int count = 0;
-            stack.set<double>(p++, 1.23456789);
+            stack.set<double>(p++, 1.234567);
             if (is32Bit())
                 p++; // On 32-bit targets, a double takes up 2 uintptr_t.
             while (p < reinterpret_cast<uintptr_t*>(originalSP))
@@ -631,7 +631,7 @@ void testProbeModifiesStackValues()
             // Validate the stack values.
             uintptr_t* p = reinterpret_cast<uintptr_t*>(newSP);
             int count = 0;
-            CHECK_EQ(stack.get<double>(p++), 1.23456789);
+            CHECK_EQ(stack.get<double>(p++), 1.234567);
             if (is32Bit())
                 p++; // On 32-bit targets, a double takes up 2 uintptr_t.
             while (p < reinterpret_cast<uintptr_t*>(originalSP))
index 20b3dd4..fc5e124 100644 (file)
@@ -1,3 +1,17 @@
+2017-09-14  Mark Lam  <mark.lam@apple.com>
+
+        AddressSanitizer: stack-buffer-underflow in JSC::Probe::Page::Page
+        https://bugs.webkit.org/show_bug.cgi?id=176874
+        <rdar://problem/34436415>
+
+        Reviewed by Saam Barati.
+
+        Added a convenience version of roundUpToMultipleOf() so that it can be applied to
+        pointers without the client having to cast explicitly.
+
+        * wtf/StdLibExtras.h:
+        (WTF::roundUpToMultipleOf):
+
 2017-09-14  Youenn Fablet  <youenn@apple.com>
 
         Allow WTF::map to take function as parameter
index 1cb03e5..12e393a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, 2016 Apple Inc. All Rights Reserved.
+ * Copyright (C) 2008-2017 Apple Inc. All Rights Reserved.
  * Copyright (C) 2013 Patrick Gansterer <paroga@paroga.com>
  *
  * Redistribution and use in source and binary forms, with or without
@@ -204,6 +204,12 @@ template<size_t divisor> inline constexpr size_t roundUpToMultipleOf(size_t x)
     return roundUpToMultipleOfImpl(divisor, x);
 }
 
+template<size_t divisor, typename T> inline T* roundUpToMultipleOf(T* x)
+{
+    static_assert(sizeof(T*) == sizeof(size_t), "");
+    return reinterpret_cast<T*>(roundUpToMultipleOf<divisor>(reinterpret_cast<size_t>(x)));
+}
+
 enum BinarySearchMode {
     KeyMustBePresentInArray,
     KeyMightNotBePresentInArray,