Skip to content
This repository was archived by the owner on Nov 1, 2023. It is now read-only.

Commit 8058497

Browse files
authored
Truncate large webhook events (#2742)
* Add ITrunctable * Tests * Pass some more tests * Cleanup
1 parent cd659d2 commit 8058497

File tree

11 files changed

+197
-32
lines changed

11 files changed

+197
-32
lines changed

src/ApiService/ApiService/Functions/ValidateScriban.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymou
103103
null,
104104
null,
105105
null,
106+
null,
106107
null
107108
);
108109

src/ApiService/ApiService/OneFuzzTypes/Events.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -302,17 +302,27 @@ public record EventCrashReported(
302302
Container Container,
303303
[property: JsonPropertyName("filename")] String FileName,
304304
TaskConfig? TaskConfig
305-
) : BaseEvent();
306-
305+
) : BaseEvent(), ITruncatable<BaseEvent> {
306+
public BaseEvent Truncate(int maxLength) {
307+
return this with {
308+
Report = Report.Truncate(maxLength)
309+
};
310+
}
311+
}
307312

308313
[EventType(EventType.RegressionReported)]
309314
public record EventRegressionReported(
310315
RegressionReport RegressionReport,
311316
Container Container,
312317
[property: JsonPropertyName("filename")] String FileName,
313318
TaskConfig? TaskConfig
314-
) : BaseEvent();
315-
319+
) : BaseEvent(), ITruncatable<BaseEvent> {
320+
public BaseEvent Truncate(int maxLength) {
321+
return this with {
322+
RegressionReport = RegressionReport.Truncate(maxLength)
323+
};
324+
}
325+
}
316326

317327
[EventType(EventType.FileAdded)]
318328
public record EventFileAdded(

src/ApiService/ApiService/OneFuzzTypes/Model.cs

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,31 @@ public record Report(
457457
string? MinimizedStackFunctionLinesSha256,
458458
string? ToolName,
459459
string? ToolVersion,
460-
string? OnefuzzVersion
461-
) : IReport;
460+
string? OnefuzzVersion,
461+
Uri? ReportUrl
462+
) : IReport, ITruncatable<Report> {
463+
public Report Truncate(int maxLength) {
464+
return this with {
465+
Executable = Executable[..maxLength],
466+
CrashType = CrashType[..Math.Min(maxLength, CrashType.Length)],
467+
CrashSite = CrashSite[..Math.Min(maxLength, CrashSite.Length)],
468+
CallStack = TruncateUtils.TruncateList(CallStack, maxLength),
469+
CallStackSha256 = CallStackSha256[..Math.Min(maxLength, CallStackSha256.Length)],
470+
InputSha256 = InputSha256[..Math.Min(maxLength, InputSha256.Length)],
471+
AsanLog = AsanLog?[..Math.Min(maxLength, AsanLog.Length)],
472+
ScarinessDescription = ScarinessDescription?[..Math.Min(maxLength, ScarinessDescription.Length)],
473+
MinimizedStack = MinimizedStack != null ? TruncateUtils.TruncateList(MinimizedStack, maxLength) : MinimizedStack,
474+
MinimizedStackSha256 = MinimizedStackSha256?[..Math.Min(maxLength, MinimizedStackSha256.Length)],
475+
MinimizedStackFunctionNames = MinimizedStackFunctionNames != null ? TruncateUtils.TruncateList(MinimizedStackFunctionNames, maxLength) : MinimizedStackFunctionNames,
476+
MinimizedStackFunctionNamesSha256 = MinimizedStackFunctionNamesSha256?[..Math.Min(maxLength, MinimizedStackFunctionNamesSha256.Length)],
477+
MinimizedStackFunctionLines = MinimizedStackFunctionLines != null ? TruncateUtils.TruncateList(MinimizedStackFunctionLines, maxLength) : MinimizedStackFunctionLines,
478+
MinimizedStackFunctionLinesSha256 = MinimizedStackFunctionLinesSha256?[..Math.Min(maxLength, MinimizedStackFunctionLinesSha256.Length)],
479+
ToolName = ToolName?[..Math.Min(maxLength, ToolName.Length)],
480+
ToolVersion = ToolVersion?[..Math.Min(maxLength, ToolVersion.Length)],
481+
OnefuzzVersion = OnefuzzVersion?[..Math.Min(maxLength, OnefuzzVersion.Length)],
482+
};
483+
}
484+
}
462485

463486
public record NoReproReport(
464487
string InputSha,
@@ -468,18 +491,40 @@ public record NoReproReport(
468491
Guid JobId,
469492
long Tries,
470493
string? Error
471-
);
494+
) : ITruncatable<NoReproReport> {
495+
public NoReproReport Truncate(int maxLength) {
496+
return this with {
497+
Executable = Executable?[..maxLength],
498+
Error = Error?[..maxLength]
499+
};
500+
}
501+
}
472502

