{"record":{"id":"503e04de7ba00a76","repo":"signalapp/Signal-Server","slug":"invalid-sourceobject-size","errorCode":null,"errorMessage":"Invalid sourceObject size","messagePattern":"Invalid sourceObject size","errorType":"exception","errorClass":"BackupInvalidArgumentException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java","lineNumber":342,"sourceCode":"   *\n   * @param toCopy     The proposed copy requests\n   * @return QuotaResult indicating which requests fit into the remaining quota and which requests should be\n   * rejected with {@link CopyResult.Outcome#OUT_OF_QUOTA}\n   * @throws BackupInvalidArgumentException if toCopy contains an invalid copy request\n   * @throws BackupPermissionException if the credential does not have the correct level\n   * @throws BackupWrongCredentialTypeException if the credential does not have the media type\n   */\n  public CopyQuota getCopyQuota(\n      final AuthenticatedBackupUser backupUser,\n      final List<CopyParameters> toCopy,\n      final long maximumSourceObjectSize)\n      throws BackupWrongCredentialTypeException, BackupPermissionException, BackupInvalidArgumentException {\n    checkBackupLevel(backupUser, BackupLevel.PAID);\n    checkBackupCredentialType(backupUser, BackupCredentialType.MEDIA);\n\n    for (CopyParameters copyParameters : toCopy) {\n      if (copyParameters.sourceLength() > maximumSourceObjectSize || copyParameters.sourceLength() < 0) {\n        throw new BackupInvalidArgumentException(\"Invalid sourceObject size\");\n      }\n    }\n    final long totalBytesAdded = toCopy.stream().mapToLong(CopyParameters::destinationObjectSize).sum();\n\n    final Duration maxQuotaStaleness = backupConfiguration.maxQuotaStaleness();\n    final long maxTotalMediaSize = backupConfiguration.maxTotalMediaSize();\n\n    final BackupsDb.TimestampedUsageInfo info = backupsDb.getMediaUsage(backupUser).join();\n    long estimatedRemainingQuota = maxTotalMediaSize - info.usageInfo().bytesUsed();\n    final boolean canStore = estimatedRemainingQuota >= totalBytesAdded;\n    if (canStore || info.lastRecalculationTime().isAfter(clock.instant().minus(maxQuotaStaleness))) {\n      return CopyQuota.create(backupUser, toCopy, estimatedRemainingQuota);\n    }\n\n    // The user is out of quota, and we have not recently recalculated the user's usage. Double check by doing a\n    // hard recalculation before actually forbidding the user from storing additional media.\n    boolean usageChanged = false;\n    try {","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java#L324-L360","documentation":"BackupInvalidArgumentException thrown by BackupManager.getCopyQuota when any CopyParameters entry has a sourceLength greater than the configured maximumSourceObjectSize or a negative length. The server validates every source object size up front so copy operations cannot bypass the quota system with oversized objects. Negative sizes are treated as corrupt input rather than 'unlimited'.","triggerScenarios":"Calling the copy-quota/copy endpoint with source objects larger than the configured maximumSourceObjectSize, or with sourceLength serialized as -1 from corrupt or placeholder metadata.","commonSituations":"Copying very large videos between backup layers; deserializing attachment metadata with sentinel -1 lengths; clients computing length from truncated headers; server config lowering maximumSourceObjectSize below previously accepted objects.","solutions":["Check each sourceLength against the server's maximum object size before issuing the copy request and skip or split oversized objects","Fix the client metadata source so lengths are real byte counts, never negative sentinels","Compress or chunk large media before it becomes a source object","If legitimate objects now exceed the limit, have the operator raise maximumSourceObjectSize in backup configuration"],"exampleFix":"// before\ncopy(List.of(new CopyParameters(messageId, hugeFileLength, destLen))); // 400: Invalid sourceObject size\n// after\nif (hugeFileLength >= 0 && hugeFileLength <= MAX_SOURCE_OBJECT_SIZE) {\n  copy(List.of(new CopyParameters(messageId, hugeFileLength, destLen)));\n} else {\n  uploadFreshMediaCopy(hugeFile); // skip oversized source\n}","handlingStrategy":"validation","validationCode":"for (CopyParameters p : toCopy) {\n  if (p.sourceLength() < 0 || p.sourceLength() > MAX_SOURCE_OBJECT_SIZE) {\n    throw new IllegalArgumentException(\"sourceObject size out of range: \" + p.sourceLength());\n  }\n}","typeGuard":null,"tryCatchPattern":"try { getCopyQuota(backupUser, toCopy); }\ncatch (BackupInvalidArgumentException e) {\n  if (e.getMessage().contains(\"sourceObject\")) { splitOrReuploadOversizedObjects(toCopy); } else { throw e; }\n}","preventionTips":["Check byte length of every source object before copy requests","Reject negative sentinel lengths at metadata parse time","Compress or chunk media larger than the configured maximum","Keep client size limits aligned with server backupConfiguration"],"tags":["backup","media-copy","size-limit"],"backgroundTag":"file-size-limit-exceeded","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}