gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libmicrohttpd] 02/02: Added custom memory poisoning for MemoryPool


From: gnunet
Subject: [libmicrohttpd] 02/02: Added custom memory poisoning for MemoryPool
Date: Sun, 10 Oct 2021 20:17:04 +0200

This is an automated email from the git hooks/post-receive script.

karlson2k pushed a commit to branch master
in repository libmicrohttpd.

commit 50c9e5efea6913cf97b4b1afd8c57f886f552d32
Author: Evgeny Grin (Karlson2k) <k2k@narod.ru>
AuthorDate: Sun Oct 10 21:16:25 2021 +0300

    Added custom memory poisoning for MemoryPool
---
 configure.ac                 | 111 ++++++++++++++++++++++++++++++++++++++++++-
 src/include/mhd_options.h    |  27 ++++++++++-
 src/microhttpd/memorypool.c  |  75 +++++++++++++++++++++++------
 src/testcurl/test_toolarge.c |   6 +++
 4 files changed, 203 insertions(+), 16 deletions(-)

diff --git a/configure.ac b/configure.ac
index f013ecb1..67b18c5f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2648,6 +2648,8 @@ AS_VAR_IF([enable_sanitizers], ["yes"],
        )
        AS_VAR_IF([mhd_cv_cc_sanitizer_address],["yes"],
          [
+           AC_DEFINE([MHD_ASAN_ACTIVE], [1], [Define to '1' if you have 
address sanitizer enabled])
+           AC_CHECK_HEADERS([sanitizer/asan_interface.h], [], [], 
[AC_INCLUDES_DEFAULT])
            AX_APPEND_FLAG([-fsanitize=address], [san_FLAGS])
            enabled_sanitizers="${enabled_sanitizers}${enabled_sanitizers:+, 
}address"
            AC_CACHE_CHECK([whether leak detect is supported], 
[mhd_cv_cc_sanitizer_address_leak],
@@ -2691,6 +2693,104 @@ AS_VAR_IF([enable_sanitizers], ["yes"],
                
enabled_sanitizers="${enabled_sanitizers}${enabled_sanitizers:+, }pointer 
subtract"
              ]
            )
+           AS_VAR_IF([ac_cv_header_sanitizer_asan_interface_h],["yes"],
+             [
+               AC_CACHE_CHECK([whether 
'__attribute__((no_sanitize("pointer-compare","pointer-subtract")))' works], 
[mhd_cv_func_attribute_nosanitize_ptr],
+                 [
+                   
ASAN_OPTIONS="exitcode=88:detect_invalid_pointer_pairs=3:halt_on_error=1"
+                   export ASAN_OPTIONS
+                   CFLAGS="${saved_CFLAGS} ${san_CFLAGS} ${san_FLAGS} 
${errattr_CFLAGS}"
+                   AC_RUN_IFELSE(
+                     [
+                       AC_LANG_PROGRAM(
+                         [[
+#include <stdlib.h>
+
+__attribute__((no_sanitize("pointer-compare","pointer-subtract")))
+int ptr_process(void *ptr1, void *ptr2)
+{
+  if ((char*)ptr1 <= (char*)ptr2)
+    return (int) ((char*)ptr2 - (char*)ptr1);
+  return (int) ((char*)ptr1 - (char*)ptr2);
+}
+                         ]],
+                         [[
+  int *a = (int*) malloc (sizeof(int)*4);
+  int *b = (int*) malloc (sizeof(long)*6);
+  int c = ptr_process(a, b);
+  if (c)
+  {
+    free (b);
+    free (a);
+    return 0;
+  }
+  free (a);
+  free (b);
+                         ]]
+                       )
+                     ],
+                     [mhd_cv_func_attribute_nosanitize_ptr=yes], 
[mhd_cv_func_attribute_nosanitize_ptr=no],
+                     [
+                       # Cross-compiling with sanitizers??
+                       mhd_cv_func_attribute_nosanitize_ptr=no
+                     ]
+                   )
+                   AS_UNSET([ASAN_OPTIONS])
+                 ]
+               )
+               AS_VAR_IF([mhd_cv_func_attribute_nosanitize_ptr], ["yes"],
+                 [AC_DEFINE([FUNC_ATTR_PTRCOMPARE_WOKRS],[1],[Define to '1' if 
'__attribute__((no_sanitize("pointer-compare","pointer-subtract")))' works])],
+                 [
+                   AC_CACHE_CHECK([whether 
'__attribute__((no_sanitize("address")))' works for pointers compare], 
[mhd_cv_func_attribute_nosanitize_addr],
+                     [
+                       
ASAN_OPTIONS="exitcode=88:detect_invalid_pointer_pairs=3:halt_on_error=1"
+                       export ASAN_OPTIONS
+                       CFLAGS="${saved_CFLAGS} ${san_CFLAGS} ${san_FLAGS} 
${errattr_CFLAGS}"
+                       AC_RUN_IFELSE(
+                         [
+                           AC_LANG_PROGRAM(
+                             [[
+#include <stdlib.h>
+
+__attribute__((no_sanitize("address")))
+int ptr_process(void *ptr1, void *ptr2)
+{
+  if ((char*)ptr1 <= (char*)ptr2)
+    return (int) ((char*)ptr2 - (char*)ptr1);
+  return (int) ((char*)ptr1 - (char*)ptr2);
+}
+                         ]],
+                         [[
+  int *a = (int*) malloc (sizeof(int)*4);
+  int *b = (int*) malloc (sizeof(long)*6);
+  int c = ptr_process(a, b);
+  if (c)
+  {
+    free (b);
+    free (a);
+    return 0;
+  }
+  free (a);
+  free (b);
+                             ]]
+                           )
+                         ],
+                         [mhd_cv_func_attribute_nosanitize_addr=yes], 
[mhd_cv_func_attribute_nosanitize_addr=no],
+                         [
+                           # Cross-compiling with sanitizers??
+                           mhd_cv_func_attribute_nosanitize_addr=no
+                         ]
+                       )
+                       AS_UNSET([ASAN_OPTIONS])
+                     ]
+                   )
+                   AS_VAR_IF([mhd_cv_func_attribute_nosanitize_addr], ["yes"],
+                     [AC_DEFINE([FUNC_ATTR_NOSANITIZE_WORKS],[1],[Define to 
'1' if '__attribute__((no_sanitize("address")))' works for pointers compare])]
+                   )
+                 ]
+               )
+             ]
+           )
          ]
        )
        dnl Ensure that '#' will be processed correctly