473503
public record CrashTestResult(
474504
Report? CrashReport,
475505
NoReproReport? NoReproReport
476-
);
506+
) : ITruncatable<CrashTestResult> {
507+
public CrashTestResult Truncate(int maxLength) {
508+
return new CrashTestResult(
509+
CrashReport?.Truncate(maxLength),
510+
NoReproReport?.Truncate(maxLength)
511+
);
512+
}
513+
}
477514

478515
public record RegressionReport(
479516
CrashTestResult CrashTestResult,
480-
CrashTestResult? OriginalCrashTestResult
481-
) : IReport;
482-
517+
CrashTestResult? OriginalCrashTestResult,
518+
Uri? ReportUrl
519+
) : IReport, ITruncatable<RegressionReport> {
520+
public RegressionReport Truncate(int maxLength) {
521+
return new RegressionReport(
522+
CrashTestResult.Truncate(maxLength),
523+
OriginalCrashTestResult?.Truncate(maxLength),
524+
ReportUrl
525+
);
526+
}
527+
}
483528

484529
[JsonConverter(typeof(NotificationTemplateConverter))]
485530
#pragma warning disable CA1715
@@ -968,3 +1013,7 @@ public record TemplateRenderContext(
9681013
string ReportFilename,
9691014
string ReproCmd
9701015
);
1016+
1017+
public interface ITruncatable<T> {
1018+
public T Truncate(int maxLength);
1019+
}

src/ApiService/ApiService/onefuzzlib/Reports.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,27 @@ public Reports(ILogTracer log, IContainers containers) {
4444
return null;
4545
}
4646

47-
return ParseReportOrRegression(blob.ToString(), filePath, expectReports);
47+
var reportUrl = await _containers.GetFileUrl(container, fileName, StorageType.Corpus);
48+
49+
return ParseReportOrRegression(blob.ToString(), filePath, reportUrl, expectReports);
4850
}
4951

50-
private IReport? ParseReportOrRegression(string content, string? filePath, bool expectReports = false) {
52+
private IReport? ParseReportOrRegression(string content, string? filePath, Uri? reportUrl, bool expectReports = false) {
5153
var regressionReport = JsonSerializer.Deserialize<RegressionReport>(content, EntityConverter.GetJsonSerializerOptions());
5254
if (regressionReport == null || regressionReport.CrashTestResult == null) {
5355
var report = JsonSerializer.Deserialize<Report>(content, EntityConverter.GetJsonSerializerOptions());
5456
if (expectReports && report == null) {
5557
_log.Error($"unable to parse report ({filePath:Tag:FilePath}) as a report or regression");
5658
return null;
5759
}
58-
return report;
60+
return report != null ? report with { ReportUrl = reportUrl } : report;
5961
}
60-
return regressionReport;
62+
return regressionReport != null ? regressionReport with { ReportUrl = reportUrl } : regressionReport;
6163
}
6264
}
6365

64-
public interface IReport { };
66+
public interface IReport {
67+
Uri? ReportUrl {
68+
init;
69+
}
70+
};

src/ApiService/ApiService/onefuzzlib/Utils.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,10 @@ public static async IAsyncEnumerable<List<TSource>> Chunk<TSource>(this IAsyncEn
3939
}
4040
}
4141
}
42+
43+
public static class TruncateUtils {
44+
public static List<string> TruncateList(List<string> data, int maxLength) {
45+
int currentLength = 0;
46+
return data.TakeWhile(curr => (currentLength += curr.Length) <= maxLength).ToList();
47+
}
48+
}

src/ApiService/ApiService/onefuzzlib/WebhookOperations.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ async public Async.Task SendEvent(EventMessage eventMessage) {
3434
}
3535

