-
Notifications
You must be signed in to change notification settings - Fork 147
/
Copy pathDefaultTaintedMap.cs
266 lines (229 loc) · 7.48 KB
/
DefaultTaintedMap.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
// <copyright file = "DefaultTaintedMap.cs" company = "Datadog" >
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>
#nullable enable
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace Datadog.Trace.Iast;
internal class DefaultTaintedMap : ITaintedMap
{
// Default capacity. It MUST be a power of 2.
public const int DefaultCapacity = 1 << 14;
// Default flat mode threshold.
public const int DefaultFlatModeThresold = 1 << 13;
// Bitmask to convert hashes to positive integers.
public const int PositiveMask = int.MaxValue;
// Periodicity of table purges, as number of put operations. It MUST be a power of two.
private const int PurgeCount = 1 << 6;
// Bitmask for fast modulo with PURGE_COUNT.
private const int PurgeMask = PurgeCount - 1;
// Map containing the tainted objects
private ConcurrentDictionary<int, ITaintedObject> _map;
// Bitmask for fast modulo with table length.
private int _lengthMask = 0;
// Flag to ensure we do not run multiple purges concurrently.
private bool _isPurging = false;
private object _purgingLock = new();
// Number of hash table entries. If the hash table switches to flat mode, it stops counting elements.
private int _entriesCount;
/* Number of elements in the hash table before switching to flat mode. */
private int _flatModeThreshold;
public DefaultTaintedMap()
{
_map = new ConcurrentDictionary<int, ITaintedObject>();
_lengthMask = DefaultCapacity - 1;
_flatModeThreshold = DefaultFlatModeThresold;
}
/// <summary>
/// Gets a value indicating whether flat mode is enabled or not. Once this is set to true, it is not set to false again unless clear() is called.
/// The get accessor is only intended for testing purposes.
/// </summary>
public bool IsFlat { get; private set; } = false;
/// <summary>
/// Returns the ITaintedObject for the given input object.
/// </summary>
/// <param name="objectToFind">The object that should be found in the map</param>
/// <returns>The retrieved tainted object or null</returns>
public ITaintedObject? Get(object objectToFind)
{
if (objectToFind is null)
{
return null;
}
_map.TryGetValue(IndexObject(objectToFind), out var entry);
bool isString = objectToFind is string;
while (entry != null)
{
if (isString)
{
if (objectToFind == entry.Value)
{
return entry;
}
}
else
{
if (objectToFind.Equals(entry.Value))
{
return entry;
}
}
entry = entry.Next;
}
return null;
}
/// <summary>
/// Put a new TaintedObject in the dictionary.
/// </summary>
/// <param name="entry">Tainted object</param>
public void Put(ITaintedObject entry)
{
if (entry is null || (entry.Value is null or ""))
{
return;
}
var index = Index(entry.PositiveHashCode);
if (!IsFlat)
{
// By default, add the new entry to the head of the chain.
// We do not control duplicate entries.
_map.TryGetValue(index, out var existingValue);
entry.Next = existingValue;
// If there are two callers calling Put on the same map and the objects have the same index and we are not flat,
// then one of the ITaintedObjects could potentially be lost because of racing conditions.
// We assume that the corresponding lock mechanism benefits would not compensate the performance loss.
// We only count the entries if we are not in flat mode
Interlocked.Increment(ref _entriesCount);
}
// If we flipped to flat mode:
// - Always override elements ignoring chaining.
// - Stop updating the estimated size.
_map[index] = entry;
if ((entry.PositiveHashCode & PurgeMask) == 0)
{
Purge();
}
}
/// <summary>
/// Purge entries that have been garbage collected. Only one concurrent call to this method is
/// allowed, further concurrent calls will be ignored.
/// </summary>
internal void Purge()
{
// Ensure we enter only once concurrently.
lock (_purgingLock)
{
if (_isPurging)
{
return;
}
_isPurging = true;
}
try
{
// Remove GC'd entries.
var removedCount = RemoveDeadKeys();
if (!IsFlat)
{
// We only count the entries if we are not in flat mode
if (Interlocked.Add(ref _entriesCount, -removedCount) > _flatModeThreshold)
{
IsFlat = true;
}
}
}
finally
{
// Reset purging flag.
lock (_purgingLock)
{
_isPurging = false;
}
}
}
private int RemoveDeadKeys()
{
var removed = 0;
List<int> deadKeys = new();
ITaintedObject? previous;
foreach (var key in _map.Keys.ToArray())
{
var current = _map[key];
previous = null;
while (current is not null)
{
if (!current.IsAlive)
{
if (previous is null)
{
// We can delete the map key
if (current.Next is null)
{
deadKeys.Add(key);
}
else
{
_map[key] = current.Next;
}
}
else
{
previous.Next = current.Next;
}
current = current.Next;
removed++;
}
else
{
previous = current;
current = current.Next;
}
}
}
foreach (var key in deadKeys)
{
_map.TryRemove(key, out _);
}
return removed;
}
public void Clear()
{
IsFlat = false;
Interlocked.Exchange(ref _entriesCount, 0);
_map.Clear();
}
private int IndexObject(object objectStored)
{
return Index(PositiveHashCode(objectStored.GetHashCode()));
}
private int PositiveHashCode(int hash)
{
return hash & PositiveMask;
}
public int Index(int hash)
{
return hash & _lengthMask;
}
public int GetEstimatedSize()
{
return _entriesCount;
}
// For testing only
public List<ITaintedObject> GetListValues()
{
List<ITaintedObject> list = new();
foreach (var value in _map.Values)
{
var copy = value;
while (copy != null)
{
list.Add(copy);
copy = copy.Next;
}
}
return (list);
}
}