Skip to content

Commit a2c61d4

Browse files
fixed: Thread safety in AssemblyStoreReader when sending events on Android (#4814)
Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io>
1 parent 35aaef9 commit a2c61d4

File tree

5 files changed

+215
-148
lines changed

5 files changed

+215
-148
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
- Extended `SentryThread` by `Main` to allow indication whether the thread is considered the current main thread ([#4807](https://github.com/getsentry/sentry-dotnet/pull/4807))
88

9+
### Fixes
10+
11+
- Fixed thread-safety issue on Android when multiple events are captured concurrently ([#4814](https://github.com/getsentry/sentry-dotnet/pull/4814))
12+
913
### Dependencies
1014

1115
- Bump Native SDK from v0.12.2 to v0.12.3 ([#4832](https://github.com/getsentry/sentry-dotnet/pull/4832))

src/Sentry.Android.AssemblyReader/V2/AssemblyStoreReader.cs

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ internal abstract class AssemblyStoreReader
1111

1212
private static readonly UTF8Encoding ReaderEncoding = new UTF8Encoding(false);
1313

14+
internal Lock StreamLock { get; } = new();
1415
protected Stream StoreStream { get; }
1516

1617
public abstract string Description { get; }
@@ -44,34 +45,44 @@ protected AssemblyStoreReader(Stream store, string path, DebugLogger? logger)
4445

4546
protected BinaryReader CreateReader() => new BinaryReader(StoreStream, ReaderEncoding, leaveOpen: true);
4647

47-
protected abstract bool IsSupported();
48+
protected internal abstract bool IsSupported();
4849
protected abstract void Prepare();
4950
protected abstract ulong GetStoreStartDataOffset();
5051

5152
public MemoryStream ReadEntryImageData(AssemblyStoreItem entry, bool uncompressIfNeeded = false)
5253
{
53-
ulong startOffset = GetStoreStartDataOffset();
54-
StoreStream.Seek((uint)startOffset + entry.DataOffset, SeekOrigin.Begin);
55-
var stream = new MemoryStream();
56-
57-
if (uncompressIfNeeded)
58-
{
59-
throw new NotImplementedException();
60-
}
61-
62-
const long BufferSize = 65535;
63-
byte[] buffer = Utils.BytePool.Rent((int)BufferSize);
64-
long remainingToRead = entry.DataSize;
65-
66-
while (remainingToRead > 0)
54+
lock (StreamLock)
6755
{
68-
int nread = StoreStream.Read(buffer, 0, (int)Math.Min(BufferSize, remainingToRead));
69-
stream.Write(buffer, 0, nread);
70-
remainingToRead -= (long)nread;
56+
var startOffset = GetStoreStartDataOffset();
57+
StoreStream.Seek((uint)startOffset + entry.DataOffset, SeekOrigin.Begin);
58+
var stream = new MemoryStream();
59+
60+
if (uncompressIfNeeded)
61+
{
62+
throw new NotImplementedException();
63+
}
64+
65+
const long bufferSize = 65535;
66+
var buffer = Utils.BytePool.Rent((int)bufferSize);
67+
try
68+
{
69+
long remainingToRead = entry.DataSize;
70+
71+
while (remainingToRead > 0)
72+
{
73+
var nread = StoreStream.Read(buffer, 0, (int)Math.Min(bufferSize, remainingToRead));
74+
stream.Write(buffer, 0, nread);
75+
remainingToRead -= (long)nread;
76+
}
77+
stream.Flush();
78+
stream.Seek(0, SeekOrigin.Begin);
79+
}
80+
finally
81+
{
82+
Utils.BytePool.Return(buffer);
83+
}
84+
85+
return stream;
7186
}
72-
stream.Flush();
73-
stream.Seek(0, SeekOrigin.Begin);
74-
75-
return stream;
7687
}
7788
}

src/Sentry.Android.AssemblyReader/V2/StoreReader.Classes.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public Header(uint magic, uint version, uint entry_count, uint index_entry_count
3131
}
3232
}
3333

34-
private sealed class IndexEntry
34+
internal sealed class IndexEntry
3535
{
3636
public readonly ulong name_hash;
3737
public readonly uint descriptor_index;
@@ -45,7 +45,7 @@ public IndexEntry(ulong name_hash, uint descriptor_index, bool ignore)
4545
}
4646
}
4747

48-
private sealed class EntryDescriptor
48+
internal sealed class EntryDescriptor
4949
{
5050
public uint mapping_index;
5151

@@ -59,7 +59,7 @@ private sealed class EntryDescriptor
5959
public uint config_data_size;
6060
}
6161

62-
private sealed class StoreItemV2 : AssemblyStoreItem
62+
internal sealed class StoreItemV2 : AssemblyStoreItem
6363
{
6464
public StoreItemV2(AndroidTargetArch targetArch, string name, bool is64Bit, List<IndexEntry> indexEntries,
6565
EntryDescriptor descriptor, bool ignore)

src/Sentry.Android.AssemblyReader/V2/StoreReader.cs

Lines changed: 123 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -91,163 +91,163 @@ public StoreReader(Stream store, string path, DebugLogger? logger)
9191

9292
protected override ulong GetStoreStartDataOffset() => elfOffset;
9393

94-
protected override bool IsSupported()
94+
protected internal override bool IsSupported()
9595
{
96-
StoreStream.Seek(0, SeekOrigin.Begin);
97-
using var reader = CreateReader();
98-
99-
uint magic = reader.ReadUInt32();
100-
if (magic == Utils.ELFMagic)
96+
lock (StreamLock)
10197
{
102-
ELFPayloadError error;
103-
(elfOffset, _, error) = Utils.FindELFPayloadSectionOffsetAndSize(StoreStream);
98+
StoreStream.Seek(0, SeekOrigin.Begin);
99+
using var reader = CreateReader();
104100

105-
if (error != ELFPayloadError.None)
101+
var magic = reader.ReadUInt32();
102+
if (magic == Utils.ELFMagic)
106103
{
107-
string message = error switch
104+
(elfOffset, _, var error) = Utils.FindELFPayloadSectionOffsetAndSize(StoreStream);
105+
106+
if (error != ELFPayloadError.None)
108107
{
109-
ELFPayloadError.NotELF => $"Store '{StorePath}' is not a valid ELF binary",
110-
ELFPayloadError.LoadFailed => $"Store '{StorePath}' could not be loaded",
111-
ELFPayloadError.NotSharedLibrary => $"Store '{StorePath}' is not a shared ELF library",
112-
ELFPayloadError.NotLittleEndian => $"Store '{StorePath}' is not a little-endian ELF image",
113-
ELFPayloadError.NoPayloadSection => $"Store '{StorePath}' does not contain the 'payload' section",
114-
_ => $"Unknown ELF payload section error for store '{StorePath}': {error}"
115-
};
116-
Logger?.Invoke(DebugLoggerLevel.Debug, message);
117-
// Was originally:
118-
// ```
119-
// } else if (elfOffset >= 0) {
120-
// ```
121-
// However since elfOffset is an ulong, it will never be less than 0
108+
var message = error switch
109+
{
110+
ELFPayloadError.NotELF => $"Store '{StorePath}' is not a valid ELF binary",
111+
ELFPayloadError.LoadFailed => $"Store '{StorePath}' could not be loaded",
112+
ELFPayloadError.NotSharedLibrary => $"Store '{StorePath}' is not a shared ELF library",
113+
ELFPayloadError.NotLittleEndian => $"Store '{StorePath}' is not a little-endian ELF image",
114+
ELFPayloadError.NoPayloadSection => $"Store '{StorePath}' does not contain the 'payload' section",
115+
_ => $"Unknown ELF payload section error for store '{StorePath}': {error}"
116+
};
117+
Logger?.Invoke(DebugLoggerLevel.Debug, message);
118+
}
119+
else
120+
{
121+
StoreStream.Seek((long)elfOffset, SeekOrigin.Begin);
122+
magic = reader.ReadUInt32();
123+
}
122124
}
123-
else
125+
126+
if (magic != Utils.AssemblyStoreMagic)
124127
{
125-
StoreStream.Seek((long)elfOffset, SeekOrigin.Begin);
126-
magic = reader.ReadUInt32();
128+
Logger?.Invoke(DebugLoggerLevel.Debug, "Store '{0}' has invalid header magic number.", StorePath);
129+
return false;
127130
}
128-
}
129131

130-
if (magic != Utils.AssemblyStoreMagic)
131-
{
132-
Logger?.Invoke(DebugLoggerLevel.Debug, "Store '{0}' has invalid header magic number.", StorePath);
133-
return false;
134-
}
135-
136-
uint version = reader.ReadUInt32();
137-
if (!supportedVersions.Contains(version))
138-
{
139-
Logger?.Invoke(DebugLoggerLevel.Debug, "Store '{0}' has unsupported version 0x{1:x}", StorePath, version);
140-
return false;
141-
}
132+
var version = reader.ReadUInt32();
133+
if (!supportedVersions.Contains(version))
134+
{
135+
Logger?.Invoke(DebugLoggerLevel.Debug, "Store '{0}' has unsupported version 0x{1:x}", StorePath, version);
136+
return false;
137+
}
142138

143-
uint entry_count = reader.ReadUInt32();
144-
uint index_entry_count = reader.ReadUInt32();
145-
uint index_size = reader.ReadUInt32();
139+
var entry_count = reader.ReadUInt32();
140+
var index_entry_count = reader.ReadUInt32();
141+
var index_size = reader.ReadUInt32();
146142

147-
header = new Header(magic, version, entry_count, index_entry_count, index_size);
148-
return true;
143+
header = new Header(magic, version, entry_count, index_entry_count, index_size);
144+
return true;
145+
}
149146
}
150147

151148
protected override void Prepare()
152149
{
153-
if (header == null)
150+
lock (StreamLock)
154151
{
155-
throw new InvalidOperationException("Internal error: header not set, was IsSupported() called?");
156-
}
152+
if (header == null)
153+
{
154+
throw new InvalidOperationException("Internal error: header not set, was IsSupported() called?");
155+
}
157156

158-
TargetArch = (header.version & ASSEMBLY_STORE_ABI_MASK) switch
159-
{
160-
ASSEMBLY_STORE_ABI_AARCH64 => AndroidTargetArch.Arm64,
161-
ASSEMBLY_STORE_ABI_ARM => AndroidTargetArch.Arm,
162-
ASSEMBLY_STORE_ABI_X64 => AndroidTargetArch.X86_64,
163-
ASSEMBLY_STORE_ABI_X86 => AndroidTargetArch.X86,
164-
_ => throw new NotSupportedException($"Unsupported ABI in store version: 0x{header.version:x}")
165-
};
157+
TargetArch = (header.version & ASSEMBLY_STORE_ABI_MASK) switch
158+
{
159+
ASSEMBLY_STORE_ABI_AARCH64 => AndroidTargetArch.Arm64,
160+
ASSEMBLY_STORE_ABI_ARM => AndroidTargetArch.Arm,
161+
ASSEMBLY_STORE_ABI_X64 => AndroidTargetArch.X86_64,
162+
ASSEMBLY_STORE_ABI_X86 => AndroidTargetArch.X86,
163+
_ => throw new NotSupportedException($"Unsupported ABI in store version: 0x{header.version:x}")
164+
};
166165

167-
Is64Bit = (header.version & ASSEMBLY_STORE_FORMAT_VERSION_MASK) != 0;
168-
AssemblyCount = header.entry_count;
169-
IndexEntryCount = header.index_entry_count;
166+
Is64Bit = (header.version & ASSEMBLY_STORE_FORMAT_VERSION_MASK) != 0;
167+
AssemblyCount = header.entry_count;
168+
IndexEntryCount = header.index_entry_count;
170169

171-
StoreStream.Seek((long)elfOffset + Header.NativeSize, SeekOrigin.Begin);
172-
using var reader = CreateReader();
170+
StoreStream.Seek((long)elfOffset + Header.NativeSize, SeekOrigin.Begin);
171+
using var reader = CreateReader();
173172

174-
var index = new List<IndexEntry>();
175-
for (uint i = 0; i < header.index_entry_count; i++)
176-
{
177-
ulong name_hash;
178-
if (Is64Bit)
179-
{
180-
name_hash = reader.ReadUInt64();
181-
}
182-
else
173+
var index = new List<IndexEntry>();
174+
for (uint i = 0; i < header.index_entry_count; i++)
183175
{
184-
name_hash = (ulong)reader.ReadUInt32();
185-
}
176+
ulong name_hash;
177+
if (Is64Bit)
178+
{
179+
name_hash = reader.ReadUInt64();
180+
}
181+
else
182+
{
183+
name_hash = (ulong)reader.ReadUInt32();
184+
}
186185

187-
uint descriptor_index = reader.ReadUInt32();
186+
uint descriptor_index = reader.ReadUInt32();
188187
#if NET10_0_OR_GREATER
189188
bool ignore = reader.ReadByte () != 0;
190189
#else
191-
bool ignore = false;
190+
bool ignore = false;
192191
#endif
193-
index.Add(new IndexEntry(name_hash, descriptor_index, ignore));
194-
}
192+
index.Add(new IndexEntry(name_hash, descriptor_index, ignore));
193+
}
195194

196-
var descriptors = new List<EntryDescriptor>();
197-
for (uint i = 0; i < header.entry_count; i++)
198-
{
199-
uint mapping_index = reader.ReadUInt32();
200-
uint data_offset = reader.ReadUInt32();
201-
uint data_size = reader.ReadUInt32();
202-
uint debug_data_offset = reader.ReadUInt32();
203-
uint debug_data_size = reader.ReadUInt32();
204-
uint config_data_offset = reader.ReadUInt32();
205-
uint config_data_size = reader.ReadUInt32();
206-
207-
var desc = new EntryDescriptor
195+
var descriptors = new List<EntryDescriptor>();
196+
for (uint i = 0; i < header.entry_count; i++)
208197
{
209-
mapping_index = mapping_index,
210-
data_offset = data_offset,
211-
data_size = data_size,
212-
debug_data_offset = debug_data_offset,
213-
debug_data_size = debug_data_size,
214-
config_data_offset = config_data_offset,
215-
config_data_size = config_data_size,
216-
};
217-
descriptors.Add(desc);
218-
}
198+
uint mapping_index = reader.ReadUInt32();
199+
uint data_offset = reader.ReadUInt32();
200+
uint data_size = reader.ReadUInt32();
201+
uint debug_data_offset = reader.ReadUInt32();
202+
uint debug_data_size = reader.ReadUInt32();
203+
uint config_data_offset = reader.ReadUInt32();
204+
uint config_data_size = reader.ReadUInt32();
205+
206+
var desc = new EntryDescriptor
207+
{
208+
mapping_index = mapping_index,
209+
data_offset = data_offset,
210+
data_size = data_size,
211+
debug_data_offset = debug_data_offset,
212+
debug_data_size = debug_data_size,
213+
config_data_offset = config_data_offset,
214+
config_data_size = config_data_size,
215+
};
216+
descriptors.Add(desc);
217+
}
219218

220-
var names = new List<string>();
221-
for (uint i = 0; i < header.entry_count; i++)
222-
{
223-
uint name_length = reader.ReadUInt32();
224-
byte[] name_bytes = reader.ReadBytes((int)name_length);
225-
names.Add(Encoding.UTF8.GetString(name_bytes));
226-
}
219+
var names = new List<string>();
220+
for (uint i = 0; i < header.entry_count; i++)
221+
{
222+
uint name_length = reader.ReadUInt32();
223+
byte[] name_bytes = reader.ReadBytes((int)name_length);
224+
names.Add(Encoding.UTF8.GetString(name_bytes));
225+
}
227226

228-
var tempItems = new Dictionary<uint, TemporaryItem>();
229-
foreach (IndexEntry ie in index)
230-
{
231-
if (!tempItems.TryGetValue(ie.descriptor_index, out TemporaryItem? item))
227+
var tempItems = new Dictionary<uint, TemporaryItem>();
228+
foreach (IndexEntry ie in index)
232229
{
233-
item = new TemporaryItem(names[(int)ie.descriptor_index], descriptors[(int)ie.descriptor_index], ie.ignore);
234-
tempItems.Add(ie.descriptor_index, item);
230+
if (!tempItems.TryGetValue(ie.descriptor_index, out TemporaryItem? item))
231+
{
232+
item = new TemporaryItem(names[(int)ie.descriptor_index], descriptors[(int)ie.descriptor_index], ie.ignore);
233+
tempItems.Add(ie.descriptor_index, item);
234+
}
235+
item.IndexEntries.Add(ie);
235236
}
236-
item.IndexEntries.Add(ie);
237-
}
238237

239-
if (tempItems.Count != descriptors.Count)
240-
{
241-
throw new InvalidOperationException($"Assembly store '{StorePath}' index is corrupted.");
242-
}
238+
if (tempItems.Count != descriptors.Count)
239+
{
240+
throw new InvalidOperationException($"Assembly store '{StorePath}' index is corrupted.");
241+
}
243242

244-
var storeItems = new List<AssemblyStoreItem>();
245-
foreach (var kvp in tempItems)
246-
{
247-
TemporaryItem ti = kvp.Value;
248-
var item = new StoreItemV2(TargetArch, ti.Name, Is64Bit, ti.IndexEntries, ti.Descriptor, ti.Ignored);
249-
storeItems.Add(item);
243+
var storeItems = new List<AssemblyStoreItem>();
244+
foreach (var kvp in tempItems)
245+
{
246+
TemporaryItem ti = kvp.Value;
247+
var item = new StoreItemV2(TargetArch, ti.Name, Is64Bit, ti.IndexEntries, ti.Descriptor, ti.Ignored);
248+
storeItems.Add(item);
249+
}
250+
Assemblies = storeItems.AsReadOnly();
250251
}
251-
Assemblies = storeItems.AsReadOnly();
252252
}
253253
}

0 commit comments

Comments
 (0)