| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
| * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
| * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include "grp_pwd_file.h" |
| |
| #include <fcntl.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| |
| #include <async_safe/log.h> |
| |
| #include "private/ErrnoRestorer.h" |
| |
| // This file mmap's /*/etc/passwd and /*/etc/group in order to return their contents without any |
| // allocations. Note that these files and the strings contained within them are explicitly not |
| // null-terminated. ':'s are used to deliminate fields and '\n's are used to deliminate lines. |
| // There is a check that the file ends with '\n', such that terminating loops at '\n' ensures that |
| // memory will be not read beyond the mmap region. |
| |
| namespace { |
| |
| void CopyFieldToString(char* dest, const char* source, size_t max) { |
| while (*source != ':' && *source != '\n' && max > 1) { |
| *dest++ = *source++; |
| --max; |
| } |
| *dest = '\0'; |
| } |
| |
| bool FieldToUid(const char* field, uid_t* uid) { |
| if (field == nullptr) { |
| return false; |
| } |
| |
| char* end = nullptr; |
| errno = 0; |
| uid_t result = strtoul(field, &end, 0); |
| if (errno != 0 || field == end || *end != ':') { |
| return false; |
| } |
| *uid = result; |
| return true; |
| } |
| |
| // Returns a pointer to one past the end of line. |
| const char* ParseLine(const char* begin, const char* end, const char** fields, size_t num_fields) { |
| size_t fields_written = 0; |
| const char* position = begin; |
| fields[fields_written++] = position; |
| |
| while (position < end && fields_written < num_fields) { |
| if (*position == '\n') { |
| return position + 1; |
| } |
| if (*position == ':') { |
| fields[fields_written++] = position + 1; |
| } |
| position++; |
| } |
| |
| while (position < end && *position != '\n') { |
| position++; |
| } |
| |
| return position + 1; |
| } |
| |
| struct PasswdLine { |
| const char* name() const { |
| return fields[0]; |
| } |
| // Password is not supported. |
| const char* uid() const { |
| return fields[2]; |
| } |
| const char* gid() const { |
| return fields[3]; |
| } |
| // User Info is not supported |
| const char* dir() const { |
| return fields[5]; |
| } |
| const char* shell() const { |
| return fields[6]; |
| } |
| |
| bool ToPasswdState(passwd_state_t* passwd_state) { |
| if (name() == nullptr || dir() == nullptr || shell() == nullptr) { |
| return false; |
| } |
| |
| uid_t uid; |
| if (!FieldToUid(this->uid(), &uid)) { |
| return false; |
| } |
| |
| gid_t gid; |
| if (!FieldToUid(this->gid(), &gid)) { |
| return false; |
| } |
| |
| passwd_state->passwd_.pw_uid = uid; |
| passwd_state->passwd_.pw_gid = gid; |
| |
| CopyFieldToString(passwd_state->name_buffer_, name(), sizeof(passwd_state->name_buffer_)); |
| passwd_state->passwd_.pw_name = passwd_state->name_buffer_; |
| |
| passwd_state->passwd_.pw_passwd = nullptr; |
| |
| #ifdef __LP64__ |
| passwd_state->passwd_.pw_gecos = nullptr; |
| #endif |
| |
| CopyFieldToString(passwd_state->dir_buffer_, dir(), sizeof(passwd_state->dir_buffer_)); |
| passwd_state->passwd_.pw_dir = passwd_state->dir_buffer_; |
| |
| CopyFieldToString(passwd_state->sh_buffer_, shell(), sizeof(passwd_state->sh_buffer_)); |
| passwd_state->passwd_.pw_shell = passwd_state->sh_buffer_; |
| |
| return true; |
| } |
| |
| static constexpr size_t kNumFields = 7; |
| const char* fields[kNumFields] = {}; |
| }; |
| |
| struct GroupLine { |
| const char* name() const { |
| return fields[0]; |
| } |
| // Password is not supported. |
| const char* gid() const { |
| return fields[2]; |
| } |
| // User list is not supported (returns simply name) |
| |
| bool ToGroupState(group_state_t* group_state) { |
| if (name() == nullptr || gid() == nullptr) { |
| return false; |
| } |
| |
| gid_t gid; |
| if (!FieldToUid(this->gid(), &gid)) { |
| return false; |
| } |
| |
| group_state->group_.gr_gid = gid; |
| |
| CopyFieldToString(group_state->group_name_buffer_, name(), |
| sizeof(group_state->group_name_buffer_)); |
| group_state->group_.gr_name = group_state->group_name_buffer_; |
| |
| group_state->group_.gr_passwd = nullptr; |
| |
| group_state->group_.gr_mem = group_state->group_members_; |
| group_state->group_.gr_mem[0] = group_state->group_.gr_name; |
| group_state->group_.gr_mem[1] = nullptr; |
| |
| return true; |
| } |
| |
| static constexpr size_t kNumFields = 4; |
| const char* fields[kNumFields] = {}; |
| }; |
| |
| } // namespace |
| |
| MmapFile::MmapFile(const char* filename, const char* required_prefix) |
| : filename_(filename), required_prefix_(required_prefix) { |
| lock_.init(false); |
| } |
| |
| void MmapFile::Unmap() { |
| if (status_ == FileStatus::Initialized) { |
| size_t size = end_ - start_ + 1; |
| munmap(const_cast<char*>(start_), size); |
| status_ = FileStatus::Uninitialized; |
| start_ = nullptr; |
| end_ = nullptr; |
| } |
| } |
| |
| bool MmapFile::GetFile(const char** start, const char** end) { |
| LockGuard guard(lock_); |
| if (status_ == FileStatus::Initialized) { |
| *start = start_; |
| *end = end_; |
| return true; |
| } |
| if (status_ == FileStatus::Error) { |
| return false; |
| } |
| |
| if (!DoMmap()) { |
| status_ = FileStatus::Error; |
| return false; |
| } |
| |
| status_ = FileStatus::Initialized; |
| *start = start_; |
| *end = end_; |
| return true; |
| } |
| |
| bool MmapFile::DoMmap() { |
| int fd = open(filename_, O_CLOEXEC | O_NOFOLLOW | O_RDONLY); |
| |
| struct stat fd_stat; |
| if (fstat(fd, &fd_stat) == -1) { |
| close(fd); |
| return false; |
| } |
| |
| auto mmap_size = fd_stat.st_size; |
| |
| void* map_result = mmap(nullptr, mmap_size, PROT_READ, MAP_SHARED, fd, 0); |
| close(fd); |
| |
| if (map_result == MAP_FAILED) { |
| return false; |
| } |
| |
| start_ = static_cast<const char*>(map_result); |
| end_ = start_ + mmap_size - 1; |
| |
| if (*end_ != '\n') { |
| munmap(map_result, mmap_size); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| template <typename Line, typename Predicate> |
| bool MmapFile::Find(Line* line, Predicate predicate) { |
| const char* start; |
| const char* end; |
| if (!GetFile(&start, &end)) { |
| return false; |
| } |
| |
| const char* line_beginning = start; |
| |
| while (line_beginning < end) { |
| line_beginning = ParseLine(line_beginning, end, line->fields, line->kNumFields); |
| // To comply with Treble, users/groups from each partition need to be prefixed with |
| // the partition name. |
| if (required_prefix_ != nullptr) { |
| if (strncmp(line->fields[0], required_prefix_, strlen(required_prefix_)) != 0) { |
| char name[kGrpPwdBufferSize]; |
| CopyFieldToString(name, line->fields[0], sizeof(name)); |
| async_safe_format_log(ANDROID_LOG_ERROR, "libc", |
| "Found user/group name '%s' in '%s' without required prefix '%s'", |
| name, filename_, required_prefix_); |
| continue; |
| } |
| } |
| if (predicate(line)) return true; |
| } |
| |
| return false; |
| } |
| |
| template <typename Line> |
| bool MmapFile::FindById(uid_t uid, Line* line) { |
| return Find(line, [uid](const auto& line) { |
| uid_t line_id; |
| if (!FieldToUid(line->fields[2], &line_id)) { |
| return false; |
| } |
| |
| return line_id == uid; |
| }); |
| } |
| |
| template <typename Line> |
| bool MmapFile::FindByName(const char* name, Line* line) { |
| return Find(line, [name](const auto& line) { |
| const char* line_name = line->fields[0]; |
| if (line_name == nullptr) { |
| return false; |
| } |
| |
| const char* match_name = name; |
| while (*line_name != '\n' && *line_name != ':' && *match_name != '\0') { |
| if (*line_name++ != *match_name++) { |
| return false; |
| } |
| } |
| |
| return *line_name == ':' && *match_name == '\0'; |
| }); |
| } |
| |
| PasswdFile::PasswdFile(const char* filename, const char* required_prefix) |
| : mmap_file_(filename, required_prefix) { |
| } |
| |
| bool PasswdFile::FindById(uid_t id, passwd_state_t* passwd_state) { |
| ErrnoRestorer errno_restorer; |
| PasswdLine passwd_line; |
| return mmap_file_.FindById(id, &passwd_line) && passwd_line.ToPasswdState(passwd_state); |
| } |
| |
| bool PasswdFile::FindByName(const char* name, passwd_state_t* passwd_state) { |
| ErrnoRestorer errno_restorer; |
| PasswdLine passwd_line; |
| return mmap_file_.FindByName(name, &passwd_line) && passwd_line.ToPasswdState(passwd_state); |
| } |
| |
| GroupFile::GroupFile(const char* filename, const char* required_prefix) |
| : mmap_file_(filename, required_prefix) { |
| } |
| |
| bool GroupFile::FindById(gid_t id, group_state_t* group_state) { |
| ErrnoRestorer errno_restorer; |
| GroupLine group_line; |
| return mmap_file_.FindById(id, &group_line) && group_line.ToGroupState(group_state); |
| } |
| |
| bool GroupFile::FindByName(const char* name, group_state_t* group_state) { |
| ErrnoRestorer errno_restorer; |
| GroupLine group_line; |
| return mmap_file_.FindByName(name, &group_line) && group_line.ToGroupState(group_state); |
| } |