Skip to content

Commit fa4dfaf

Browse files
feat(minidump): Shed large attachments (#5970)
Co-authored-by: Joris Bayer <joris.bayer@sentry.io>
1 parent 46a37c4 commit fa4dfaf

5 files changed

Lines changed: 100 additions & 29 deletions

File tree

relay-server/src/endpoints/attachments.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::envelope::{AttachmentType, Envelope, Item};
1414
use crate::extractors::RequestMeta;
1515
use crate::managed::Managed;
1616
use crate::service::ServiceState;
17-
use crate::utils::{self, AttachmentStrategy, read_attachment_bytes_into_item};
17+
use crate::utils::{self, AttachmentStrategy, read_bytes_into_item};
1818

1919
#[derive(Debug, Deserialize)]
2020
pub struct AttachmentPath {
@@ -28,13 +28,13 @@ impl AttachmentStrategy for AttachmentsAttachmentStrategy {
2828
AttachmentType::default()
2929
}
3030

31-
fn add_to_item(
31+
async fn add_to_item(
3232
&self,
3333
field: Field<'static>,
3434
item: Managed<Item>,
3535
config: &Config,
36-
) -> impl Future<Output = Result<Option<Managed<Item>>, multer::Error>> + Send {
37-
read_attachment_bytes_into_item(field, item, config, false)
36+
) -> Result<Option<Managed<Item>>, multer::Error> {
37+
read_bytes_into_item(field, item, config).await.map(Some)
3838
}
3939
}
4040

relay-server/src/endpoints/minidump.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use crate::service::ServiceState;
3030
use crate::services::outcome::{DiscardAttachmentType, DiscardItemType, DiscardReason, Outcome};
3131
use crate::services::upload::Upload;
3232
use crate::statsd::RelayCounters;
33-
use crate::utils::{self, AttachmentStrategy, read_attachment_bytes_into_item};
33+
use crate::utils::{self, AttachmentStrategy, read_bytes_into_item};
3434

3535
/// The field name of a minidump in the multipart form-data upload.
3636
///
@@ -220,9 +220,18 @@ impl<'a> AttachmentStrategy for MinidumpAttachmentStrategy<'a> {
220220
item: Managed<Item>,
221221
config: &Config,
222222
) -> Result<Option<Managed<Item>>, multer::Error> {
223+
let read_inline = async |field: Field<'static>, item: Managed<Item>| {
224+
let is_minidump = matches!(item.attachment_type(), Some(AttachmentType::Minidump));
225+
match read_bytes_into_item(field, item, config).await {
226+
// Don't bubble up errors caused by large items unless it is the minidump itself.
227+
Err(multer::Error::FieldSizeExceeded { .. }) if !is_minidump => Ok(None),
228+
r => r.map(Some),
229+
}
230+
};
231+
223232
// If we have no upload context just fall back to the old behavior.
224233
let Some(ref upload_context) = self.upload_context else {
225-
return read_attachment_bytes_into_item(field, item, config, false).await;
234+
return read_inline(field, item).await;
226235
};
227236

228237
match upload_context.upload_decision(item.attachment_type()) {
@@ -239,9 +248,7 @@ impl<'a> AttachmentStrategy for MinidumpAttachmentStrategy<'a> {
239248
)
240249
.await)
241250
}
242-
UploadDecision::Inline => {
243-
read_attachment_bytes_into_item(field, item, config, false).await
244-
}
251+
UploadDecision::Inline => read_inline(field, item).await,
245252
UploadDecision::Drop(limits) => {
246253
// This is best effort, the item here does not yet have its content set hence size
247254
// is not correct.

relay-server/src/endpoints/playstation.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,12 @@ impl<'a> AttachmentStrategy for PlaystationAttachmentStrategy<'a> {
152152
)
153153
.await)
154154
}
155-
_ => utils::read_attachment_bytes_into_item(field, item, config, true).await,
155+
_ => match utils::read_bytes_into_item(field, item, config).await {
156+
// Don't bubble up errors caused by large attachments, skip over them and continue
157+
// with the next item.
158+
Err(multer::Error::FieldSizeExceeded { .. }) => Ok(None),
159+
r => r.map(Some),
160+
},
156161
}
157162
}
158163
}

relay-server/src/utils/multipart.rs

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,11 @@ pub trait AttachmentStrategy {
186186
) -> impl Future<Output = Result<Option<Managed<Item>>, multer::Error>> + Send;
187187
}
188188

