| /* |
| * 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" |
| #include"private/ScopedFd.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{ |
| |
| voidCopyFieldToString(char* dest,constchar* source,size_t max){ |
| while(*source!=':'&&*source!='\n'&& max>1){ |
| *dest++=*source++; |
| --max; |
| } |
| *dest='\0'; |
| } |
| |
| boolFieldToUid(constchar* field,uid_t* uid){ |
| if(field==nullptr){ |
| returnfalse; |
| } |
| |
| char* end=nullptr; |
| errno=0; |
| uid_t result= strtoul(field,&end,0); |
| if(errno!=0|| field== end||*end!=':'){ |
| returnfalse; |
| } |
| *uid= result; |
| returntrue; |
| } |
| |
| // Returns a pointer to one past the end of line. |
| constchar*ParseLine(constchar* begin,constchar* end,constchar** fields,size_t num_fields){ |
| size_t fields_written=0; |
| constchar* 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; |
| } |
| |
| structPasswdLine{ |
| constchar* name()const{ |
| return fields[0]; |
| } |
| // Password is not supported. |
| constchar* uid()const{ |
| return fields[2]; |
| } |
| constchar* gid()const{ |
| return fields[3]; |
| } |
| // User Info is not supported |
| constchar* dir()const{ |
| return fields[5]; |
| } |
| constchar* shell()const{ |
| return fields[6]; |
| } |
| |
| boolToPasswdState(passwd_state_t* passwd_state){ |
| if(name()==nullptr|| dir()==nullptr|| shell()==nullptr){ |
| returnfalse; |
| } |
| |
| uid_t uid; |
| if(!FieldToUid(this->uid(),&uid)){ |
| returnfalse; |
| } |
| |
| gid_t gid; |
| if(!FieldToUid(this->gid(),&gid)){ |
| returnfalse; |
| } |
| |
| 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_; |
| |
| returntrue; |
| } |
| |
| staticconstexprsize_t kNumFields=7; |
| constchar* fields[kNumFields]={}; |
| }; |
| |
| structGroupLine{ |
| constchar* name()const{ |
| return fields[0]; |
| } |
| // Password is not supported. |
| constchar* gid()const{ |
| return fields[2]; |
| } |
| // User list is not supported (returns simply name) |
| |
| boolToGroupState(group_state_t* group_state){ |
| if(name()==nullptr|| gid()==nullptr){ |
| returnfalse; |
| } |
| |
| gid_t gid; |
| if(!FieldToUid(this->gid(),&gid)){ |
| returnfalse; |
| } |
| |
| 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; |
| |
| returntrue; |
| } |
| |
| staticconstexprsize_t kNumFields=4; |
| constchar* fields[kNumFields]={}; |
| }; |
| |
| }// namespace |
| |
| MmapFile::MmapFile(constchar* filename,constchar* required_prefix) |
| : filename_(filename), required_prefix_(required_prefix){ |
| lock_.init(false); |
| } |
| |
| voidMmapFile::Unmap(){ |
| if(status_==FileStatus::Initialized){ |
| size_t size= end_- start_+1; |
| munmap(const_cast<char*>(start_), size); |
| status_=FileStatus::Uninitialized; |
| start_=nullptr; |
| end_=nullptr; |
| } |
| } |
| |
| boolMmapFile::GetFile(constchar** start,constchar** end){ |
| LockGuard guard(lock_); |
| if(status_==FileStatus::Initialized){ |
| *start= start_; |
| *end= end_; |
| returntrue; |
| } |
| if(status_==FileStatus::Error){ |
| returnfalse; |
| } |
| |
| if(!DoMmap()){ |
| status_=FileStatus::Error; |
| returnfalse; |
| } |
| |
| status_=FileStatus::Initialized; |
| *start= start_; |
| *end= end_; |
| returntrue; |
| } |
| |
| boolMmapFile::DoMmap(){ |
| ScopedFd fd(open(filename_, O_CLOEXEC| O_NOFOLLOW| O_RDONLY)); |
| |
| struct stat fd_stat; |
| if(fstat(fd.get(),&fd_stat)==-1){ |
| returnfalse; |
| } |
| |
| auto mmap_size= fd_stat.st_size; |
| |
| void* map_result= mmap(nullptr, mmap_size, PROT_READ, MAP_SHARED, fd.get(),0); |
| if(map_result== MAP_FAILED){ |
| returnfalse; |
| } |
| |
| start_=static_cast<constchar*>(map_result); |
| end_= start_+ mmap_size-1; |
| |
| if(*end_!='\n'){ |
| munmap(map_result, mmap_size); |
| returnfalse; |
| } |
| |
| returntrue; |
| } |
| |
| template<typenameLine,typenamePredicate> |
| boolMmapFile::Find(Line* line,Predicate predicate){ |
| constchar* start; |
| constchar* end; |
| if(!GetFile(&start,&end)){ |
| returnfalse; |
| } |
| |
| constchar* line_beginning= start; |
| |
| while(line_beginning< end){ |
| line_beginning=ParseLine(line_beginning, end, line->fields, line->kNumFields); |
| #if defined(__ANDROID__) |
| // 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; |
| } |
| } |
| #endif |
| if(predicate(line))returntrue; |
| } |
| |
| returnfalse; |
| } |
| |
| template<typenameLine> |
| boolMmapFile::FindById(uid_t uid,Line* line){ |
| returnFind(line,[uid](constauto& line){ |
| uid_t line_id; |
| if(!FieldToUid(line->fields[2],&line_id)){ |
| returnfalse; |
| } |
| |
| return line_id== uid; |
| }); |
| } |
| |
| template<typenameLine> |
| boolMmapFile::FindByName(constchar* name,Line* line){ |
| returnFind(line,[name](constauto& line){ |
| constchar* line_name= line->fields[0]; |
| if(line_name==nullptr){ |
| returnfalse; |
| } |
| |
| constchar* match_name= name; |
| while(*line_name!='\n'&&*line_name!=':'&&*match_name!='\0'){ |
| if(*line_name++!=*match_name++){ |
| returnfalse; |
| } |
| } |
| |
| return*line_name==':'&&*match_name=='\0'; |
| }); |
| } |
| |
| PasswdFile::PasswdFile(constchar* filename,constchar* required_prefix) |
| : mmap_file_(filename, required_prefix){ |
| } |
| |
| boolPasswdFile::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); |
| } |
| |
| boolPasswdFile::FindByName(constchar* 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(constchar* filename,constchar* required_prefix) |
| : mmap_file_(filename, required_prefix){ |
| } |
| |
| boolGroupFile::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); |
| } |
| |
| boolGroupFile::FindByName(constchar* name,group_state_t* group_state){ |
| ErrnoRestorer errno_restorer; |
| GroupLine group_line; |
| return mmap_file_.FindByName(name,&group_line)&& group_line.ToGroupState(group_state); |
| } |