Skip to content

Commit

Permalink
Fix infrequent infinite loop on Mono EventPipe streaming thread.
Browse files Browse the repository at this point in the history
As observed by dotnet#59296, EventPipe
streaming thread could infrequently cause an infinite loop on Mono
when cleaning up stack hash map, ep_rt_stack_hash_remove_all called
from ep_file_write_sequence_point, flushing buffer memory into file stream.

Issue only occurred on Release builds and so far, only observed on OSX,
and reproduced in 1 of around 100 runs of the test suite.

After debugging the assembler when hitting the hang, it turns out that
one item in the hash map has a hash key, that doesn't correspond
to its hash bucket, this scenario should not be possible
since items get placed into buckets based on hash key value that
doesn't change for the lifetime of the item. This indicates that
there is some sort of corruption happening to the key, after it
has been added to the hash map.

After some more instrumentation it turns out that insert into the
hash map infrequently triggers a replace, but Mono hash table used in
EventPipe is setup to insert without replace, meaning it will keep old
key but switch and free old value. Stack has map uses same memory
for its key and value, so freeing the old value will also free the key,
but since old key is kept, it will point into freed memory and future
reuse of that memory region will cause corruption of the hash table key.

This scenario should not be possible since EventPipe code will only add
to the hash map, if the item is not already in the hash map. After some
further investigation it turns out that the call to ep_rt_stack_hash_lookup
reports false, while call to ep_rt_stack_hash_add for the same key
will hit replace scenario in g_hash_table_insert_replace.
g_hash_table_insert_replace finds item in the hash map, using callbacks for
hash and equal of hash keys. It turns out that the equal callback is defined to return
gboolean, while the callback implementation used in EventPipe is defined to return
bool. gboolean is typed as int32_t on Mono and this is the root cause of the complete issue.
On optimized OSX build (potential on other platforms) the callback will do a memcmp
(updating full eax register) and when returning from callback, callback will only update
first byte of eax register to 0/1, keeping upper bits, so if memcmp returns negative value
or a positive value bigger than first byte, eax will contains garbage in byte 2, 3 and 4,
but since Mono's g_hash_table_insert_replace expects gboolean, it will
look at complete eax content meaning if any of the bits in byte 2, 3 or 4 are still set,
condition will still be true, even if byte 1 is 0, representing false, incorrectly trigger the
replace logic, freeing the old value and key opening up for future corruption of the key,
now reference freed memory.

Fix is to make sure the callback signatures used with hash map callbacks,
match expected signatures of underlying container implementation.

Fix also adds a checked build assert into hash map’s add implementation
on Mono validating that the added key is not already contained in the hash map
enforcing callers to check for existence before calling add on hash map.
  • Loading branch information
lateralusX committed Jul 20, 2022
1 parent 199580b commit 14dd73d
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 5 deletions.
1 change: 1 addition & 0 deletions src/mono/mono/eventpipe/ep-rt-config-mono.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
#define __EVENTPIPE_RT_CONFIG_MONO_H__

#define EP_THREAD_INCLUDE_ACTIVITY_ID
#define EP_RT_USE_CUSTOM_HASH_MAP_CALLBACKS

#endif /* __EVENTPIPE_RT_CONFIG_MONO_H__ */
27 changes: 24 additions & 3 deletions src/mono/mono/eventpipe/ep-rt-mono.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ prefix_name ## _rt_ ## type_name ## _ ## func_name
} \
static inline bool EP_RT_BUILD_TYPE_FUNC_NAME(prefix_name, queue_name, is_empty) (const queue_type *queue) { \
EP_ASSERT (queue != NULL); \
return g_queue_is_empty (queue->queue); \
return (g_queue_is_empty (queue->queue) == TRUE) ? true : false; \
} \
static inline bool EP_RT_BUILD_TYPE_FUNC_NAME(prefix_name, queue_name, is_valid) (const queue_type *queue) { return (queue != NULL && queue->queue != NULL); }

Expand Down Expand Up @@ -258,7 +258,7 @@ prefix_name ## _rt_ ## type_name ## _ ## func_name
EP_RT_DEFINE_ARRAY_REVERSE_ITERATOR_PREFIX(ep, array_name, array_type, iterator_type, item_type)

