1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *  * Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  *  * Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in
12  *    the documentation and/or other materials provided with the
13  *    distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
22  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include "grp_pwd_file.h"
30 
31 #include <fcntl.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/mman.h>
35 #include <sys/stat.h>
36 
37 #include <async_safe/log.h>
38 
39 #include "private/ErrnoRestorer.h"
40 #include "private/ScopedFd.h"
41 
42 // This file mmap's /*/etc/passwd and /*/etc/group in order to return their contents without any
43 // allocations.  Note that these files and the strings contained within them are explicitly not
44 // null-terminated.  ':'s are used to deliminate fields and '\n's are used to deliminate lines.
45 // There is a check that the file ends with '\n', such that terminating loops at '\n' ensures that
46 // memory will be not read beyond the mmap region.
47 
48 namespace {
49 
CopyFieldToString(char * dest,const char * source,size_t max)50 void CopyFieldToString(char* dest, const char* source, size_t max) {
51   while (*source != ':' && *source != '\n' && max > 1) {
52     *dest++ = *source++;
53     --max;
54   }
55   *dest = '\0';
56 }
57 
FieldToUid(const char * field,uid_t * uid)58 bool FieldToUid(const char* field, uid_t* uid) {
59   if (field == nullptr) {
60     return false;
61   }
62 
63   char* end = nullptr;
64   errno = 0;
65   uid_t result = strtoul(field, &end, 0);
66   if (errno != 0 || field == end || *end != ':') {
67     return false;
68   }
69   *uid = result;
70   return true;
71 }
72 
73 // Returns a pointer to one past the end of line.
ParseLine(const char * begin,const char * end,const char ** fields,size_t num_fields)74 const char* ParseLine(const char* begin, const char* end, const char** fields, size_t num_fields) {
75   size_t fields_written = 0;
76   const char* position = begin;
77   fields[fields_written++] = position;
78 
79   while (position < end && fields_written < num_fields) {
80     if (*position == '\n') {
81       return position + 1;
82     }
83     if (*position == ':') {
84       fields[fields_written++] = position + 1;
85     }
86     position++;
87   }
88 
89   while (position < end && *position != '\n') {
90     position++;
91   }
92 
93   return position + 1;
94 }
95 
96 struct PasswdLine {
name__anon715fc6510111::PasswdLine97   const char* name() const {
98     return fields[0];
99   }
100   // Password is not supported.
uid__anon715fc6510111::PasswdLine101   const char* uid() const {
102     return fields[2];
103   }
gid__anon715fc6510111::PasswdLine104   const char* gid() const {
105     return fields[3];
106   }
107   // User Info is not supported
dir__anon715fc6510111::PasswdLine108   const char* dir() const {
109     return fields[5];
110   }
shell__anon715fc6510111::PasswdLine111   const char* shell() const {
112     return fields[6];
113   }
114 
ToPasswdState__anon715fc6510111::PasswdLine115   bool ToPasswdState(passwd_state_t* passwd_state) {
116     if (name() == nullptr || dir() == nullptr || shell() == nullptr) {
117       return false;
118     }
119 
120     uid_t uid;
121     if (!FieldToUid(this->uid(), &uid)) {
122       return false;
123     }
124 
125     gid_t gid;
126     if (!FieldToUid(this->gid(), &gid)) {
127       return false;
128     }
129 
130     passwd_state->passwd_.pw_uid = uid;
131     passwd_state->passwd_.pw_gid = gid;
132 
133     CopyFieldToString(passwd_state->name_buffer_, name(), sizeof(passwd_state->name_buffer_));
134     passwd_state->passwd_.pw_name = passwd_state->name_buffer_;
135 
136     passwd_state->passwd_.pw_passwd = nullptr;
137 
138 #ifdef __LP64__
139     passwd_state->passwd_.pw_gecos = nullptr;
140 #endif
141 
142     CopyFieldToString(passwd_state->dir_buffer_, dir(), sizeof(passwd_state->dir_buffer_));
143     passwd_state->passwd_.pw_dir = passwd_state->dir_buffer_;
144 
145     CopyFieldToString(passwd_state->sh_buffer_, shell(), sizeof(passwd_state->sh_buffer_));
146     passwd_state->passwd_.pw_shell = passwd_state->sh_buffer_;
147 
148     return true;
149   }
150 
151   static constexpr size_t kNumFields = 7;
152   const char* fields[kNumFields] = {};
153 };
154 
155 struct GroupLine {
name__anon715fc6510111::GroupLine156   const char* name() const {
157     return fields[0];
158   }
159   // Password is not supported.
gid__anon715fc6510111::GroupLine160   const char* gid() const {
161     return fields[2];
162   }
163   // User list is not supported (returns simply name)
164 
ToGroupState__anon715fc6510111::GroupLine165   bool ToGroupState(group_state_t* group_state) {
166     if (name() == nullptr || gid() == nullptr) {
167       return false;
168     }
169 
170     gid_t gid;
171     if (!FieldToUid(this->gid(), &gid)) {
172       return false;
173     }
174 
175     group_state->group_.gr_gid = gid;
176 
177     CopyFieldToString(group_state->group_name_buffer_, name(),
178                       sizeof(group_state->group_name_buffer_));
179     group_state->group_.gr_name = group_state->group_name_buffer_;
180 
181     group_state->group_.gr_passwd = nullptr;
182 
183     group_state->group_.gr_mem = group_state->group_members_;
184     group_state->group_.gr_mem[0] = group_state->group_.gr_name;
185     group_state->group_.gr_mem[1] = nullptr;
186 
187     return true;
188   }
189 
190   static constexpr size_t kNumFields = 4;
191   const char* fields[kNumFields] = {};
192 };
193 
194 }  // namespace
195 
MmapFile(const char * filename,const char * required_prefix)196 MmapFile::MmapFile(const char* filename, const char* required_prefix)
197     : filename_(filename), required_prefix_(required_prefix) {
198   lock_.init(false);
199 }
200 
Unmap()201 void MmapFile::Unmap() {
202   if (status_ == FileStatus::Initialized) {
203     size_t size = end_ - start_ + 1;
204     munmap(const_cast<char*>(start_), size);
205     status_ = FileStatus::Uninitialized;
206     start_ = nullptr;
207     end_ = nullptr;
208   }
209 }
210 
GetFile(const char ** start,const char ** end)211 bool MmapFile::GetFile(const char** start, const char** end) {
212   LockGuard guard(lock_);
213   if (status_ == FileStatus::Initialized) {
214     *start = start_;
215     *end = end_;
216     return true;
217   }
218   if (status_ == FileStatus::Error) {
219     return false;
220   }
221 
222   if (!DoMmap()) {
223     status_ = FileStatus::Error;
224     return false;
225   }
226 
227   status_ = FileStatus::Initialized;
228   *start = start_;
229   *end = end_;
230   return true;
231 }
232 
DoMmap()233 bool MmapFile::DoMmap() {
234   ScopedFd fd(open(filename_, O_CLOEXEC | O_NOFOLLOW | O_RDONLY));
235 
236   struct stat fd_stat;
237   if (fstat(fd.get(), &fd_stat) == -1) {
238     return false;
239   }
240 
241   auto mmap_size = fd_stat.st_size;
242 
243   void* map_result = mmap(nullptr, mmap_size, PROT_READ, MAP_SHARED, fd.get(), 0);
244   if (map_result == MAP_FAILED) {
245     return false;
246   }
247 
248   start_ = static_cast<const char*>(map_result);
249   end_ = start_ + mmap_size - 1;
250 
251   if (*end_ != '\n') {
252     munmap(map_result, mmap_size);
253     return false;
254   }
255 
256   return true;
257 }
258 
259 template <typename Line, typename Predicate>
Find(Line * line,Predicate predicate)260 bool MmapFile::Find(Line* line, Predicate predicate) {
261   const char* start;
262   const char* end;
263   if (!GetFile(&start, &end)) {
264     return false;
265   }
266 
267   const char* line_beginning = start;
268 
269   while (line_beginning < end) {
270     line_beginning = ParseLine(line_beginning, end, line->fields, line->kNumFields);
271 #if defined(__ANDROID__)
272     // To comply with Treble, users/groups from each partition need to be prefixed with
273     // the partition name.
274     if (required_prefix_ != nullptr) {
275       if (strncmp(line->fields[0], required_prefix_, strlen(required_prefix_)) != 0) {
276         char name[kGrpPwdBufferSize];
277         CopyFieldToString(name, line->fields[0], sizeof(name));
278         async_safe_format_log(ANDROID_LOG_ERROR, "libc",
279                               "Found user/group name '%s' in '%s' without required prefix '%s'",
280                               name, filename_, required_prefix_);
281         continue;
282       }
283     }
284 #endif
285     if (predicate(line)) return true;
286   }
287 
288   return false;
289 }
290 
291 template <typename Line>
FindById(uid_t uid,Line * line)292 bool MmapFile::FindById(uid_t uid, Line* line) {
293   return Find(line, [uid](const auto& line) {
294     uid_t line_id;
295     if (!FieldToUid(line->fields[2], &line_id)) {
296       return false;
297     }
298 
299     return line_id == uid;
300   });
301 }
302 
303 template <typename Line>
FindByName(const char * name,Line * line)304 bool MmapFile::FindByName(const char* name, Line* line) {
305   return Find(line, [name](const auto& line) {
306     const char* line_name = line->fields[0];
307     if (line_name == nullptr) {
308       return false;
309     }
310 
311     const char* match_name = name;
312     while (*line_name != '\n' && *line_name != ':' && *match_name != '\0') {
313       if (*line_name++ != *match_name++) {
314         return false;
315       }
316     }
317 
318     return *line_name == ':' && *match_name == '\0';
319   });
320 }
321 
PasswdFile(const char * filename,const char * required_prefix)322 PasswdFile::PasswdFile(const char* filename, const char* required_prefix)
323     : mmap_file_(filename, required_prefix) {
324 }
325 
FindById(uid_t id,passwd_state_t * passwd_state)326 bool PasswdFile::FindById(uid_t id, passwd_state_t* passwd_state) {
327   ErrnoRestorer errno_restorer;
328   PasswdLine passwd_line;
329   return mmap_file_.FindById(id, &passwd_line) && passwd_line.ToPasswdState(passwd_state);
330 }
331 
FindByName(const char * name,passwd_state_t * passwd_state)332 bool PasswdFile::FindByName(const char* name, passwd_state_t* passwd_state) {
333   ErrnoRestorer errno_restorer;
334   PasswdLine passwd_line;
335   return mmap_file_.FindByName(name, &passwd_line) && passwd_line.ToPasswdState(passwd_state);
336 }
337 
GroupFile(const char * filename,const char * required_prefix)338 GroupFile::GroupFile(const char* filename, const char* required_prefix)
339     : mmap_file_(filename, required_prefix) {
340 }
341 
FindById(gid_t id,group_state_t * group_state)342 bool GroupFile::FindById(gid_t id, group_state_t* group_state) {
343   ErrnoRestorer errno_restorer;
344   GroupLine group_line;
345   return mmap_file_.FindById(id, &group_line) && group_line.ToGroupState(group_state);
346 }
347 
FindByName(const char * name,group_state_t * group_state)348 bool GroupFile::FindByName(const char* name, group_state_t* group_state) {
349   ErrnoRestorer errno_restorer;
350   GroupLine group_line;
351   return mmap_file_.FindByName(name, &group_line) && group_line.ToGroupState(group_state);
352 }
353