189-
pub async fn read_attachment_bytes_into_item(
189+
pub async fn read_bytes_into_item(
190190
field: Field<'static>,
191191
mut item: Managed<Item>,
192192
config: &Config,
193-
ignore_size_exceeded: bool,
194-
) -> Result<Option<Managed<Item>>, multer::Error> {
193+
) -> Result<Managed<Item>, multer::Error> {
195194
let content_type = field
196195
.content_type()
197196
.map(|ct| ct.as_ref().parse().unwrap_or(ContentType::OctetStream));
@@ -213,19 +212,19 @@ pub async fn read_attachment_bytes_into_item(
213212
};
214213
records.lenient(DataCategory::Attachment);
215214
});
215+
216216
if n_bytes > limit {
217217
let attachment_type = item.attachment_type().unwrap_or(AttachmentType::Attachment);
218218
let item_type = DiscardItemType::Attachment(DiscardAttachmentType::from(attachment_type));
219219
let _ = item.reject_err(Outcome::Invalid(DiscardReason::TooLarge(item_type)));
220-
return match ignore_size_exceeded {
221-
true => Ok(None),
222-
false => Err(multer::Error::FieldSizeExceeded {
223-
limit: limit as u64,
224-
field_name,
225-
}),
226-
};
220+
221+
Err(multer::Error::FieldSizeExceeded {
222+
limit: limit as u64,
223+
field_name,
224+
})
225+
} else {
226+
Ok(item)
227227
}
228-
Ok(Some(item))
229228
}
230229

231230
pub async fn multipart_items(
@@ -412,14 +411,13 @@ mod tests {
412411

413412
struct MockAttachmentStrategy;
414413
impl AttachmentStrategy for MockAttachmentStrategy {
415-
fn add_to_item(
414+
async fn add_to_item(
416415
&self,
417416
field: Field<'static>,
418417
item: Managed<Item>,
419418
config: &Config,
420-
) -> impl Future<Output = Result<Option<Managed<Item>>, multer::Error>> + Send
421-
{
422-
read_attachment_bytes_into_item(field, item, config, false)
419+
) -> Result<Option<Managed<Item>>, multer::Error> {
420+
read_bytes_into_item(field, item, config).await.map(Some)
423421
}
424422

425423
fn infer_type(&self, _: &Field) -> AttachmentType {
@@ -465,14 +463,13 @@ mod tests {
465463

466464
struct MockAttachmentStrategy;
467465
impl AttachmentStrategy for MockAttachmentStrategy {
468-
fn add_to_item(
466+
async fn add_to_item(
469467
&self,
470468
field: Field<'static>,
471469
item: Managed<Item>,
472470
config: &Config,
473-
) -> impl Future<Output = Result<Option<Managed<Item>>, multer::Error>> + Send
474-
{
475-
read_attachment_bytes_into_item(field, item, config, true)
471+
) -> Result<Option<Managed<Item>>, multer::Error> {
472+
read_bytes_into_item(field, item, config).await.map(Some)
476473
}
477474

478475
fn infer_type(&self, _: &Field) -> AttachmentType {

tests/integration/test_minidump.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,3 +1215,65 @@ def test_minidump_max_attachment_size_exceeded(
12151215
"quantity": 1,
12161216
},
12171217
]
1218+
1219+
1220+
def test_minidump_large_attachment_skipped_when_no_project_fetching(mini_sentry, relay):
1221+
"""
1222+
When the project fetching in the endpoints is disabled (and as a consequence
1223+
large attachments can not be uploaded to the objectstore), oversized regular
1224+
attachments should be silently skipped rather than rejecting the entire request.
1225+
The minidump must still be present in the forwarded envelope.
1226+
"""
1227+
project_id = 42
1228+
minidump_content = b"MDMP content"
1229+
attachment_content = b"some attachment" * 100
1230+
1231+
mini_sentry.add_full_project_config(project_id)
1232+
mini_sentry.global_config["options"]["relay.endpoint-fetch-config.enabled"] = False
1233+
1234+
relay = relay(
1235+
mini_sentry,
1236+
options={
1237+
"limits": {
1238+
"max_attachment_size": len(attachment_content) - 1,
1239+
"max_attachments_size": 1000 * 1024 * 1024,
1240+
},
1241+
"outcomes": {
1242+
"emit_outcomes": True,
1243+
"batch_size": 1,
1244+
"batch_interval": 1,
1245+
},
1246+
},
1247+
)
1248+
1249+
attachments = [
1250+
("attachment1", "attach1.txt", attachment_content),
1251+
(MINIDUMP_ATTACHMENT_NAME, "minidump.dmp", minidump_content),
1252+
]
1253+
1254+
response = relay.send_minidump(project_id=project_id, files=attachments)
1255+
assert response.ok
1256+
1257+
outcomes = mini_sentry.get_aggregated_outcomes()
1258+
1259+
assert outcomes == [
1260+
{
1261+
"category": 4,
1262+
"outcome": 3,
1263+
"project_id": 42,
1264+
"quantity": 1500,
1265+
"reason": "too_large:attachment:attachment",
1266+
},
1267+
{
1268+
"category": 22,
1269+
"outcome": 3,
1270+
"project_id": 42,
1271+
"quantity": 1,
1272+
"reason": "too_large:attachment:attachment",
1273+
},
1274+
]
1275+
1276+
envelope = mini_sentry.get_captured_envelope()
1277+
1278+
assert len(envelope.items) == 1
1279+
assert envelope.items[0].payload.bytes == minidump_content

0 commit comments

Comments
 (0)