1 /*
2  * Copyright (C) 2023 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include "acl.h"
16 
17 #include <cerrno>
18 #include <dirent.h>
19 #include <list>
20 #include <new>
21 #include <type_traits>
22 #include <sys/stat.h>
23 #include <sys/xattr.h>
24 
25 #include "medialibrary_errno.h"
26 #include "media_log.h"
27 #include "securec.h"
28 
29 namespace OHOS {
30 namespace Media {
31 
32 const std::map<ACL_TAG, const char *> ACL_TAG_STR = {
33     { ACL_TAG::UNDEFINED, "ACL_UNDEFINED_TAG" },
34     { ACL_TAG::USER_OBJ,  "ACL_USER_OBJ" },
35     { ACL_TAG::USER,      "ACL_USER" },
36     { ACL_TAG::GROUP_OBJ, "ACL_GROUP_OBJ" },
37     { ACL_TAG::GROUP,     "ACL_GROUP" },
38     { ACL_TAG::MASK,      "ACL_MASK" },
39     { ACL_TAG::OTHER,     "ACL_OTHER" },
40 };
41 
42 constexpr int BUF_SIZE = 400;
43 
PrintACLDetail(const std::string & path,const char * aclAttrName)44 void PrintACLDetail(const std::string& path, const char* aclAttrName)
45 {
46     char *buf = nullptr;
47     ssize_t len = getxattr(path.c_str(), aclAttrName, nullptr, 0);
48     if (len > 0) {
49         buf = new (std::nothrow) char[len]{};
50         if (buf == nullptr) {
51             MEDIA_ERR_LOG("Memory allocation fails");
52             return;
53         }
54         len = getxattr(path.c_str(), aclAttrName, buf, len);
55     }
56     if (len == -1) {
57         MEDIA_ERR_LOG("getxattr error");
58         if (buf != nullptr) {
59             delete[] buf;
60             buf = nullptr;
61         }
62         return;
63     }
64 
65     Acl acl;
66     acl.DeSerialize(buf, len);
67     acl.Print(path);
68 
69     if (buf != nullptr) {
70         delete[] buf;
71         buf = nullptr;
72     }
73 }
74 
ReCalcMaskPerm()75 ACL_PERM Acl::ReCalcMaskPerm()
76 {
77     ACL_PERM perm;
78     for (const auto &e : entries) {
79         if (e.tag == ACL_TAG::USER || e.tag == ACL_TAG::GROUP_OBJ || e.tag == ACL_TAG::GROUP) {
80             perm.Merge(e.perm);
81         }
82     }
83     return perm;
84 }
85 
IsEmpty()86 bool Acl::IsEmpty()
87 {
88     return entries.empty();
89 }
90 
IsValid()91 bool Acl::IsValid()
92 {
93     if (!entries.count(ACL_TAG::USER_OBJ) || !entries.count(ACL_TAG::GROUP_OBJ) ||
94             !entries.count(ACL_TAG::OTHER)) {
95         return false;
96     }
97     if (maskDemand && !entries.count(ACL_TAG::MASK)) {
98         return false;
99     }
100     return true;
101 }
102 
CompareInsertEntry(const AclXattrEntry & entry)103 void Acl::CompareInsertEntry(const AclXattrEntry &entry)
104 {
105     if (entries.count(entry)) {
106         auto it = entries.find(entry);
107         entries.erase(it);
108     }
109     if (entry.perm.IsReadable() || entry.perm.IsWritable() || entry.perm.IsExecutable()) {
110         entries.insert(entry);
111     }
112 }
113 
InsertEntry(const AclXattrEntry & entry)114 int Acl::InsertEntry(const AclXattrEntry &entry)
115 {
116     if (entries.size() >= ENTRIES_MAX_NUM) {
117         errno = EAGAIN;
118         return E_ERR;
119     }
120     CompareInsertEntry(entry); // must before ReCalcMaskPerm()
121 
122     maskDemand++;
123     /*
124         * In either case there's no or already one ACL_MASK entry in the set,
125         * we need to re-calculate MASK's permission and *insert* it (to replace
126         * the old one in latter case since we can't change std::set's element
127         * in-place). So do the following unconditionally.
128         *
129         * Be warned: do _NOT_ combine the following into one line, otherwise
130         * you can't pass the !!genius!! CI coding style check.
131         */
132     CompareInsertEntry(
133         { ACL_TAG::MASK, ReCalcMaskPerm(), ACL_UNDEFINED_ID }
134     );
135     return E_OK;
136 }
137 
Serialize(size_t & bufSize)138 char *Acl::Serialize(size_t &bufSize)
139 {
140     if (!IsValid()) {
141         errno = EINVAL;
142         return nullptr;
143     }
144 
145     /* clear possible previous allocation */
146     if (buf != nullptr) {
147         delete[] buf;
148         buf = nullptr;
149     }
150 
151     bufSize = sizeof(AclXattrHeader) + sizeof(AclXattrEntry) * entries.size();
152     if (bufSize > BUF_MAX_SIZE) {
153         bufSize = 0;
154         errno = EINVAL;
155         return nullptr;
156     }
157     buf = new (std::nothrow) char[bufSize];
158     if (buf == nullptr) {
159         errno = ENOMEM;
160         return nullptr;
161     }
162     auto err = memcpy_s(buf, bufSize, &header, sizeof(AclXattrHeader));
163     if (err != EOK) {
164         errno = err;
165         delete[] buf;
166         buf = nullptr;
167         return nullptr;
168     }
169 
170     size_t restSize = bufSize - sizeof(AclXattrHeader);
171     AclXattrEntry *ptr = reinterpret_cast<AclXattrEntry*>(buf + sizeof(AclXattrHeader));
172     for (const auto &e : entries) {
173         auto err = memcpy_s(ptr++, restSize, &e, sizeof(AclXattrEntry));
174         if (err != EOK) {
175             errno = err;
176             delete[] buf;
177             buf = nullptr;
178             return nullptr;
179         }
180         restSize -= sizeof(AclXattrEntry);
181     }
182     return buf;
183 }
184 
DeSerialize(const char * aclHead,size_t size)185 int Acl::DeSerialize(const char* aclHead, size_t size)
186 {
187     if (size > BUF_MAX_SIZE || size < sizeof(AclXattrHeader)) {
188         errno = EINVAL;
189         return errno;
190     }
191     header = *reinterpret_cast<const AclXattrHeader*>(aclHead);
192     size -= sizeof(AclXattrHeader);
193     aclHead += sizeof(AclXattrHeader);
194 
195     /*
196      * `entry->tag != ACL_TAG::UNDEFINED` is unreliable outside the buffer, so check
197      * it after checking the size of remaining buffer.
198      */
199     for (const AclXattrEntry *entry = reinterpret_cast<const AclXattrEntry*>(aclHead);
200         size >= sizeof(AclXattrEntry) && entry->tag != ACL_TAG::UNDEFINED; entry++) {
201         InsertEntry(*entry);
202         size -= sizeof(AclXattrEntry);
203     }
204     if (size < 0) {
205         entries.clear();
206         header = { 0 };
207         errno = EINVAL;
208         return errno;
209     }
210 
211     return E_OK;
212 }
213 
AclFromMode(const std::string & file)214 Acl AclFromMode(const std::string &file)
215 {
216     Acl acl;
217     struct stat st;
218 
219     if (stat(file.c_str(), &st) == -1) {
220         return acl;
221     }
222 
223     acl.InsertEntry(
224         {
225         .tag = ACL_TAG::USER_OBJ,
226         .perm = (st.st_mode & S_IRWXU) >> 6,
227         .id = ACL_UNDEFINED_ID,
228         }
229     );
230     acl.InsertEntry(
231         {
232         .tag = ACL_TAG::GROUP_OBJ,
233         .perm = (st.st_mode & S_IRWXG) >> 3,
234         .id = ACL_UNDEFINED_ID,
235         }
236     );
237     acl.InsertEntry(
238         {
239         .tag = ACL_TAG::OTHER,
240         .perm = (st.st_mode & S_IRWXO),
241         .id = ACL_UNDEFINED_ID,
242         }
243     );
244 
245     return acl;
246 }
247 
InitSandboxEntry(AclXattrEntry & entry)248 void InitSandboxEntry(AclXattrEntry &entry)
249 {
250     entry.tag = ACL_TAG::GROUP;
251     entry.id = THUMB_ACL_GROUP;
252     entry.perm.SetRead();
253     entry.perm.SetExecute();
254 }
255 
InitSandboxGroupEntry(AclXattrEntry & entry,uint32_t id,uint16_t access)256 void InitSandboxGroupEntry(AclXattrEntry& entry, uint32_t id, uint16_t access)
257 {
258     entry.tag = ACL_TAG::GROUP;
259     entry.id = id;
260     if (access & ACL_PERM::Value::READ) {
261         entry.perm.SetRead();
262     }
263     if (access & ACL_PERM::Value::WRITE) {
264         entry.perm.SetWrite();
265     }
266     if (access & ACL_PERM::Value::EXECUTE) {
267         entry.perm.SetExecute();
268     }
269 }
270 
AclSetDefault()271 int32_t Acl::AclSetDefault()
272 {
273     if (EnableAcl(THUMB_DIR, ACL_XATTR_DEFAULT, ACL_PERM::Value::READ | ACL_PERM::Value::EXECUTE,
274         MEDIA_DB_ACL_GROUP) != E_OK) {
275         MEDIA_ERR_LOG("Failed to set the acl permission for the Photo dir");
276         return E_ERR;
277     }
278     return E_OK;
279 }
280 
RecursiveEnableAcl(const std::string & path,const char * aclAttrName,const uint16_t & permission,uint32_t groupId)281 int32_t Acl::RecursiveEnableAcl(const std::string& path, const char* aclAttrName, const uint16_t& permission,
282     uint32_t groupId)
283 {
284     DIR* fileDir;
285     struct dirent* dirEntry;
286     struct stat st;
287     std::list<std::string> dirPathList{path};
288     int32_t result = E_OK;
289     while (!dirPathList.empty()) {
290         std::string dir = dirPathList.back();
291         dirPathList.pop_back();
292         if ((fileDir = opendir(dir.c_str())) == nullptr) {
293             MEDIA_ERR_LOG("dir not exist: %{private}s, error: %s", dir.c_str(), strerror(errno));
294             result = E_ERR;
295             continue;
296         }
297         while ((dirEntry = readdir(fileDir)) != nullptr) {
298             if ((strcmp(dirEntry->d_name, ".") == 0) || (strcmp(dirEntry->d_name, "..") == 0)) {
299                 continue;
300             }
301             std::string fileName = dir + "/" + dirEntry->d_name;
302             if (stat(fileName.c_str(), &st) != 0) {
303                 MEDIA_ERR_LOG("getting file: %{private}s stat fail, error: %s", fileName.c_str(), strerror(errno));
304                 result = E_ERR;
305                 continue;
306             }
307             if (st.st_mode & S_IFDIR) {
308                 dirPathList.push_front(fileName);
309             }
310             if (EnableAcl(fileName, aclAttrName, permission, groupId) != E_OK) {
311                 MEDIA_ERR_LOG("Failed to set the acl permission for the %{private}s", fileName.c_str());
312                 result = E_ERR;
313             } else {
314                 MEDIA_INFO_LOG("acl set succeed %{public}s", fileName.c_str());
315             }
316         }
317         closedir(fileDir);
318     }
319     return result;
320 }
321 
EnableAcl(const std::string & path,const char * aclAttrName,const uint16_t & permission,uint32_t groupId)322 int32_t Acl::EnableAcl(const std::string& path, const char* aclAttrName, const uint16_t& permission, uint32_t groupId)
323 {
324     AclXattrEntry entry = {};
325     InitSandboxGroupEntry(entry, groupId, permission);
326     int32_t err = EntryInsert(entry, path, aclAttrName);
327     if (err != E_OK) {
328         MEDIA_ERR_LOG("Failed to set the acl permission for path %{private}s", path.c_str());
329         return E_ERR;
330     }
331     return E_OK;
332 }
333 
AclSetDatabase()334 int32_t Acl::AclSetDatabase()
335 {
336     if (EnableAcl(MEDIA_DB_DIR, ACL_XATTR_ACCESS, ACL_PERM::Value::READ | ACL_PERM::Value::WRITE |
337         ACL_PERM::Value::EXECUTE, MEDIA_DB_ACL_GROUP) != E_OK) {
338         MEDIA_ERR_LOG("Failed to set the acl permission for the DB dir");
339         return E_ERR;
340     }
341     if (RecursiveEnableAcl(MEDIA_DB_DIR, ACL_XATTR_ACCESS, ACL_PERM::Value::READ | ACL_PERM::Value::WRITE |
342         ACL_PERM::Value::EXECUTE, MEDIA_DB_ACL_GROUP) != E_OK) {
343         MEDIA_ERR_LOG("Failed to set the acl permission for the DB sub dir");
344         return E_ERR;
345     }
346     return E_OK;
347 }
348 
IsDirExist(const std::string & path)349 bool IsDirExist(const std::string &path)
350 {
351     DIR *dir = opendir(path.c_str());
352     if (dir == nullptr) {
353         MEDIA_ERR_LOG("Failed to open dir:%{private}s, errno: %{public}d, Just return dir NOT empty.",
354             path.c_str(), errno);
355         return false;
356     }
357     if (closedir(dir) < 0) {
358         MEDIA_ERR_LOG("Failed to closedir: %{private}s, errno: %{public}d.", path.c_str(), errno);
359         return false;
360     }
361     return true;
362 }
363 
AclSetSlaveDatabase()364 int32_t Acl::AclSetSlaveDatabase()
365 {
366     if (!IsDirExist(MEDIA_DB_DIR)) {
367         MEDIA_ERR_LOG("Media database directory is not exist");
368         return E_ERR;
369     }
370 
371     if (EnableAcl(MEDIA_DB_FILE_SLAVE, ACL_XATTR_ACCESS, ACL_PERM::Value::READ | ACL_PERM::Value::WRITE |
372         ACL_PERM::Value::EXECUTE, DDMS_ACL_GROUP) != E_OK) {
373         MEDIA_ERR_LOG("Failed to set the acl permission for the DB file");
374         return E_ERR;
375     }
376     if (EnableAcl(MEDIA_DB_FILE_SLAVE_SHM, ACL_XATTR_ACCESS, ACL_PERM::Value::READ | ACL_PERM::Value::WRITE |
377         ACL_PERM::Value::EXECUTE, DDMS_ACL_GROUP) != E_OK) {
378         MEDIA_ERR_LOG("Failed to set the acl permission for the DB file");
379         return E_ERR;
380     }
381     if (EnableAcl(MEDIA_DB_FILE_SLAVE_WAL, ACL_XATTR_ACCESS, ACL_PERM::Value::READ | ACL_PERM::Value::WRITE |
382         ACL_PERM::Value::EXECUTE, DDMS_ACL_GROUP) != E_OK) {
383         MEDIA_ERR_LOG("Failed to set the acl permission for the DB file");
384         return E_ERR;
385     }
386 
387     return E_OK;
388 }
389 
AclFromFile(const std::string & file)390 Acl AclFromFile(const std::string& file)
391 {
392     Acl acl;
393     char buf[BUF_SIZE] = { 0 };
394     ssize_t len = getxattr(file.c_str(), ACL_XATTR_ACCESS, buf, BUF_SIZE);
395     if (len != -1) {
396         acl.DeSerialize(buf, BUF_SIZE);
397         return acl;
398     }
399     return AclFromMode(file);
400 }
401 
EntryInsert(AclXattrEntry & entry,const std::string & path,const char * aclAttrName)402 int32_t Acl::EntryInsert(AclXattrEntry& entry, const std::string& path, const char* aclAttrName)
403 {
404     /* init acl from file's mode */
405     Acl acl;
406     if (strcmp(aclAttrName, ACL_XATTR_ACCESS) == 0) {
407         acl = AclFromFile(path);
408     } else {
409         acl = AclFromMode(path);
410     }
411     if (acl.IsEmpty()) {
412         MEDIA_ERR_LOG("Failed to generate ACL from file's mode: %{public}s", std::strerror(errno));
413         return E_ERR;
414     }
415 
416     /* add new entry into set */
417     if (acl.InsertEntry(entry) == E_ERR) {
418         MEDIA_ERR_LOG("Failed to insert new entry into ACL: %{public}s", std::strerror(errno));
419         return E_ERR;
420     }
421 
422     /* in case that this acl has no OTHER TAG and can't be serialized */
423     acl.InsertEntry(
424         {
425             .tag = ACL_TAG::OTHER,
426             .perm = S_IXOTH,
427             .id = ACL_UNDEFINED_ID,
428         }
429     );
430 
431     acl.InsertEntry(
432         {
433             .tag = ACL_TAG::GROUP_OBJ,
434             .perm = S_IRWXG >> 3,
435             .id = ACL_UNDEFINED_ID,
436         }
437     );
438 
439     /* transform to binary and write to file */
440     size_t bufSize;
441     char *buf = acl.Serialize(bufSize);
442     if (buf == nullptr) {
443         MEDIA_ERR_LOG("Failed to serialize ACL into binary: %{public}s", std::strerror(errno));
444         return E_ERR;
445     }
446     if (setxattr(path.c_str(), aclAttrName, buf, bufSize, 0) == -1) {
447         MEDIA_ERR_LOG("Failed to write into file's xattr: %{public}s", std::strerror(errno));
448         return E_ERR;
449     }
450     return E_OK;
451 }
452 
Print(const std::string & path)453 void Acl::Print(const std::string& path)
454 {
455     MEDIA_DEBUG_LOG("Version: %#x, path: %{private}s\n", header.version, path.c_str());
456     for (const auto &e: entries) {
457         MEDIA_DEBUG_LOG("---------------ACL ATTR---------------\n"
458             "tag:  %s\n"
459             "perm: %hx\n"
460             "id:   %#x (%u)\n",
461             ACL_TAG_STR.at(e.tag), (uint16_t)e.perm, e.id, e.id);
462     }
463 }
464 
~Acl()465 Acl::~Acl()
466 {
467     if (buf != nullptr) {
468         delete[] buf;
469         buf = nullptr;
470     }
471 }
472 } // MEDIA
473 } // OHOS
474