@@ -2812,6 +2912,15 @@ int main(void)
        )
        AS_IF([test -z "${enabled_sanitizers}"],
          [AC_MSG_ERROR([cannot find any sanitizer supported by $CC])])
+       AC_MSG_CHECKING([whether to enable user memory poisoning])
+       AS_IF([test "x${mhd_cv_cc_sanitizer_address}" = "xyes" && test 
"x${ac_cv_header_sanitizer_asan_interface_h}" = "xyes" && \
+         (test "x${mhd_cv_func_attribute_nosanitize_ptr}" = "xyes" || test 
"x${mhd_cv_func_attribute_nosanitize_addr}" = "xyes")],
+         [
+           AC_DEFINE([MHD_ASAN_POISON_ACTIVE], [1], [Define to '1' if user 
memory poison is used])
+           enabled_sanitizers="${enabled_sanitizers}${enabled_sanitizers:+, 
}user-poison"
+           AC_MSG_RESULT([yes])
+         ], [AC_MSG_RESULT([no])]
+       )
        AS_VAR_IF([mhd_cv_cc_sanitizer_address],["yes"],
          [
            AX_APPEND_FLAG([-D_FORTIFY_SOURCE=0], [san_CFLAGS])
@@ -2829,7 +2938,7 @@ int main(void)
        
AM_ASAN_OPTIONS="exitcode=88:strict_string_checks=1:detect_stack_use_after_return=1"
        
AM_ASAN_OPTIONS="${AM_ASAN_OPTIONS}:check_initialization_order=1:strict_init_order=1:redzone=64"
        
AM_ASAN_OPTIONS="${AM_ASAN_OPTIONS}:max_free_fill_size=1024:detect_invalid_pointer_pairs=3"
-       AM_ASAN_OPTIONS="${AM_ASAN_OPTIONS}:handle_ioctl=1:halt_on_error=1"
+       
AM_ASAN_OPTIONS="${AM_ASAN_OPTIONS}:handle_ioctl=1:allow_user_poisoning=1:halt_on_error=1"
        AS_VAR_IF([mhd_cv_cc_sanitizer_address_leak], ["yes"],
          [AM_ASAN_OPTIONS="${AM_ASAN_OPTIONS}:detect_leaks=1"])
        AM_UBSAN_OPTIONS="exitcode=87:print_stacktrace=1:halt_on_error=1"
diff --git a/src/include/mhd_options.h b/src/include/mhd_options.h
index 0e803451..e405fd23 100644
--- a/src/include/mhd_options.h
+++ b/src/include/mhd_options.h
@@ -1,6 +1,6 @@
 /*
   This file is part of libmicrohttpd
-  Copyright (C) 2016 Karlson2k (Evgeny Grin)
+  Copyright (C) 2016-2021 Karlson2k (Evgeny Grin)
 
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
@@ -142,4 +142,29 @@
 #define MHD_FAVOR_FAST_CODE 1
 #endif /* !MHD_FAVOR_FAST_CODE && !MHD_FAVOR_SMALL_CODE */
 
+#ifndef MHD_ASAN_ACTIVE
+#if (defined(__GNUC__) || defined(_MSC_VER)) && defined(__SANITIZE_ADDRESS__)
+#define MHD_ASAN_ACTIVE 1
+#elif defined(__has_feature)
+#if __has_feature (address_sanitizer)
+#define MHD_ASAN_ACTIVE 1
+#endif /* __has_feature(address_sanitizer) */
+#endif /* __has_feature */
+#endif /* MHD_ASAN_ACTIVE */
+
+#if defined(MHD_ASAN_ACTIVE) && defined(HAVE_SANITIZER_ASAN_INTERFACE_H) && \
+  (defined(FUNC_ATTR_PTRCOMPARE_WOKRS) || defined(FUNC_ATTR_NOSANITIZE_WORKS))
+#ifndef MHD_ASAN_POISON_ACTIVE
+/* Manual ASAN poisoning could be used */
+#warning User memory poisoning is not active
+#endif /* ! MHD_ASAN_POISON_ACTIVE */
+#define _MHD_USE_ASAN_POISON 1
+#else  /* ! (MHD_ASAN_ACTIVE && HAVE_SANITIZER_ASAN_INTERFACE_H &&
+           (FUNC_ATTR_PTRCOMPARE_WOKRS || FUNC_ATTR_NOSANITIZE_WORKS))   */
+#ifdef MHD_ASAN_POISON_ACTIVE
+#error User memory poisoning is active, but conditions are not suitable
+#endif /* MHD_ASAN_POISON_ACTIVE */
+#endif /* ! (MHD_ASAN_ACTIVE && HAVE_SANITIZER_ASAN_INTERFACE_H &&
+           (FUNC_ATTR_PTRCOMPARE_WOKRS || FUNC_ATTR_NOSANITIZE_WORKS))   */
+
 #endif /* MHD_OPTIONS_H */