#define EP_RT_DEFINE_HASH_MAP_BASE_PREFIX(prefix_name, hash_map_name, hash_map_type, key_type, value_type) \
static inline void EP_RT_BUILD_TYPE_FUNC_NAME(prefix_name, hash_map_name, alloc) (hash_map_type *hash_map, uint32_t (*hash_callback)(const void *), bool (*eq_callback)(const void *, const void *), void (*key_free_callback)(void *), void (*value_free_callback)(void *)) { \
static inline void EP_RT_BUILD_TYPE_FUNC_NAME(prefix_name, hash_map_name, alloc) (hash_map_type *hash_map, ep_rt_hash_map_hash_callback_t hash_callback, ep_rt_hash_map_equal_callback_t eq_callback, void (*key_free_callback)(void *), void (*value_free_callback)(void *)) { \
EP_ASSERT (hash_map != NULL); \
EP_ASSERT (key_free_callback == NULL); \
hash_map->table = g_hash_table_new_full ((GHashFunc)hash_callback, (GEqualFunc)eq_callback, (GDestroyNotify)key_free_callback, (GDestroyNotify)value_free_callback); \
Expand All @@ -270,6 +270,7 @@ prefix_name ## _rt_ ## type_name ## _ ## func_name
} \
static inline bool EP_RT_BUILD_TYPE_FUNC_NAME(prefix_name, hash_map_name, add) (hash_map_type *hash_map, key_type key, value_type value) { \
EP_ASSERT (hash_map != NULL); \
EP_ASSERT (!g_hash_table_lookup_extended (hash_map->table, (gconstpointer)key, NULL, NULL)); \
g_hash_table_insert (hash_map->table, (gpointer)key, ((gpointer)(gsize)value)); \
return true; \
} \
Expand All @@ -280,7 +281,7 @@ prefix_name ## _rt_ ## type_name ## _ ## func_name
static inline bool EP_RT_BUILD_TYPE_FUNC_NAME(prefix_name, hash_map_name, lookup) (const hash_map_type *hash_map, const key_type key, value_type *value) { \
EP_ASSERT (hash_map != NULL && value != NULL); \
gpointer _value = NULL; \
bool result = g_hash_table_lookup_extended (hash_map->table, (gconstpointer)key, NULL, &_value); \
bool result = (g_hash_table_lookup_extended (hash_map->table, (gconstpointer)key, NULL, &_value) == TRUE) ? true : false; \
*value = ((value_type)(gsize)_value); \
return result; \
} \
Expand Down Expand Up @@ -380,6 +381,8 @@ extern int64_t ep_rt_mono_system_timestamp_get (void);
extern void ep_rt_mono_os_environment_get_utf16 (ep_rt_env_array_utf16_t *env_array);
extern MonoNativeTlsKey _ep_rt_mono_thread_holder_tls_id;
extern EventPipeThread * ep_rt_mono_thread_get_or_create (void);
extern uint32_t ep_stack_hash_key_hash (const void *key);
extern bool ep_stack_hash_key_equal (const void *key1, const void *key2);

static
inline
Expand Down Expand Up @@ -877,6 +880,24 @@ EP_RT_DEFINE_HASH_MAP_REMOVE(metadata_labels_hash, ep_rt_metadata_labels_hash_ma
EP_RT_DEFINE_HASH_MAP(stack_hash, ep_rt_stack_hash_map_t, StackHashKey *, StackHashEntry *)
EP_RT_DEFINE_HASH_MAP_ITERATOR(stack_hash, ep_rt_stack_hash_map_t, ep_rt_stack_hash_map_iterator_t, StackHashKey *, StackHashEntry *)

#ifdef EP_RT_USE_CUSTOM_HASH_MAP_CALLBACKS
static
inline
guint
ep_rt_stack_hash_key_hash (gconstpointer key)
{
return (guint)ep_stack_hash_key_hash (key);
}

static
inline
gboolean
ep_rt_stack_hash_key_equal (gconstpointer key1, gconstpointer key2)
{
return !!ep_stack_hash_key_equal (key1, key2);
}
#endif

/*
* EventPipeProvider.
*/
Expand Down
5 changes: 5 additions & 0 deletions src/mono/mono/eventpipe/ep-rt-types-mono.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ struct _rt_mono_array_iterator_internal_t {
int32_t index;
};

#ifdef EP_RT_USE_CUSTOM_HASH_MAP_CALLBACKS
typedef GHashFunc ep_rt_hash_map_hash_callback_t;
typedef GEqualFunc ep_rt_hash_map_equal_callback_t;
#endif

struct _rt_mono_table_internal_t {
GHashTable *table;
};
Expand Down
2 changes: 1 addition & 1 deletion src/native/eventpipe/ep-file.c
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ ep_file_alloc (
ep_rt_metadata_labels_hash_alloc (&instance->metadata_ids, NULL, NULL, NULL, NULL);
ep_raise_error_if_nok (ep_rt_metadata_labels_hash_is_valid (&instance->metadata_ids));

ep_rt_stack_hash_alloc (&instance->stack_hash, ep_stack_hash_key_hash, ep_stack_hash_key_equal, NULL, stack_hash_value_free_func);
ep_rt_stack_hash_alloc (&instance->stack_hash, ep_rt_stack_hash_key_hash, ep_rt_stack_hash_key_equal, NULL, stack_hash_value_free_func);
ep_raise_error_if_nok (ep_rt_stack_hash_is_valid (&instance->stack_hash));

// Start at 0 - The value is always incremented prior to use, so the first ID will be 1.
Expand Down
12 changes: 11 additions & 1 deletion src/native/eventpipe/ep-rt.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,13 @@ prefix_name ## _rt_ ## type_name ## _ ## func_name

#define EP_RT_DEFINE_ARRAY_REVERSE_ITERATOR ep_rt_redefine

#ifndef EP_RT_USE_CUSTOM_HASH_MAP_CALLBACKS
typedef uint32_t (*ep_rt_hash_map_hash_callback_t)(const void *);
typedef bool (*ep_rt_hash_map_equal_callback_t)(const void *, const void *);
#endif

#define EP_RT_DECLARE_HASH_MAP_BASE_PREFIX(prefix_name, hash_map_name, hash_map_type, key_type, value_type) \
static void EP_RT_BUILD_TYPE_FUNC_NAME(prefix_name, hash_map_name, alloc) (hash_map_type *hash_map, uint32_t (*hash_callback)(const void *), bool (*eq_callback)(const void *, const void *), void (*key_free_callback)(void *), void (*value_free_callback)(void *)); \
static void EP_RT_BUILD_TYPE_FUNC_NAME(prefix_name, hash_map_name, alloc) (hash_map_type *hash_map, ep_rt_hash_map_hash_callback_t hash_callback, ep_rt_hash_map_equal_callback_t eq_callback, void (*key_free_callback)(void *), void (*value_free_callback)(void *)); \
static void EP_RT_BUILD_TYPE_FUNC_NAME(prefix_name, hash_map_name, free) (hash_map_type *hash_map); \
static bool EP_RT_BUILD_TYPE_FUNC_NAME(prefix_name, hash_map_name, add) (hash_map_type *hash_map, key_type key, value_type value); \
static void EP_RT_BUILD_TYPE_FUNC_NAME(prefix_name, hash_map_name, remove_all) (hash_map_type *hash_map); \
Expand Down Expand Up @@ -349,6 +354,11 @@ EP_RT_DECLARE_HASH_MAP_REMOVE(metadata_labels_hash, ep_rt_metadata_labels_hash_m
EP_RT_DECLARE_HASH_MAP(stack_hash, ep_rt_stack_hash_map_t, StackHashKey *, StackHashEntry *)
EP_RT_DECLARE_HASH_MAP_ITERATOR(stack_hash, ep_rt_stack_hash_map_t, ep_rt_stack_hash_map_iterator_t, StackHashKey *, StackHashEntry *)

#ifndef EP_RT_USE_CUSTOM_HASH_MAP_CALLBACKS
#define ep_rt_stack_hash_key_hash ep_stack_hash_key_hash
#define ep_rt_stack_hash_key_equal ep_stack_hash_key_equal
#endif

/*
* EventPipeProvider.
*/
Expand Down

0 comments on commit 14dd73d

Please sign in to comment.