Android 9.0でのsdcardの権限とマウントの問題
前言
Androidは6.0からRuntime permissionを導入しており、アプリケーションがstorageに対して読み取り、記憶を行う際には、登録、申請に対応する権限が必要です.Android 8.0ではsdcardの読み書きに対して申請権限だけで使用でき、Android 9.0で同じアプリケーションで同じ手順を実行できるが、Permission deniedに提示されている.
本稿では,これによりsdcardを簡単に剖析する.コードベースバージョンAndroid 9.0
問題の説明
1、応用中のコード
コードのように、ファイル作成はcreateFile()によって行われます.テストなので、filePathは/storage/6344-0 FEFとして直接書かれています.
アプリケーションのAndroidManifestでxmlでは、次の権限も与えられています.
2、問題log
もんだいかいせき
最終的にFileに対する操作がUnixFileSystemに呼び出されることがlogでわかる.createFileExclusively 0()では、source codeを見てみましょう.
最終的にはnativeメソッドが呼び出されます.
詳細はlibcore/ojluni/src/main/native/UnixFileSystemを参照してください.md.c
pathが空でない場合handleOpen()が呼び出されます.
Open 64はライブラリ関数open、すなわちopenのときにerrorが現れ、errnoは13、すなわちEACCES、すなわちPermission deniedである.
では、この問題の原因は、おそらくファイルノードの権限が足りないことであり、この考えを持ってファイルノードを見てみましょう.
もんだいぶんせき
まず、デバイスのmountの状況を見てみましょう.
現在、システムはsdcardfsファイルシステムを使用しており、以前よりfuse効率が高い.
また、次のことがわかりました./mnt/runtime/defaultのgidは1015、すなわちsdcard_rw;maskは6で、つまりotherにはrw権限がありません. /mnt/runtime/readのgidは9997、すなわちeverybodyである.maskは18、つまりgroup、otherにはw権限がありません. /mnt/runtime/writeのgidは9997、すなわちeverybodyである.maskは18、つまりgroup、otherにはw権限がありません.
gidと名前の詳細はsource codeを見ることができます.パスはsystem/core/include/cutils/android_です.filesystem_config.h
これらのノードを見てみましょう.
defaultノードの場合groupidはsdcard_rw,他の2つのgroupはeverybodyであり,これは上のmountの結果と一致した.ここで実際にsdcardが書き込めないのはmountのときにw権限が与えられていないためであることがわかります.
PublicVolume
パス:system/vold/model/PublicVolume.cpp
sdcardがマウントされるとvoldはPublicVolumeのdoMount()に呼び出されます.
最終的には、いくつかのmountのパラメータがexecl関数で実装され、関数パラメータkFusePathは次のようになります.
コードにより、外付けsdcardパラメータに-wがないことがわかり、次のmountの場合にw権限が与えられず、分析を続行します.
最終的には実行可能プログラムsdcardによってmount()が実現されます.詳細はsystem/core/sdcard/sdcard.を参照してください.cpp:
注意: full_writeが全ユーザにw権限を与えるかどうか default_normalがdefault_を使用するかどうかnormalモード should_use_sdcardfs()はsdcardfsを使用するかどうか、デフォルトはtrue です.
PublicVolumeから渡されたパラメータを解析し、最終的にrun_を実行します.sdcardfs():
ここでは、gidがAID_であるmount sdcardのすべてのパラメータを指定します.EVERYBODY、これは上のmount情報に対応しています.
また、full_によりwriteは、対応するノードに権限を書き込むかどうかを確認します.PublicVolume.domount()関数は、外付けsdcardに対して-wパラメータが入力されていないことを知っています.つまり、ここのfull_writeはfalseであり、/mnt/runtime/write/labelに対するumaskは0022、すなわちgroupとotherにw権限が与えられていないことがコードによって発見され、これが最終的にEACCESが現れる根本的な場所となった.
Androidは外付けのsdcardを書かせないと思っているのか分からない.それともAndroidが存在するバグなのでしょうか?
これはしばらく分かりませんが、次のバージョンが出てきてgoogleが相応の変更があるかどうかを確認するのを待っています.
もちろん最初に言ったPermission deniedの問題を修正したいなら、ここのumaskを修正すれば間違いありません!
ここで疑問に思うのは、なぜAndroid 8.0の時にsdcardの読み書き権限を外付けしていないのかという問題です.
この問題については、以下で詳しく説明しますが、その前にstorageに対するgidがどのように来たのかを見てみましょう!!
Storageのgidと権限制御
アプリケーションを起動すると、zygoteを通じてプロセスを再forkすることを知っています.
ソース解析-AndroidのZygoteプロセスがどのようにアプリプロセスをforkするかという文から、zygote forkプロセスのプロセス全体を知っていますが、AMSでは関数startProcessLockedを通じて入り、変数gids、mountExternalはzygote forkプロセスまで関数に従います.
ソースコードを見てみましょう
ここでgidはPMS解析により取得され,アプリケーションで使用される権限に関連するgroup idはすべてここにある.
mountExternalは、以下のような外部デバイスmountタイプを取得します.
mountExternal
まず、getExternalStorageMountMode()を見てみましょう.
登録されたExternalStorageMountPolicyを1つずつクエリーし、最小のmodeを取得することが最後に必要です.クエリの関数はgetMountMode()であり,アプリケーションに登録されているPolicyについてはPMSで詳細はPMS.を参照する.systemReady():
ここがstorage権限を扱う場所で、READ_EXTERNAL_STORAGE権限が申請されていない場合、デフォルトmountのmodeはMOUNT_です.EXTERNAL_DEFAULT(sdcard_rwなどの特殊な権限が必要).READだけあげたらEXTERNAL_STORAGE、WRITEにあげなかったEXTERNAL_STORAGE,mount modeはMOUNT_EXTERNAL_READ(読取り専用権限);両方の権限が申請された場合、mount modeはMOUNT_です.EXTERNAL_WRITE(読み書き権限).mount modeは以下に説明を続けます.
startViaZygote()
ソース解析-AndroidのZygoteプロセスがどのようにアプリプロセスをforkしているかの一文から、AMS.startProcessLocked()以降もAMSを呼び出し続ける.startProcess()に続いてZygoteProcess.start()は、最終的にZygoteProcessを呼び出す.startViaZygote():
異なるmount modeから異なるパラメータが入力され、最終的にzygoteに入力されてforkが行われる.
ソースコード解析-AndroidのZygoteプロセスがどのようにアプリプロセスをforkするかの一文から、最後にzygoteを呼び出すことを知った.forkAndSpecialize():
ここでnativeForkAndSpecialize()は最後にJNIに呼び出されますcom_android_internal_os_Zygote.cppで、関数MountEmulatedStorage()をトリガーします.
PMSで取り上げたmount modeはここのmount_mode,すなわち全過程で伝達されるmountExternal変数である.
ここでは2つのことをしました.所与のuserに対して、指定された/mnt/runtime/readまたは/mnt/runtime/writeまたは/mnt/runtime/defaultを/storageディレクトリの下にマウントする. 対応するuserプライベートstorageディレクトリを作成し、/storage/selfの下にマウントします.
Android 8.0との違い
8.0で外部設定sdcardにアクセスするには、前の権限を追加する必要があります.
この権限を申請するアプリケーションはgroup idにmediaを追加します.rwとsdcard_rw.
詳細はframeworks/base/data/etc/platform.xml
Android 9.0ではsdcard_rwこのグループは削除します.
PMSでExternalStorageMountPolicyを登録した場所:
デフォルトでWRITE_が申請されましたMEDIA_STORAGE権限後、mount modeをMOUNT_に設定EXTERNAL_DEFAULTでは、zygoteでもmountから/mnt/runtime/defaultがデフォルトで、このノードのgroup idはsdcard_rw、すなわちsdcard_rwグループ内のアプリケーションは,いずれもこのノードで読み書きが可能である.
ただし、Android 9.0ではREAD_のみEXTERNAL_STORAGEとWRITE_EXTERNAL_STORAGEの2つの権限が処理されました.
そのため、同じようにこれらの権限を申請したアプリケーションがAndroid 8.0で読み書きができるのに、Android 9.0で問題が発生したのです.
プロセス情報の表示
方法1:
例えば、ファイルブラウザにmediaがあるかどうかを見たいです.rwの権限、私達はまずpsを見て、ファイルブラウザのpidを見つけます
それからproc/pidの下を見て、ここならproc/6653で、cat status:
Groupsがあるのを見ましたrwは1023のはずです
idコマンドを使用して確認できます.
確かに1023です.これにより、ファイルブラウザアプリケーションにmediaがあることが確認されます.rwの権限
方法2:
私たちは/system/etc/permissionsディレクトリのplatformに行くことができます.xml表示media_rw対応権限
それからファイルブラウザのソースコードのAndroidManifestに行きます.xmlファイルは、次のコードで、その権限があることを知っています.
まとめ:
storageの読み書き操作を正常に使用するには、いくつかのステップに注意する必要があります.
1、対応するstorage権限の申請
アプリケーションのAndroidManifestでxmlにstorage権限を登録し、grantすることができます.
2、アプリケーションが正しいmount modeを取得したことを確定する
現在では1点目と同じですが、後で権限を追加する可能性があります.例えば、前のWRITE_MEDIA_STORAGE
3、mountのノードのユーザー、ユーザーグループ権限を確定する
ディレクトリ/mnt/runtime/readまたは/mnt/runtime/writeまたは/mnt/runtime/defaultにアクセスして、対応するファイル操作権限を確認します.
たとえば、mount modeは/mnt/runtime/writeノードを明確に指していますが、ユーザーグループがw権限を与えていないため、Permission deniedエラーが発生します.
Android 9.0の場合、最初はmount/mnt/runtime/*がsdcard.cppではmount時のumaskを修正すればよい.
もう一つの博文論Android 9.0外付けsdcard読み書きは、プリセットアプリケーションを深く掘り起こし、議論している.
Androidは6.0からRuntime permissionを導入しており、アプリケーションがstorageに対して読み取り、記憶を行う際には、登録、申請に対応する権限が必要です.Android 8.0ではsdcardの読み書きに対して申請権限だけで使用でき、Android 9.0で同じアプリケーションで同じ手順を実行できるが、Permission deniedに提示されている.
本稿では,これによりsdcardを簡単に剖析する.コードベースバージョンAndroid 9.0
問題の説明
1、応用中のコード
private boolean doCreate(File file) {
try {
File parentFile = file.getParentFile();
if (!isFileExists(parentFile)) {
parentFile.mkdirs();
}
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
private boolean createFile(File file) {
if (isFileExists(file))
return true;
return doCreate(file);
}
/**
*
*/
private boolean createFile(String filePath) {
Log.d(TAG, "==== createFile, filePath = " + filePath);
File file = new File(filePath);
return createFile(file);
}
コードのように、ファイル作成はcreateFile()によって行われます.テストなので、filePathは/storage/6344-0 FEFとして直接書かれています.
アプリケーションのAndroidManifestでxmlでは、次の権限も与えられています.
2、問題log
01-02 05:47:32.711 24318 24318 W System.err: java.io.IOException: Permission denied
01-02 05:47:32.711 24318 24318 W System.err: at java.io.UnixFileSystem.createFileExclusively0(Native Method)
01-02 05:47:32.711 24318 24318 W System.err: at java.io.UnixFileSystem.createFileExclusively(UnixFileSystem.java:281)
01-02 05:47:32.712 24318 24318 W System.err: at java.io.File.createNewFile(File.java:1008)
01-02 05:47:32.712 24318 24318 W System.err: at com.shift.test.testfile.TestFileActivity.doCreate(TestFileActivity.java:126)
01-02 05:47:32.712 24318 24318 W System.err: at com.shift.test.testfile.TestFileActivity.createFile(TestFileActivity.java:138)
01-02 05:47:32.712 24318 24318 W System.err: at com.shift.test.testfile.TestFileActivity.createFile(TestFileActivity.java:145)
01-02 05:47:32.713 24318 24318 W System.err: at com.shift.test.testfile.TestFileActivity.createInSdcard(TestFileActivity.java:110)
01-02 05:47:32.713 24318 24318 W System.err: at com.shift.test.testfile.TestFileActivity.onClick(TestFileActivity.java:183)
01-02 05:47:32.714 24318 24318 W System.err: at android.view.View.performClick(View.java:6597)
01-02 05:47:32.714 24318 24318 W System.err: at android.view.View.performClickInternal(View.java:6574)
01-02 05:47:32.714 24318 24318 W System.err: at android.view.View.access$3100(View.java:778)
01-02 05:47:32.714 24318 24318 W System.err: at android.view.View$PerformClick.run(View.java:25889)
01-02 05:47:32.714 24318 24318 W System.err: at android.os.Handler.handleCallback(Handler.java:873)
01-02 05:47:32.714 24318 24318 W System.err: at android.os.Handler.dispatchMessage(Handler.java:99)
01-02 05:47:32.714 24318 24318 W System.err: at android.os.Looper.loop(Looper.java:193)
01-02 05:47:32.714 24318 24318 W System.err: at android.app.ActivityThread.main(ActivityThread.java:6692)
01-02 05:47:32.714 24318 24318 W System.err: at java.lang.reflect.Method.invoke(Native Method)
01-02 05:47:32.715 24318 24318 W System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
01-02 05:47:32.715 24318 24318 W System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
もんだいかいせき
最終的にFileに対する操作がUnixFileSystemに呼び出されることがlogでわかる.createFileExclusively 0()では、source codeを見てみましょう.
/* -- File operations -- */
// Android-changed: Added thread policy check
public boolean createFileExclusively(String path) throws IOException {
BlockGuard.getThreadPolicy().onWriteToDisk();
return createFileExclusively0(path);
}
private native boolean createFileExclusively0(String path) throws IOException;
最終的にはnativeメソッドが呼び出されます.
詳細はlibcore/ojluni/src/main/native/UnixFileSystemを参照してください.md.c
// Android-changed: Name changed because of added thread policy check
JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_createFileExclusively0(JNIEnv *env, jclass cls,
jstring pathname)
{
jboolean rv = JNI_FALSE;
WITH_PLATFORM_STRING(env, pathname, path) {
FD fd;
/* The root directory always exists */
if (strcmp (path, "/")) {
fd = handleOpen(path, O_RDWR | O_CREAT | O_EXCL, 0666);
ALOGD("path = %s, fd = %d, errno = %d", path, fd, errno);
if (fd < 0) {
if (errno != EEXIST)
JNU_ThrowIOExceptionWithLastError(env, path);
} else {
if (close(fd) == -1)
JNU_ThrowIOExceptionWithLastError(env, path);
rv = JNI_TRUE;
}
}
} END_PLATFORM_STRING(env, path);
return rv;
}
pathが空でない場合handleOpen()が呼び出されます.
FD
handleOpen(const char *path, int oflag, int mode) {
FD fd;
RESTARTABLE(open64(path, oflag, mode), fd);
if (fd != -1) {
struct stat64 buf64;
int result;
RESTARTABLE(fstat64(fd, &buf64), result);
if (result != -1) {
if (S_ISDIR(buf64.st_mode)) {
close(fd);
errno = EISDIR;
fd = -1;
}
} else {
close(fd);
fd = -1;
}
}
return fd;
}
Open 64はライブラリ関数open、すなわちopenのときにerrorが現れ、errnoは13、すなわちEACCES、すなわちPermission deniedである.
では、この問題の原因は、おそらくファイルノードの権限が足りないことであり、この考えを持ってファイルノードを見てみましょう.
もんだいぶんせき
まず、デバイスのmountの状況を見てみましょう.
/dev/block/vold/public:179,65 on /mnt/media_rw/6344-0FEF type vfat (rw,dirsync,nosuid,nodev,noexec,noatime,uid=1023,gid=1023,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro)
/mnt/media_rw/6344-0FEF on /mnt/runtime/default/6344-0FEF type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,mask=6)
/mnt/media_rw/6344-0FEF on /storage/6344-0FEF type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,mask=6)
/mnt/media_rw/6344-0FEF on /mnt/runtime/read/6344-0FEF type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,mask=18)
/mnt/media_rw/6344-0FEF on /mnt/runtime/write/6344-0FEF type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,mask=18)
現在、システムはsdcardfsファイルシステムを使用しており、以前よりfuse効率が高い.
また、次のことがわかりました.
gidと名前の詳細はsource codeを見ることができます.パスはsystem/core/include/cutils/android_です.filesystem_config.h
#define AID_SDCARD_RW 1015 /* external storage write access */
#define AID_MEDIA_RW 1023 /* internal media storage write access */
#define AID_EVERYBODY 9997 /* shared between all apps in the same profile */
これらのノードを見てみましょう.
msm8940_EVB:/mnt/runtime/write # ls -l
total 36
drwxr-xr-x 9 root everybody 32768 2018-12-29 03:53 6344-0FEF
msm8940_EVB:/mnt/runtime/read # ls -l
total 36
drwxr-xr-x 9 root everybody 32768 2018-12-29 03:53 6344-0FEF
msm8940_EVB:/mnt/runtime/default # ls -l
total 36
drwxrwx--x 9 root sdcard_rw 32768 2018-12-29 03:53 6344-0FEF
defaultノードの場合groupidはsdcard_rw,他の2つのgroupはeverybodyであり,これは上のmountの結果と一致した.ここで実際にsdcardが書き込めないのはmountのときにw権限が与えられていないためであることがわかります.
PublicVolume
パス:system/vold/model/PublicVolume.cpp
sdcardがマウントされるとvoldはPublicVolumeのdoMount()に呼び出されます.
status_t PublicVolume::doMount() {
...
...
mRawPath = StringPrintf("/mnt/media_rw/%s", stableName.c_str());
mFuseDefault = StringPrintf("/mnt/runtime/default/%s", stableName.c_str());
mFuseRead = StringPrintf("/mnt/runtime/read/%s", stableName.c_str());
mFuseWrite = StringPrintf("/mnt/runtime/write/%s", stableName.c_str());
setInternalPath(mRawPath);
if (getMountFlags() & MountFlags::kVisible) {
setPath(StringPrintf("/storage/%s", stableName.c_str()));
} else {
setPath(mRawPath);
}
if (fs_prepare_dir(mRawPath.c_str(), 0700, AID_ROOT, AID_ROOT)) {
PLOG(ERROR) << getId() << " failed to create mount points";
return -errno;
}
...
if (getMountFlags() & MountFlags::kPrimary) {
initAsecStage();
}
if (!(getMountFlags() & MountFlags::kVisible)) {
// Not visible to apps, so no need to spin up FUSE
return OK;
}
if (fs_prepare_dir(mFuseDefault.c_str(), 0700, AID_ROOT, AID_ROOT) ||
fs_prepare_dir(mFuseRead.c_str(), 0700, AID_ROOT, AID_ROOT) ||
fs_prepare_dir(mFuseWrite.c_str(), 0700, AID_ROOT, AID_ROOT)) {
PLOG(ERROR) << getId() << " failed to create FUSE mount points";
return -errno;
}
dev_t before = GetDevice(mFuseWrite);
if (!(mFusePid = fork())) {
if (getMountFlags() & MountFlags::kPrimary) {
if (execl(kFusePath, kFusePath,
"-u", "1023", // AID_MEDIA_RW
"-g", "1023", // AID_MEDIA_RW
"-U", std::to_string(getMountUserId()).c_str(),
"-w",
mRawPath.c_str(),
stableName.c_str(),
NULL)) {
PLOG(ERROR) << "Failed to exec";
}
} else {
if (execl(kFusePath, kFusePath,
"-u", "1023", // AID_MEDIA_RW
"-g", "1023", // AID_MEDIA_RW
"-U", std::to_string(getMountUserId()).c_str(),
mRawPath.c_str(),
stableName.c_str(),
NULL)) {
PLOG(ERROR) << "Failed to exec";
}
}
LOG(ERROR) << "FUSE exiting";
_exit(1);
}
...
...
return OK;
}
最終的には、いくつかのmountのパラメータがexecl関数で実装され、関数パラメータkFusePathは次のようになります.
static const char* kFusePath = "/system/bin/sdcard";
コードにより、外付けsdcardパラメータに-wがないことがわかり、次のmountの場合にw権限が与えられず、分析を続行します.
最終的には実行可能プログラムsdcardによってmount()が実現されます.詳細はsystem/core/sdcard/sdcard.を参照してください.cpp:
int main(int argc, char **argv) {
...
...
int opt;
while ((opt = getopt(argc, argv, "u:g:U:mwGi")) != -1) {
switch (opt) {
case 'u':
uid = strtoul(optarg, NULL, 10);
break;
case 'g':
gid = strtoul(optarg, NULL, 10);
break;
case 'U':
userid = strtoul(optarg, NULL, 10);
break;
case 'm':
multi_user = true;
break;
case 'w':
full_write = true;
break;
case 'G':
derive_gid = true;
break;
case 'i':
default_normal = true;
break;
case '?':
default:
return usage();
}
}
for (i = optind; i < argc; i++) {
char* arg = argv[i];
if (!source_path) {
source_path = arg;
} else if (!label) {
label = arg;
} else {
LOG(ERROR) << "too many arguments";
return usage();
}
}
if (!source_path) {
LOG(ERROR) << "no source path specified";
return usage();
}
if (!label) {
LOG(ERROR) << "no label specified";
return usage();
}
if (!uid || !gid) {
LOG(ERROR) << "uid and gid must be nonzero";
return usage();
}
rlim.rlim_cur = 8192;
rlim.rlim_max = 8192;
if (setrlimit(RLIMIT_NOFILE, &rlim) == -1) {
PLOG(ERROR) << "setting RLIMIT_NOFILE failed";
}
while ((fs_read_atomic_int("/data/.layout_version", &fs_version) == -1) || (fs_version < 3)) {
LOG(ERROR) << "installd fs upgrade not yet complete; waiting...";
sleep(1);
}
run_sdcardfs(source_path, label, uid, gid, userid, multi_user, full_write, derive_gid,
default_normal, !should_use_sdcardfs());
return 1;
}
注意:
PublicVolumeから渡されたパラメータを解析し、最終的にrun_を実行します.sdcardfs():
static void run_sdcardfs(const std::string& source_path, const std::string& label, uid_t uid,
gid_t gid, userid_t userid, bool multi_user, bool full_write,
bool derive_gid, bool default_normal, bool use_esdfs) {
std::string dest_path_default = "/mnt/runtime/default/" + label;
std::string dest_path_read = "/mnt/runtime/read/" + label;
std::string dest_path_write = "/mnt/runtime/write/" + label;
umask(0);
if (multi_user) {
// Multi-user storage is fully isolated per user, so "other"
// permissions are completely masked off.
if (!sdcardfs_setup(source_path, dest_path_default, uid, gid, multi_user, userid,
AID_SDCARD_RW, 0006, derive_gid, default_normal, use_esdfs) ||
!sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_read, uid, gid,
multi_user, userid, AID_EVERYBODY, 0027, derive_gid,
default_normal, use_esdfs) ||
!sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_write, uid, gid,
multi_user, userid, AID_EVERYBODY, full_write ? 0007 : 0027,
derive_gid, default_normal, use_esdfs)) {
LOG(FATAL) << "failed to sdcardfs_setup";
}
} else {
// Physical storage is readable by all users on device, but
// the Android directories are masked off to a single user
// deep inside attr_from_stat().
if (!sdcardfs_setup(source_path, dest_path_default, uid, gid, multi_user, userid,
AID_SDCARD_RW, 0006, derive_gid, default_normal, use_esdfs) ||
!sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_read, uid, gid,
multi_user, userid, AID_EVERYBODY, full_write ? 0027 : 0022,
derive_gid, default_normal, use_esdfs) ||
!sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_write, uid, gid,
multi_user, userid, AID_EVERYBODY, full_write ? 0007 : 0022,
derive_gid, default_normal, use_esdfs)) {
LOG(FATAL) << "failed to sdcardfs_setup";
}
}
ここでは、gidがAID_であるmount sdcardのすべてのパラメータを指定します.EVERYBODY、これは上のmount情報に対応しています.
また、full_によりwriteは、対応するノードに権限を書き込むかどうかを確認します.PublicVolume.domount()関数は、外付けsdcardに対して-wパラメータが入力されていないことを知っています.つまり、ここのfull_writeはfalseであり、/mnt/runtime/write/labelに対するumaskは0022、すなわちgroupとotherにw権限が与えられていないことがコードによって発見され、これが最終的にEACCESが現れる根本的な場所となった.
Androidは外付けのsdcardを書かせないと思っているのか分からない.それともAndroidが存在するバグなのでしょうか?
これはしばらく分かりませんが、次のバージョンが出てきてgoogleが相応の変更があるかどうかを確認するのを待っています.
もちろん最初に言ったPermission deniedの問題を修正したいなら、ここのumaskを修正すれば間違いありません!
ここで疑問に思うのは、なぜAndroid 8.0の時にsdcardの読み書き権限を外付けしていないのかという問題です.
この問題については、以下で詳しく説明しますが、その前にstorageに対するgidがどのように来たのかを見てみましょう!!
Storageのgidと権限制御
アプリケーションを起動すると、zygoteを通じてプロセスを再forkすることを知っています.
ソース解析-AndroidのZygoteプロセスがどのようにアプリプロセスをforkするかという文から、zygote forkプロセスのプロセス全体を知っていますが、AMSでは関数startProcessLockedを通じて入り、変数gids、mountExternalはzygote forkプロセスまで関数に従います.
ソースコードを見てみましょう
private final boolean startProcessLocked(ProcessRecord app, String hostingType,
String hostingNameStr, boolean disableHiddenApiChecks, String abiOverride) {
...
...
int uid = app.uid;
int[] gids = null;
int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
if (!app.isolated) {
int[] permGids = null;
try {
checkTime(startTime, "startProcess: getting gids from package manager");
final IPackageManager pm = AppGlobals.getPackageManager();
permGids = pm.getPackageGids(app.info.packageName,
MATCH_DEBUG_TRIAGED_MISSING, app.userId);
StorageManagerInternal storageManagerInternal = LocalServices.getService(
StorageManagerInternal.class);
mountExternal = storageManagerInternal.getExternalStorageMountMode(uid,
app.info.packageName);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
...
...
ここでgidはPMS解析により取得され,アプリケーションで使用される権限に関連するgroup idはすべてここにある.
mountExternalは、以下のような外部デバイスmountタイプを取得します.
/** No external storage should be mounted. */
public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
/** Default external storage should be mounted. */
public static final int MOUNT_EXTERNAL_DEFAULT = IVold.REMOUNT_MODE_DEFAULT;
/** Read-only external storage should be mounted. */
public static final int MOUNT_EXTERNAL_READ = IVold.REMOUNT_MODE_READ;
/** Read-write external storage should be mounted. */
public static final int MOUNT_EXTERNAL_WRITE = IVold.REMOUNT_MODE_WRITE;
mountExternal
まず、getExternalStorageMountMode()を見てみましょう.
@Override
public int getExternalStorageMountMode(int uid, String packageName) {
// No locking - CopyOnWriteArrayList
int mountMode = Integer.MAX_VALUE;
for (ExternalStorageMountPolicy policy : mPolicies) {
final int policyMode = policy.getMountMode(uid, packageName);
if (policyMode == Zygote.MOUNT_EXTERNAL_NONE) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
mountMode = Math.min(mountMode, policyMode);
}
if (mountMode == Integer.MAX_VALUE) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
return mountMode;
}
登録されたExternalStorageMountPolicyを1つずつクエリーし、最小のmodeを取得することが最後に必要です.クエリの関数はgetMountMode()であり,アプリケーションに登録されているPolicyについてはPMSで詳細はPMS.を参照する.systemReady():
StorageManagerInternal StorageManagerInternal = LocalServices.getService(
StorageManagerInternal.class);
StorageManagerInternal.addExternalStoragePolicy(
new StorageManagerInternal.ExternalStorageMountPolicy() {
@Override
public int getMountMode(int uid, String packageName) {
if (Process.isIsolated(uid)) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
return Zygote.MOUNT_EXTERNAL_DEFAULT;
}
if (checkUidPermission(WRITE_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
return Zygote.MOUNT_EXTERNAL_READ;
}
return Zygote.MOUNT_EXTERNAL_WRITE;
}
@Override
public boolean hasExternalStorage(int uid, String packageName) {
return true;
}
});
ここがstorage権限を扱う場所で、READ_EXTERNAL_STORAGE権限が申請されていない場合、デフォルトmountのmodeはMOUNT_です.EXTERNAL_DEFAULT(sdcard_rwなどの特殊な権限が必要).READだけあげたらEXTERNAL_STORAGE、WRITEにあげなかったEXTERNAL_STORAGE,mount modeはMOUNT_EXTERNAL_READ(読取り専用権限);両方の権限が申請された場合、mount modeはMOUNT_です.EXTERNAL_WRITE(読み書き権限).mount modeは以下に説明を続けます.
startViaZygote()
ソース解析-AndroidのZygoteプロセスがどのようにアプリプロセスをforkしているかの一文から、AMS.startProcessLocked()以降もAMSを呼び出し続ける.startProcess()に続いてZygoteProcess.start()は、最終的にZygoteProcessを呼び出す.startViaZygote():
private Process.ProcessStartResult startViaZygote(final String processClass,
final String niceName,
final int uid, final int gid,
final int[] gids,
int runtimeFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String abi,
String instructionSet,
String appDataDir,
String invokeWith,
boolean startChildZygote,
String[] extraArgs)
throws ZygoteStartFailedEx {
ArrayList argsForZygote = new ArrayList();
// --runtime-args, --setuid=, --setgid=,
// and --setgroups= must go first
argsForZygote.add("--runtime-args");
argsForZygote.add("--setuid=" + uid);
argsForZygote.add("--setgid=" + gid);
argsForZygote.add("--runtime-flags=" + runtimeFlags);
if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
argsForZygote.add("--mount-external-default");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
argsForZygote.add("--mount-external-read");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
argsForZygote.add("--mount-external-write");
}
argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
// --setgroups is a comma-separated list
if (gids != null && gids.length > 0) {
StringBuilder sb = new StringBuilder();
sb.append("--setgroups=");
int sz = gids.length;
for (int i = 0; i < sz; i++) {
if (i != 0) {
sb.append(',');
}
sb.append(gids[i]);
}
argsForZygote.add(sb.toString());
}
...
...
synchronized(mLock) {
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}
}
異なるmount modeから異なるパラメータが入力され、最終的にzygoteに入力されてforkが行われる.
ソースコード解析-AndroidのZygoteプロセスがどのようにアプリプロセスをforkするかの一文から、最後にzygoteを呼び出すことを知った.forkAndSpecialize():
public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir) {
VM_HOOKS.preFork();
// Resets nice priority for zygote process.
resetNicePriority();
int pid = nativeForkAndSpecialize(
uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
fdsToIgnore, startChildZygote, instructionSet, appDataDir);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
Trace.setTracingEnabled(true, runtimeFlags);
// Note that this event ends at the end of handleChildProc,
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
}
VM_HOOKS.postForkCommon();
return pid;
}
ここでnativeForkAndSpecialize()は最後にJNIに呼び出されますcom_android_internal_os_Zygote.cppで、関数MountEmulatedStorage()をトリガーします.
static bool MountEmulatedStorage(uid_t uid, jint mount_mode,
bool force_mount_namespace, std::string* error_msg) {
// See storage config details at http://source.android.com/tech/storage/
String8 storageSource;
if (mount_mode == MOUNT_EXTERNAL_DEFAULT) {
storageSource = "/mnt/runtime/default";
} else if (mount_mode == MOUNT_EXTERNAL_READ) {
storageSource = "/mnt/runtime/read";
} else if (mount_mode == MOUNT_EXTERNAL_WRITE) {
storageSource = "/mnt/runtime/write";
} else if (!force_mount_namespace) {
// Sane default of no storage visible
return true;
}
// Create a second private mount namespace for our process
if (unshare(CLONE_NEWNS) == -1) {
*error_msg = CREATE_ERROR("Failed to unshare(): %s", strerror(errno));
return false;
}
// Handle force_mount_namespace with MOUNT_EXTERNAL_NONE.
if (mount_mode == MOUNT_EXTERNAL_NONE) {
return true;
}
if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",
NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
*error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
storageSource.string(),
strerror(errno));
return false;
}
// Mount user-specific symlink helper into place
userid_t user_id = multiuser_get_user_id(uid);
const String8 userSource(String8::format("/mnt/user/%d", user_id));
if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
*error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string());
return false;
}
if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self",
NULL, MS_BIND, NULL)) == -1) {
*error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s",
userSource.string(),
strerror(errno));
return false;
}
return true;
}
PMSで取り上げたmount modeはここのmount_mode,すなわち全過程で伝達されるmountExternal変数である.
ここでは2つのことをしました.
Android 8.0との違い
8.0で外部設定sdcardにアクセスするには、前の権限を追加する必要があります.
この権限を申請するアプリケーションはgroup idにmediaを追加します.rwとsdcard_rw.
詳細はframeworks/base/data/etc/platform.xml
Android 9.0ではsdcard_rwこのグループは削除します.
PMSでExternalStorageMountPolicyを登録した場所:
StorageManagerInternal StorageManagerInternal = LocalServices.getService(
StorageManagerInternal.class);
StorageManagerInternal.addExternalStoragePolicy(
new StorageManagerInternal.ExternalStorageMountPolicy() {
@Override
public int getMountMode(int uid, String packageName) {
if (Process.isIsolated(uid)) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
if (checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED) {
return Zygote.MOUNT_EXTERNAL_DEFAULT;
}
if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
return Zygote.MOUNT_EXTERNAL_DEFAULT;
}
if (checkUidPermission(WRITE_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
return Zygote.MOUNT_EXTERNAL_READ;
}
return Zygote.MOUNT_EXTERNAL_WRITE;
}
@Override
public boolean hasExternalStorage(int uid, String packageName) {
return true;
}
});
デフォルトでWRITE_が申請されましたMEDIA_STORAGE権限後、mount modeをMOUNT_に設定EXTERNAL_DEFAULTでは、zygoteでもmountから/mnt/runtime/defaultがデフォルトで、このノードのgroup idはsdcard_rw、すなわちsdcard_rwグループ内のアプリケーションは,いずれもこのノードで読み書きが可能である.
ただし、Android 9.0ではREAD_のみEXTERNAL_STORAGEとWRITE_EXTERNAL_STORAGEの2つの権限が処理されました.
そのため、同じようにこれらの権限を申請したアプリケーションがAndroid 8.0で読み書きができるのに、Android 9.0で問題が発生したのです.
プロセス情報の表示
方法1:
例えば、ファイルブラウザにmediaがあるかどうかを見たいです.rwの権限、私達はまずpsを見て、ファイルブラウザのpidを見つけます
u0_a31 6653 217 702776 60112 SyS_epoll_ b6d21408 S com.android.fileexplorer
root 6681 1 786596 26748 futex_wait b6d065ec S app_process
root 6683 1 786596 26700 futex_wait b6ca85ec S app_process
root 6685 1 786596 26724 futex_wait b6d185ec S app_process
それからproc/pidの下を見て、ここならproc/6653で、cat status:
root@lte26007:/proc/6653 # cat status
cat status
Name: id.fileexplorer
State: S (sleeping)
Tgid: 6653
Pid: 6653
PPid: 217
TracerPid: 0
Uid: 10031 10031 10031 10031
Gid: 10031 10031 10031 10031
FDSize: 256
Groups: 1015 1023 9997 50031
VmPeak: 991756 kB
VmSize: 702776 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 60640 kB
VmRSS: 60112 kB
Groupsがあるのを見ましたrwは1023のはずです
idコマンドを使用して確認できます.
id media_rw
uid=1023(media_rw) gid=1023(media_rw) groups=1023(media_rw), context=u:r:su:s0
確かに1023です.これにより、ファイルブラウザアプリケーションにmediaがあることが確認されます.rwの権限
msm8940_EVB:/mnt/media_rw # ls -l
total 32
drwxrwx--- 9 media_rw media_rw 32768 2018-12-29 03:53 6344-0FEF
方法2:
私たちは/system/etc/permissionsディレクトリのplatformに行くことができます.xml表示media_rw対応権限
それからファイルブラウザのソースコードのAndroidManifestに行きます.xmlファイルは、次のコードで、その権限があることを知っています.
まとめ:
storageの読み書き操作を正常に使用するには、いくつかのステップに注意する必要があります.
1、対応するstorage権限の申請
アプリケーションのAndroidManifestでxmlにstorage権限を登録し、grantすることができます.
2、アプリケーションが正しいmount modeを取得したことを確定する
現在では1点目と同じですが、後で権限を追加する可能性があります.例えば、前のWRITE_MEDIA_STORAGE
3、mountのノードのユーザー、ユーザーグループ権限を確定する
ディレクトリ/mnt/runtime/readまたは/mnt/runtime/writeまたは/mnt/runtime/defaultにアクセスして、対応するファイル操作権限を確認します.
たとえば、mount modeは/mnt/runtime/writeノードを明確に指していますが、ユーザーグループがw権限を与えていないため、Permission deniedエラーが発生します.
Android 9.0の場合、最初はmount/mnt/runtime/*がsdcard.cppではmount時のumaskを修正すればよい.
もう一つの博文論Android 9.0外付けsdcard読み書きは、プリセットアプリケーションを深く掘り起こし、議論している.