Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b5d66a17 authored by Billy Huang's avatar Billy Huang
Browse files

Reapply "opp: validate that content uri belongs to current user"

Check that the userInfo part of the content URI is
either implicit or the same as the current userId.

This reverts commit db25b125.

Bug: 296915500
Flag: EXEMPT fix for vulnerability
Test: atest GoogleBluetoothInstrumentationTests:BluetoothOppSendFileInfoTest
Ignore-AOSP-First: fix for undisclosed vulnerability
Change-Id: I76b25fcd446d5e0530308e21deafa68d0b768edc
parent 7d475c62
Loading
Loading
Loading
Loading
+21 −1
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@

package com.android.bluetooth.opp;

import static android.os.UserHandle.myUserId;

import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProtoEnums;
import android.content.ContentResolver;
@@ -41,6 +43,7 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;

@@ -53,12 +56,13 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Objects;

/**
 * This class stores information about a single sending file It will only be used for outbound
 * share.
 */
// Next tag value for ContentProfileErrorReportUtils.report(): 15
// Next tag value for ContentProfileErrorReportUtils.report(): 16
public class BluetoothOppSendFileInfo {
    private static final String TAG = "BluetoothOppSendFileInfo";

@@ -124,6 +128,16 @@ public class BluetoothOppSendFileInfo {
                return SEND_FILE_INFO_ERROR;
            }

            if (isContentUriForOtherUser(uri)) {
                Log.e(TAG, "Uri: " + uri + " is invalid for user " + myUserId());
                ContentProfileErrorReportUtils.report(
                        BluetoothProfile.OPP,
                        BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO,
                        BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR,
                        15);
                return SEND_FILE_INFO_ERROR;
            }

            contentType = contentResolver.getType(uri);
            Cursor metadataCursor;
            try {
@@ -353,6 +367,12 @@ public class BluetoothOppSendFileInfo {
        return new BluetoothOppSendFileInfo(fileName, contentType, length, is, 0);
    }

    private static boolean isContentUriForOtherUser(Uri uri) {
        String uriUserId = uri.getUserInfo();
        return !TextUtils.isEmpty(uriUserId)
                && !Objects.equals(uriUserId, String.valueOf(myUserId()));
    }

    private static long getStreamSize(FileInputStream is) throws IOException {
        long length = 0;
        byte[] unused = new byte[4096];
+106 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.bluetooth.opp;

import static android.os.UserHandle.myUserId;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
@@ -119,6 +121,110 @@ public class BluetoothOppSendFileInfoTest {
        assertThat(info).isEqualTo(BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR);
    }

    @Test
    public void generateFileInfo_withContentUriForOtherUser_returnsSendFileInfoError()
            throws Exception {
        String type = "image/jpeg";
        Uri uri = buildContentUriWithEncodedAuthority((myUserId() + 1) + "@media");

        long fileLength = 1000;
        String fileName = "pic.jpg";

        FileInputStream fs = mock(FileInputStream.class);
        AssetFileDescriptor fd = mock(AssetFileDescriptor.class);
        doReturn(fileLength).when(fd).getLength();
        doReturn(fs).when(fd).createInputStream();

        doReturn(fd).when(mCallProxy).contentResolverOpenAssetFileDescriptor(any(), eq(uri), any());

        mCursor =
                new MatrixCursor(new String[] {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE});
        mCursor.addRow(new Object[] {fileName, fileLength});

        doReturn(mCursor)
                .when(mCallProxy)
                .contentResolverQuery(any(), eq(uri), any(), any(), any(), any());

        BluetoothOppSendFileInfo info =
                BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, type, true);

        assertThat(info).isEqualTo(BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR);
    }

    @Test
    public void generateFileInfo_withContentUriForImplicitUser_returnsInfoWithCorrectLength()
            throws Exception {
        String type = "image/jpeg";
        Uri uri = buildContentUriWithEncodedAuthority("media");

        long fileLength = 1000;
        String fileName = "pic.jpg";

        FileInputStream fs = mock(FileInputStream.class);
        AssetFileDescriptor fd = mock(AssetFileDescriptor.class);
        doReturn(fileLength).when(fd).getLength();
        doReturn(fs).when(fd).createInputStream();

        doReturn(fd).when(mCallProxy).contentResolverOpenAssetFileDescriptor(any(), eq(uri), any());

        mCursor =
                new MatrixCursor(new String[] {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE});
        mCursor.addRow(new Object[] {fileName, fileLength});

        doReturn(mCursor)
                .when(mCallProxy)
                .contentResolverQuery(any(), eq(uri), any(), any(), any(), any());

        BluetoothOppSendFileInfo info =
                BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, type, true);

        assertThat(info.mInputStream).isEqualTo(fs);
        assertThat(info.mFileName).isEqualTo(fileName);
        assertThat(info.mLength).isEqualTo(fileLength);
        assertThat(info.mStatus).isEqualTo(0);
    }

    @Test
    public void generateFileInfo_withContentUriForSameUser_returnsInfoWithCorrectLength()
            throws Exception {
        String type = "image/jpeg";
        Uri uri = buildContentUriWithEncodedAuthority(myUserId() + "@media");

        long fileLength = 1000;
        String fileName = "pic.jpg";

        FileInputStream fs = mock(FileInputStream.class);
        AssetFileDescriptor fd = mock(AssetFileDescriptor.class);
        doReturn(fileLength).when(fd).getLength();
        doReturn(fs).when(fd).createInputStream();

        doReturn(fd).when(mCallProxy).contentResolverOpenAssetFileDescriptor(any(), eq(uri), any());

        mCursor =
                new MatrixCursor(new String[] {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE});
        mCursor.addRow(new Object[] {fileName, fileLength});

        doReturn(mCursor)
                .when(mCallProxy)
                .contentResolverQuery(any(), eq(uri), any(), any(), any(), any());

        BluetoothOppSendFileInfo info =
                BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, type, true);

        assertThat(info.mInputStream).isEqualTo(fs);
        assertThat(info.mFileName).isEqualTo(fileName);
        assertThat(info.mLength).isEqualTo(fileLength);
        assertThat(info.mStatus).isEqualTo(0);
    }

    private static Uri buildContentUriWithEncodedAuthority(String authority) {
        return new Uri.Builder()
                .scheme("content")
                .encodedAuthority(authority)
                .path("external/images/media/1")
                .build();
    }

    @Test
    public void generateFileInfo_withoutPermissionForAccessingUri_returnsSendFileInfoError() {
        String type = "text/plain";