diff --git a/src/microhttpd/memorypool.c b/src/microhttpd/memorypool.c
index fb6c0652..0f71ab1b 100644
--- a/src/microhttpd/memorypool.c
+++ b/src/microhttpd/memorypool.c
@@ -45,6 +45,11 @@
 #define MHD_SC_PAGESIZE _SC_PAGESIZE
 #endif /* _SC_PAGESIZE */
 #endif /* HAVE_SYSCONF */
+#include "mhd_limits.h" /* for SIZE_MAX */
+
+#ifdef MHD_ASAN_POISON_ACTIVE
+#include <sanitizer/asan_interface.h>
+#endif /* _MHD_USE_ASAN_POISON */
 
 /* define MAP_ANONYMOUS for Mac OS X */
 #if defined(MAP_ANON) && ! defined(MAP_ANONYMOUS)
@@ -67,6 +72,28 @@
 #define ROUND_TO_ALIGN(n) (((n) + (ALIGN_SIZE - 1)) \
                            / (ALIGN_SIZE) *(ALIGN_SIZE))
 
+
+#ifndef MHD_ASAN_POISON_ACTIVE
+#define _MHD_NOSANITIZE_PTRS /**/
+#define _MHD_RED_ZONE_SIZE (0)
+#define ROUND_TO_ALIGN_PLUS_RED_ZONE(n) ROUND_TO_ALIGN(n)
+#define _MHD_POISON_MEMORY(pointer, size) /**/
+#define _MHD_UNPOISON_MEMORY(pointer, size) /**/
+#else  /* MHD_ASAN_POISON_ACTIVE */
+#if defined(FUNC_ATTR_PTRCOMPARE_WOKRS)
+#define _MHD_NOSANITIZE_PTRS \
+  __attribute__((no_sanitize("pointer-compare","pointer-subtract")))
+#elif defined(FUNC_ATTR_NOSANITIZE_WORKS)
+#define _MHD_NOSANITIZE_PTRS __attribute__((no_sanitize("address")))
+#endif
+#define _MHD_RED_ZONE_SIZE (ALIGN_SIZE)
+#define ROUND_TO_ALIGN_PLUS_RED_ZONE(n) (ROUND_TO_ALIGN(n) + 
_MHD_RED_ZONE_SIZE)
+#define _MHD_POISON_MEMORY(pointer, size) \
+  ASAN_POISON_MEMORY_REGION ((pointer), (size))
+#define _MHD_UNPOISON_MEMORY(pointer, size) \
+  ASAN_UNPOISON_MEMORY_REGION ((pointer), (size))
+#endif /* MHD_ASAN_POISON_ACTIVE */
+
 #if defined(PAGE_SIZE) && (0 < (PAGE_SIZE + 0))
 #define MHD_DEF_PAGE_SIZE_ PAGE_SIZE
 #elif defined(PAGESIZE) && (0 < (PAGESIZE + 0))
