Add upload/download size restrictions for attachments based on remote config.

This commit is contained in:
Clark 2023-06-30 11:06:36 -04:00 committed by Greyson Parrelli
parent 87d4dba32b
commit f4a082584c
10 changed files with 90 additions and 45 deletions

View file

@ -30,7 +30,6 @@ import org.thoughtcrime.securesms.s3.S3;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.util.AttachmentUtil;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.ByteUnit;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
@ -58,9 +57,6 @@ public final class AttachmentDownloadJob extends BaseJob {
private static final String TAG = Log.tag(AttachmentDownloadJob.class);
/** A little extra allowed size to account for any adjustments made by other clients */
private static final int MAX_ATTACHMENT_SIZE_BUFFER = 25 * 1024 * 1024;
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_PART_ROW_ID = "part_row_id";
private static final String KEY_PAR_UNIQUE_ID = "part_unique_id";
@ -188,16 +184,20 @@ public final class AttachmentDownloadJob extends BaseJob {
final Attachment attachment)
throws IOException, RetryLaterException
{
long maxReceiveSize = FeatureFlags.maxAttachmentReceiveSizeBytes();
AttachmentTable database = SignalDatabase.attachments();
File attachmentFile = database.getOrCreateTransferFile(attachmentId);
try {
if (attachment.getSize() > maxReceiveSize) {
throw new MmsException("Attachment too large, failing download");
}
SignalServiceMessageReceiver messageReceiver = ApplicationDependencies.getSignalServiceMessageReceiver();
SignalServiceAttachmentPointer pointer = createAttachmentPointer(attachment);
InputStream stream = messageReceiver.retrieveAttachment(pointer,
attachmentFile,
ByteUnit.MEGABYTES.toBytes(FeatureFlags.maxAttachmentSizeMb()) + MAX_ATTACHMENT_SIZE_BUFFER,
maxReceiveSize,
(total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.NETWORK, total, progress)));
database.insertAttachmentsForPlaceholder(messageId, attachmentId, stream);
@ -269,6 +269,9 @@ public final class AttachmentDownloadJob extends BaseJob {
try (Response response = S3.getObject(Objects.requireNonNull(attachment.getFileName()))) {
ResponseBody body = response.body();
if (body != null) {
if (body.contentLength() > FeatureFlags.maxAttachmentReceiveSizeBytes()) {
throw new MmsException("Attachment too large, failing download");
}
SignalDatabase.attachments().insertAttachmentsForPlaceholder(messageId, attachmentId, Okio.buffer(body.source()).inputStream());
}
} catch (MmsException e) {

View file

@ -26,13 +26,16 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.NotificationController;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.AttachmentCipherOutputStream;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResumableUploadResponseCodeException;
import org.whispersystems.signalservice.api.push.exceptions.ResumeLocationInvalidException;
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec;
import java.io.IOException;
@ -64,6 +67,12 @@ public final class AttachmentUploadJob extends BaseJob {
*/
private static final int FOREGROUND_LIMIT = 10 * 1024 * 1024;
public static long getMaxPlaintextSize() {
long maxCipherTextSize = FeatureFlags.maxAttachmentSizeBytes();
long maxPaddedSize = AttachmentCipherOutputStream.getPlaintextLength(maxCipherTextSize);
return PaddingInputStream.getMaxUnpaddedSize(maxPaddedSize);
}
private final AttachmentId attachmentId;
private boolean forceV2;

View file

@ -11,11 +11,14 @@ import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.MemoryFileDescriptor;
import org.whispersystems.signalservice.api.crypto.AttachmentCipherOutputStream;
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
import java.io.IOException;
import java.io.InputStream;
@ -50,31 +53,39 @@ public abstract class MediaConstraints {
*/
public abstract int[] getImageDimensionTargets(Context context);
public abstract int getGifMaxSize(Context context);
public abstract int getVideoMaxSize(Context context);
public abstract long getGifMaxSize(Context context);
public abstract long getVideoMaxSize(Context context);
public @IntRange(from = 0, to = 100) int getImageCompressionQualitySetting(@NonNull Context context) {
return 70;
}
public int getUncompressedVideoMaxSize(Context context) {
public long getUncompressedVideoMaxSize(Context context) {
return getVideoMaxSize(context);
}
public int getCompressedVideoMaxSize(Context context) {
public long getCompressedVideoMaxSize(Context context) {
return getVideoMaxSize(context);
}
public abstract int getAudioMaxSize(Context context);
public abstract int getDocumentMaxSize(Context context);
public abstract long getAudioMaxSize(Context context);
public abstract long getDocumentMaxSize(Context context);
public long getMaxAttachmentSize() {
return AttachmentUploadJob.getMaxPlaintextSize();
}
public boolean isSatisfied(@NonNull Context context, @NonNull Attachment attachment) {
try {
return (MediaUtil.isGif(attachment) && attachment.getSize() <= getGifMaxSize(context) && isWithinBounds(context, attachment.getUri())) ||
(MediaUtil.isImage(attachment) && attachment.getSize() <= getImageMaxSize(context) && isWithinBounds(context, attachment.getUri())) ||
(MediaUtil.isAudio(attachment) && attachment.getSize() <= getAudioMaxSize(context)) ||
(MediaUtil.isVideo(attachment) && attachment.getSize() <= getVideoMaxSize(context)) ||
(MediaUtil.isFile(attachment) && attachment.getSize() <= getDocumentMaxSize(context));
long size = attachment.getSize();
if (size > getMaxAttachmentSize()) {
return false;
}
return (MediaUtil.isGif(attachment) && size <= getGifMaxSize(context) && isWithinBounds(context, attachment.getUri())) ||
(MediaUtil.isImage(attachment) && size <= getImageMaxSize(context) && isWithinBounds(context, attachment.getUri())) ||
(MediaUtil.isAudio(attachment) && size <= getAudioMaxSize(context)) ||
(MediaUtil.isVideo(attachment) && size <= getVideoMaxSize(context)) ||
(MediaUtil.isFile(attachment) && size <= getDocumentMaxSize(context));
} catch (IOException ioe) {
Log.w(TAG, "Failed to determine if media's constraints are satisfied.", ioe);
return false;
@ -83,6 +94,9 @@ public abstract class MediaConstraints {
public boolean isSatisfied(@NonNull Context context, @NonNull Uri uri, @NonNull String contentType, long size) {
try {
if (size > getMaxAttachmentSize()) {
return false;
}
return (MediaUtil.isGif(contentType) && size <= getGifMaxSize(context) && isWithinBounds(context, uri)) ||
(MediaUtil.isImageType(contentType) && size <= getImageMaxSize(context) && isWithinBounds(context, uri)) ||
(MediaUtil.isAudioType(contentType) && size <= getAudioMaxSize(context)) ||

View file

@ -43,27 +43,27 @@ final class MmsMediaConstraints extends MediaConstraints {
}
@Override
public int getGifMaxSize(Context context) {
public long getGifMaxSize(Context context) {
return getMaxMessageSize(context);
}
@Override
public int getVideoMaxSize(Context context) {
public long getVideoMaxSize(Context context) {
return getMaxMessageSize(context);
}
@Override
public int getUncompressedVideoMaxSize(Context context) {
public long getUncompressedVideoMaxSize(Context context) {
return Math.max(getVideoMaxSize(context), 15 * 1024 * 1024);
}
@Override
public int getAudioMaxSize(Context context) {
public long getAudioMaxSize(Context context) {
return getMaxMessageSize(context);
}
@Override
public int getDocumentMaxSize(Context context) {
public long getDocumentMaxSize(Context context) {
return getMaxMessageSize(context);
}

View file

@ -40,7 +40,7 @@ public class PushMediaConstraints extends MediaConstraints {
@Override
public int getImageMaxSize(Context context) {
return currentConfig.maxImageFileSize;
return (int) Math.min(currentConfig.maxImageFileSize, getMaxAttachmentSize());
}
@Override
@ -49,35 +49,35 @@ public class PushMediaConstraints extends MediaConstraints {
}
@Override
public int getGifMaxSize(Context context) {
return 25 * MB;
public long getGifMaxSize(Context context) {
return Math.min(25 * MB, getMaxAttachmentSize());
}
@Override
public int getVideoMaxSize(Context context) {
return 100 * MB;
public long getVideoMaxSize(Context context) {
return getMaxAttachmentSize();
}
@Override
public int getUncompressedVideoMaxSize(Context context) {
public long getUncompressedVideoMaxSize(Context context) {
return isVideoTranscodeAvailable() ? 500 * MB
: getVideoMaxSize(context);
}
@Override
public int getCompressedVideoMaxSize(Context context) {
public long getCompressedVideoMaxSize(Context context) {
return Util.isLowMemory(context) ? 30 * MB
: 50 * MB;
}
@Override
public int getAudioMaxSize(Context context) {
return 100 * MB;
public long getAudioMaxSize(Context context) {
return getMaxAttachmentSize();
}
@Override
public int getDocumentMaxSize(Context context) {
return 100 * MB;
public long getDocumentMaxSize(Context context) {
return getMaxAttachmentSize();
}
@Override

View file

@ -27,22 +27,22 @@ public class ProfileMediaConstraints extends MediaConstraints {
}
@Override
public int getGifMaxSize(Context context) {
public long getGifMaxSize(Context context) {
return 0;
}
@Override
public int getVideoMaxSize(Context context) {
public long getVideoMaxSize(Context context) {
return 0;
}
@Override
public int getAudioMaxSize(Context context) {
public long getAudioMaxSize(Context context) {
return 0;
}
@Override
public int getDocumentMaxSize(Context context) {
public long getDocumentMaxSize(Context context) {
return 0;
}
}

View file

@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.groups.SelectionLimits;
import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
import java.io.IOException;
import java.util.HashMap;
@ -106,7 +107,8 @@ public final class FeatureFlags {
private static final String AD_HOC_CALLING = "android.calling.ad.hoc.2";
private static final String EDIT_MESSAGE_SEND = "android.editMessage.send.3";
private static final String MAX_ATTACHMENT_COUNT = "android.attachments.maxCount";
private static final String MAX_ATTACHMENT_SIZE_MB = "android.attachments.maxSize";
private static final String MAX_ATTACHMENT_RECEIVE_SIZE_BYTES = "global.attachments.maxReceiveBytes";
private static final String MAX_ATTACHMENT_SIZE_BYTES = "global.attachments.maxBytes";
/**
* We will only store remote values for flags in this set. If you want a flag to be controllable
@ -165,7 +167,8 @@ public final class FeatureFlags {
ANY_ADDRESS_PORTS_KILL_SWITCH,
EDIT_MESSAGE_SEND,
MAX_ATTACHMENT_COUNT,
MAX_ATTACHMENT_SIZE_MB,
MAX_ATTACHMENT_RECEIVE_SIZE_BYTES,
MAX_ATTACHMENT_SIZE_BYTES,
AD_HOC_CALLING
);
@ -231,7 +234,8 @@ public final class FeatureFlags {
CDS_HARD_LIMIT,
EDIT_MESSAGE_SEND,
MAX_ATTACHMENT_COUNT,
MAX_ATTACHMENT_SIZE_MB
MAX_ATTACHMENT_RECEIVE_SIZE_BYTES,
MAX_ATTACHMENT_SIZE_BYTES
);
/**
@ -594,9 +598,16 @@ public final class FeatureFlags {
return getInteger(MAX_ATTACHMENT_COUNT, 32);
}
/** Maximum attachment size, in mebibytes. */
public static int maxAttachmentSizeMb() {
return getInteger(MAX_ATTACHMENT_SIZE_MB, 100);
/** Maximum attachment size for ciphertext in bytes. */
public static long maxAttachmentReceiveSizeBytes() {
long maxAttachmentSize = maxAttachmentSizeBytes();
long maxReceiveSize = getLong(MAX_ATTACHMENT_RECEIVE_SIZE_BYTES, (int) (maxAttachmentSize * 1.25));
return Math.max(maxAttachmentSize, maxReceiveSize);
}
/** Maximum attachment ciphertext size when sending in bytes */
public static long maxAttachmentSizeBytes() {
return getLong(MAX_ATTACHMENT_SIZE_BYTES, ByteUnit.MEGABYTES.toBytes(100));
}
/** Only for rendering debug info. */

View file

@ -41,8 +41,8 @@ public final class VideoUtil {
}
public static int getMaxVideoRecordDurationInSeconds(@NonNull Context context, @NonNull MediaConstraints mediaConstraints) {
int allowedSize = mediaConstraints.getCompressedVideoMaxSize(context);
int duration = (int) Math.floor((float) allowedSize / TOTAL_BYTES_PER_SECOND);
long allowedSize = mediaConstraints.getCompressedVideoMaxSize(context);
int duration = (int) Math.floor((float) allowedSize / TOTAL_BYTES_PER_SECOND);
return Math.min(duration, VIDEO_MAX_RECORD_LENGTH_S);
}

View file

@ -89,7 +89,11 @@ public class AttachmentCipherOutputStream extends DigestingOutputStream {
}
public static long getCiphertextLength(long plaintextLength) {
return 16 + (((plaintextLength / 16) +1) * 16) + 32;
return 16 + (((plaintextLength / 16) + 1) * 16) + 32;
}
public static long getPlaintextLength(long ciphertextLength) {
return (((ciphertextLength - 16 - 32) / 16) - 1) * 16;
}
private Mac initializeMac() {

View file

@ -56,4 +56,8 @@ public class PaddingInputStream extends FilterInputStream {
public static long getPaddedSize(long size) {
return (int) Math.max(541, Math.floor(Math.pow(1.05, Math.ceil(Math.log(size) / Math.log(1.05)))));
}
public static long getMaxUnpaddedSize(long maxPaddedSize) {
return (int) Math.floor(Math.pow(1.05, Math.floor(Math.log(maxPaddedSize) / Math.log(1.05))));
}
}