3636
async private Async.Task AddEvent(Webhook webhook, EventMessage eventMessage) {
37+
(string, string)[] tags = { ("WebhookId", webhook.WebhookId.ToString()), ("EventId", eventMessage.EventId.ToString()) };
38+
3739
var message = new WebhookMessageLog(
3840
EventId: eventMessage.EventId,
3941
EventType: eventMessage.EventType,
@@ -46,8 +48,18 @@ async private Async.Task AddEvent(Webhook webhook, EventMessage eventMessage) {
4648

4749
var r = await _context.WebhookMessageLogOperations.Replace(message);
4850
if (!r.IsOk) {
49-
_logTracer.WithHttpStatus(r.ErrorV).Error($"Failed to replace webhook message log {webhook.WebhookId:Tag:WebhookId} - {eventMessage.EventId:Tag:EventId}");
51+
if (r.ErrorV.Reason.Contains("The entity is larger than the maximum allowed size") && eventMessage.Event is ITruncatable<BaseEvent> truncatableEvent) {
52+
_logTracer.WithTags(tags).Warning($"The WebhookMessageLog was too long. Truncating event data and trying again.");
53+
var truncatedEventMessage = message with {
54+
Event = truncatableEvent.Truncate(1000)
55+
};
56+
r = await _context.WebhookMessageLogOperations.Replace(truncatedEventMessage);
57+
}
58+
if (!r.IsOk) {
59+
_logTracer.WithHttpStatus(r.ErrorV).WithTags(tags).Error($"Failed to replace webhook message log {webhook.WebhookId:Tag:WebhookId} - {eventMessage.EventId:Tag:EventId}");
60+
}
5061
}
62+
5163
await _context.WebhookMessageLogOperations.QueueWebhook(message);
5264
}
5365

@@ -57,7 +69,7 @@ public async Async.Task<bool> Send(WebhookMessageLog messageLog) {
5769
throw new Exception($"Invalid Webhook. Webhook with WebhookId: {messageLog.WebhookId} Not Found");
5870
}
5971

60-
var (data, digest) = await BuildMessage(webhookId: webhook.WebhookId, eventId: messageLog.EventId, eventType: messageLog.EventType, webhookEvent: messageLog.Event, secretToken: webhook.SecretToken, messageFormat: webhook.MessageFormat);
72+
var (data, digest) = await BuildMessage(webhookId: webhook.WebhookId, eventId: messageLog.EventId, eventType: messageLog.EventType, webhookEvent: messageLog.Event!, secretToken: webhook.SecretToken, messageFormat: webhook.MessageFormat);
6173

6274
var headers = new Dictionary<string, string> { { "User-Agent", $"onefuzz-webhook {_context.ServiceConfiguration.OneFuzzVersion}" } };
6375

src/ApiService/ApiService/onefuzzlib/orm/Orm.cs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,19 @@ public async IAsyncEnumerable<T> QueryAsync(string? filter = null) {
7979
}
8080

8181
public async Task<ResultVoid<(HttpStatusCode Status, string Reason)>> Replace(T entity) {
82-
var tableClient = await GetTableClient(typeof(T).Name);
83-
var tableEntity = _entityConverter.ToTableEntity(entity);
84-
var response = await tableClient.UpsertEntityAsync(tableEntity, TableUpdateMode.Replace);
85-
if (response.IsError) {
86-
return ResultVoid<(HttpStatusCode, string)>.Error(((HttpStatusCode)response.Status, response.ReasonPhrase));
87-
} else {
88-
// update ETag on success
89-
entity.ETag = response.Headers.ETag;
90-
return ResultVoid<(HttpStatusCode, string)>.Ok();
82+
try {
83+
var tableClient = await GetTableClient(typeof(T).Name);
84+
var tableEntity = _entityConverter.ToTableEntity(entity);
85+
var response = await tableClient.UpsertEntityAsync(tableEntity, TableUpdateMode.Replace);
86+
if (response.IsError) {
87+
return ResultVoid<(HttpStatusCode, string)>.Error(((HttpStatusCode)response.Status, response.ReasonPhrase));
88+
} else {
89+
// update ETag on success
90+
entity.ETag = response.Headers.ETag;
91+
return ResultVoid<(HttpStatusCode, string)>.Ok();
92+
}
93+
} catch (RequestFailedException ex) {
94+
return ResultVoid<(HttpStatusCode, string)>.Error(((HttpStatusCode)ex.Status, ex.Message));
9195
}
9296
}
9397

src/ApiService/IntegrationTests/ReproVmssTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ public async Async.Task CannotCreateVMForMissingReport() {
190190
null,
191191
null,
192192
null,
193+
null,
193194
null
194195
);
195196

src/ApiService/Tests/OrmModelsTest.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ public static Gen<WebhookMessageEventGrid> WebhookMessageEventGrid() {
300300
}
301301

302302
public static Gen<Report> Report() {
303-
return Arb.Generate<Tuple<string, BlobRef, List<string>, Guid, int>>().Select(
303+
return Arb.Generate<Tuple<string, BlobRef, List<string>, Guid, int, Uri?>>().Select(
304304
arg =>
305305
new Report(
306306
InputUrl: arg.Item1,
@@ -324,8 +324,8 @@ public static Gen<Report> Report() {
324324
MinimizedStackFunctionLinesSha256: arg.Item1,
325325
ToolName: arg.Item1,
326326
ToolVersion: arg.Item1,
327-
OnefuzzVersion: arg.Item1
328-
327+
OnefuzzVersion: arg.Item1,
328+
ReportUrl: arg.Item6
329329

330330
)
331331
);
@@ -357,11 +357,12 @@ public static Gen<CrashTestResult> CrashTestResult() {
357357
}
358358

359359
public static Gen<RegressionReport> RegressionReport() {
360-
return Arb.Generate<Tuple<CrashTestResult, CrashTestResult?>>().Select(
360+
return Arb.Generate<Tuple<CrashTestResult, CrashTestResult?, Uri?>>().Select(
361361
arg =>
362362
new RegressionReport(
363363
arg.Item1,
364-
arg.Item2
364+
arg.Item2,
365+
arg.Item3
365366
)
366367
);
367368
}

src/ApiService/Tests/TemplateTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ private static Report GetReport() {
190190
null,
191191
null,
192192
null,
193+
null,
193194
null
194195
);
195196
}

0 commit comments

Comments
 (0)