@@ -205,6 +232,7 @@ MHD_pool_create (size_t max)
   pool->end = alloc_size;
   pool->size = alloc_size;
   mhd_assert (0 < alloc_size);
+  _MHD_POISON_MEMORY (pool->memory, pool->size);
   return pool;
 }
 
@@ -222,6 +250,7 @@ MHD_pool_destroy (struct MemoryPool *pool)
 
   mhd_assert (pool->end >= pool->pos);
   mhd_assert (pool->size >= pool->end - pool->pos);
+  _MHD_POISON_MEMORY (pool->memory, pool->size);
   if (! pool->is_mmap)
     free (pool->memory);
   else
@@ -250,7 +279,11 @@ MHD_pool_get_free (struct MemoryPool *pool)
 {
   mhd_assert (pool->end >= pool->pos);
   mhd_assert (pool->size >= pool->end - pool->pos);
-  return (pool->end - pool->pos);
+#ifdef MHD_ASAN_POISON_ACTIVE
+  if ((pool->end - pool->pos) <= _MHD_RED_ZONE_SIZE)
+    return 0;
+#endif /* _MHD_USE_ASAN_POISON */
+  return (pool->end - pool->pos) - _MHD_RED_ZONE_SIZE;
 }
 
 
@@ -275,7 +308,7 @@ MHD_pool_allocate (struct MemoryPool *pool,
 
   mhd_assert (pool->end >= pool->pos);
   mhd_assert (pool->size >= pool->end - pool->pos);
-  asize = ROUND_TO_ALIGN (size);
+  asize = ROUND_TO_ALIGN_PLUS_RED_ZONE (size);
   if ( (0 == asize) && (0 != size) )
     return NULL; /* size too close to SIZE_MAX */
   if ( (pool->pos + asize > pool->end) ||
@@ -291,6 +324,7 @@ MHD_pool_allocate (struct MemoryPool *pool,
     ret = &pool->memory[pool->pos];
     pool->pos += asize;
   }
+  _MHD_UNPOISON_MEMORY (ret, size);
   return ret;
 }
 
@@ -299,7 +333,7 @@ MHD_pool_allocate (struct MemoryPool *pool,
  * Try to allocate @a size bytes memory area from the @a pool.
  *
  * If allocation fails, @a required_bytes is updated with size required to be
- * freed in the @a pool from relocatable area to allocate requested number
+ * freed in the @a pool from rellocatable area to allocate requested number
  * of bytes.
  * Allocated memory area is always not rellocatable ("from end").
  *
@@ -311,7 +345,7 @@ MHD_pool_allocate (struct MemoryPool *pool,
  *                            Cannot be NULL.
  * @return the pointer to allocated memory area if succeed,
  *         NULL if the pool doesn't have enough space, required_bytes is 
updated
- *         with amount of space needed to be freed in relocatable area or
+ *         with amount of space needed to be freed in rellocatable area or
  *         set to SIZE_MAX if requested size is too large for the pool.
  */
 void *
@@ -324,7 +358,7 @@ MHD_pool_try_alloc (struct MemoryPool *pool,
 
   mhd_assert (pool->end >= pool->pos);
   mhd_assert (pool->size >= pool->end - pool->pos);
-  asize = ROUND_TO_ALIGN (size);
+  asize = ROUND_TO_ALIGN_PLUS_RED_ZONE (size);
   if ( (0 == asize) && (0 != size) )
   { /* size is too close to SIZE_MAX, very unlikely */
     *required_bytes = SIZE_MAX;
@@ -333,6 +367,8 @@ MHD_pool_try_alloc (struct MemoryPool *pool,
   if ( (pool->pos + asize > pool->end) ||
        (pool->pos + asize < pool->pos))
   {
+    mhd_assert ((pool->end - pool->pos) == \
+                ROUND_TO_ALIGN (pool->end - pool->pos));
     if (asize <= pool->end)
       *required_bytes = asize - (pool->end - pool->pos);
     else
@@ -341,6 +377,7 @@ MHD_pool_try_alloc (struct MemoryPool *pool,
   }
   ret = &pool->memory[pool->end - asize];
   pool->end -= asize;
+  _MHD_UNPOISON_MEMORY (ret, size);
   return ret;
 }
 
@@ -362,7 +399,7 @@ MHD_pool_try_alloc (struct MemoryPool *pool,
  *         NULL if the pool cannot support @a new_size
  *         bytes (old continues to be valid for @a old_size)
  */
-void *
+_MHD_NOSANITIZE_PTRS void *
 MHD_pool_reallocate (struct MemoryPool *pool,
                      void *old,
                      size_t old_size,
@@ -374,11 +411,11 @@ MHD_pool_reallocate (struct MemoryPool *pool,
   mhd_assert (pool->end >= pool->pos);
   mhd_assert (pool->size >= pool->end - pool->pos);
   mhd_assert (old != NULL || old_size == 0);
-  mhd_assert (old == NULL || pool->memory <= (uint8_t*) old);
   mhd_assert (pool->size >= old_size);
+  mhd_assert (old == NULL || pool->memory <= (uint8_t*) old);
   /* (old == NULL || pool->memory + pool->size >= (uint8_t*) old + old_size) */
   mhd_assert (old == NULL || \
-              (pool->size) >= \
+              (pool->size - _MHD_RED_ZONE_SIZE) >= \
               (((size_t) (((uint8_t*) old) - pool->memory)) + old_size));
   /* Blocks "from the end" must not be reallocated */
   /* (old == NULL || old_size == 0 || pool->memory + pool->pos > (uint8_t*) 
old) */
@@ -386,7 +423,7 @@ MHD_pool_reallocate (struct MemoryPool *pool,
               pool->pos > (size_t) ((uint8_t*) old - pool->memory));
   mhd_assert (old == NULL || old_size == 0 || \
               (size_t) (((uint8_t*) old) - pool->memory) + old_size <= \
-              pool->end);
+              pool->end - _MHD_RED_ZONE_SIZE);
 
   if (0 != old_size)
   {   /* Have previously allocated data */
@@ -396,10 +433,13 @@ MHD_pool_reallocate (struct MemoryPool *pool,
     if (shrinking)
     {     /* Shrinking in-place, zero-out freed part */
       memset ((uint8_t*) old + new_size, 0, old_size - new_size);
+      _MHD_POISON_MEMORY ((uint8_t*) old + new_size, old_size - new_size);
     }
-    if (pool->pos == ROUND_TO_ALIGN (old_offset + old_size))
+    if (pool->pos ==
+        ROUND_TO_ALIGN_PLUS_RED_ZONE (old_offset + old_size))
     {     /* "old" block is the last allocated block */
-      const size_t new_apos = ROUND_TO_ALIGN (old_offset + new_size);
+      const size_t new_apos =
+        ROUND_TO_ALIGN_PLUS_RED_ZONE (old_offset + new_size);
       if (! shrinking)
       {                               /* Grow in-place, check for enough 
space. */
         if ( (new_apos > pool->end) ||
@@ -408,13 +448,14 @@ MHD_pool_reallocate (struct MemoryPool *pool,
       }
       /* Resized in-place */
       pool->pos = new_apos;
+      _MHD_UNPOISON_MEMORY (old, new_size);
       return old;
     }
     if (shrinking)
       return old;   /* Resized in-place, freed part remains allocated */
   }
   /* Need to allocate new block */
-  asize = ROUND_TO_ALIGN (new_size);
+  asize = ROUND_TO_ALIGN_PLUS_RED_ZONE (new_size);
   if ( ( (0 == asize) &&
          (0 != new_size) ) || /* Value wrap, too large new_size. */
        (asize > pool->end - pool->pos) ) /* Not enough space */
@@ -423,12 +464,14 @@ MHD_pool_reallocate (struct MemoryPool *pool,
   new_blc = pool->memory + pool->pos;
   pool->pos += asize;
 
+  _MHD_UNPOISON_MEMORY (new_blc, new_size);
   if (0 != old_size)
   {
     /* Move data to new block, old block remains allocated */
     memcpy (new_blc, old, old_size);
     /* Zero-out old block */
     memset (old, 0, old_size);
+    _MHD_POISON_MEMORY (old, old_size);
   }
   return new_blc;
 }
@@ -447,7 +490,7 @@ MHD_pool_reallocate (struct MemoryPool *pool,
  *                 (should be larger or equal to @a copy_bytes)
  * @return addr new address of @a keep (if it had to change)
  */
-void *
+_MHD_NOSANITIZE_PTRS void *
 MHD_pool_reset (struct MemoryPool *pool,
                 void *keep,
                 size_t copy_bytes,
@@ -463,6 +506,7 @@ MHD_pool_reset (struct MemoryPool *pool,
   mhd_assert (keep == NULL || \
               pool->size >= \
               ((size_t) ((uint8_t*) keep - pool->memory)) + copy_bytes);
+  _MHD_UNPOISON_MEMORY (pool->memory, new_size);
   if ( (NULL != keep) &&
        (keep != pool->memory) )
   {
@@ -477,6 +521,7 @@ MHD_pool_reset (struct MemoryPool *pool,
     size_t to_zero;   /** Size of area to zero-out */
 
     to_zero = pool->size - copy_bytes;
+    _MHD_UNPOISON_MEMORY (pool->memory + copy_bytes, to_zero);
 #ifdef _WIN32
     if (pool->is_mmap)
     {
@@ -506,8 +551,10 @@ MHD_pool_reset (struct MemoryPool *pool,
             0,
             to_zero);
   }
-  pool->pos = ROUND_TO_ALIGN (new_size);
+  pool->pos = ROUND_TO_ALIGN_PLUS_RED_ZONE (new_size);
   pool->end = pool->size;
+  _MHD_POISON_MEMORY (((uint8_t*) pool->memory) + new_size, \
+                      pool->size - new_size);
   return pool->memory;
 }
 
diff --git a/src/testcurl/test_toolarge.c b/src/testcurl/test_toolarge.c
index 70b37ff9..4294010a 100644
--- a/src/testcurl/test_toolarge.c
+++ b/src/testcurl/test_toolarge.c
@@ -193,8 +193,14 @@ _mhdErrorExit_func (const char *errDesc, const char 
*funcName, int lineNum)
 
 #define BUFFER_SIZE 1024
 
+#define MHD_ASAN_ACTIVE 1
+
 /* The size of the test element that must pass the test */
+#ifndef MHD_ASAN_POISON_ACTIVE
 #define TEST_OK_SIZE (BUFFER_SIZE - 384)
+#else  /* MHD_ASAN_POISON_ACTIVE */
+#define TEST_OK_SIZE (BUFFER_SIZE - 384 - 80)
+#endif /* MHD_ASAN_POISON_ACTIVE */
 
 /* The size of the test element where tests are started */
 #define TEST_START_SIZE (TEST_OK_SIZE - 16)

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]