/ deps / KittyMemoryEx / KittyPtrValidator.cpp
KittyPtrValidator.cpp
  1  #include "KittyPtrValidator.hpp"
  2  
  3  #ifdef __APPLE__
  4  
  5  bool KittyPtrValidator::_findRegion(uintptr_t addr, RegionInfo *region)
  6  {
  7      if (!use_cache_)
  8      {
  9          vm_address_t address = addr & ~(page_size_ - 1);
 10          vm_size_t size = 0;
 11          natural_t nesting_depth = 0;
 12          vm_region_submap_short_info_data_64_t info{};
 13          mach_msg_type_number_t info_count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
 14          kern_return_t kret = vm_region_recurse_64(task_,
 15                                                    &address,
 16                                                    &size,
 17                                                    &nesting_depth,
 18                                                    (vm_region_recurse_info_t)&info,
 19                                                    &info_count);
 20          if (kret != KERN_SUCCESS)
 21              return false;
 22  
 23          bool readable = (info.protection & VM_PROT_READ) != 0;
 24          bool writable = (info.protection & VM_PROT_WRITE) != 0;
 25          bool executable = (info.protection & VM_PROT_EXECUTE) != 0;
 26          *region = RegionInfo(address, address + size, readable, writable, executable);
 27          return address <= addr && addr < address + size;
 28      }
 29  
 30      if (!cachedRegions_.empty())
 31      {
 32          if (last_region_index_ < cachedRegions_.size() && cachedRegions_[last_region_index_].start <= addr &&
 33              addr < cachedRegions_[last_region_index_].end)
 34          {
 35              *region = cachedRegions_[last_region_index_];
 36              return true;
 37          }
 38  
 39          size_t left = 0;
 40          size_t right = cachedRegions_.size();
 41          size_t best_match = right;
 42  
 43          while (left < right)
 44          {
 45              size_t mid = left + (right - left) / 2;
 46              if (cachedRegions_[mid].end <= addr)
 47              {
 48                  left = mid + 1;
 49              }
 50              else
 51              {
 52                  best_match = mid;
 53                  right = mid;
 54              }
 55          }
 56  
 57          if (best_match < cachedRegions_.size() && cachedRegions_[best_match].start <= addr &&
 58              addr < cachedRegions_[best_match].end)
 59          {
 60              last_region_index_ = best_match;
 61              *region = cachedRegions_[best_match];
 62              return true;
 63          }
 64      }
 65  
 66      return false;
 67  }
 68  
 69  void KittyPtrValidator::refreshRegionCache()
 70  {
 71      cachedRegions_.clear();
 72      vm_address_t address = 0;
 73  
 74      while (true)
 75      {
 76          vm_size_t size = 0;
 77          natural_t nesting_depth = 0;
 78          vm_region_submap_short_info_data_64_t info{};
 79          mach_msg_type_number_t info_count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
 80          kern_return_t kret = vm_region_recurse_64(task_,
 81                                                    &address,
 82                                                    &size,
 83                                                    &nesting_depth,
 84                                                    (vm_region_recurse_info_t)&info,
 85                                                    &info_count);
 86          if (kret != KERN_SUCCESS)
 87              break;
 88  
 89          bool readable = (info.protection & VM_PROT_READ) != 0;
 90          bool writable = (info.protection & VM_PROT_WRITE) != 0;
 91          bool executable = (info.protection & VM_PROT_EXECUTE) != 0;
 92          RegionInfo new_region(address, address + size, readable, writable, executable);
 93  
 94          if (!cachedRegions_.empty() && cachedRegions_.back().canMergeWith(new_region))
 95          {
 96              cachedRegions_.back().end = new_region.end;
 97          }
 98          else
 99          {
100              cachedRegions_.emplace_back(new_region);
101          }
102  
103          address += size;
104      }
105  
106      if (!cachedRegions_.empty())
107      {
108          std::sort(cachedRegions_.begin(), cachedRegions_.end(), [](const RegionInfo &a, const RegionInfo &b) {
109              return a.start < b.start;
110          });
111      }
112  
113      last_region_index_ = 0;
114  }
115  
116  #else
117  
118  bool KittyPtrValidator::_parseMapsLine(const char *line, RegionInfo *region)
119  {
120      if (!line || *line == '\0')
121          return false;
122  
123      char *endPtr;
124      uintptr_t start = (uintptr_t)strtoull(line, &endPtr, 16);
125      if (*endPtr != '-')
126          return false;
127  
128      uintptr_t end = (uintptr_t)strtoull(endPtr + 1, &endPtr, 16);
129  
130      while (*endPtr == ' ')
131          endPtr++;
132  
133      if (!endPtr[0] || !endPtr[1] || !endPtr[2])
134          return false;
135  
136      *region = RegionInfo(start, end, endPtr[0] == 'r', endPtr[1] == 'w', endPtr[2] == 'x');
137      return true;
138  }
139  
140  bool KittyPtrValidator::_findRegion(uintptr_t addr, RegionInfo *out)
141  {
142      addr = KittyUtils::untagHeepPtr(addr);
143      if (!use_cache_)
144      {
145          bool found = false;
146  
147          char filePath[0xff] = {};
148          snprintf(filePath, sizeof(filePath), "/proc/%d/maps", pid_);
149          FILE *fp = fopen(filePath, "r");
150          if (!fp)
151              return false;
152  
153          char line[512];
154          while (fgets(line, sizeof(line), fp))
155          {
156              RegionInfo region(0, 0, false, false, false);
157              if (_parseMapsLine(line, &region))
158              {
159                  if (addr >= region.start && addr < region.end)
160                  {
161                      *out = region;
162                      found = true;
163                      break;
164                  }
165              }
166          }
167  
168          fclose(fp);
169          return found;
170      }
171  
172      if (!cachedRegions_.empty())
173      {
174          if (last_region_index_ < cachedRegions_.size())
175          {
176              const auto &last = cachedRegions_[last_region_index_];
177              if (addr >= last.start && addr < last.end)
178              {
179                  *out = last;
180                  return true;
181              }
182          }
183  
184          auto it = std::lower_bound(cachedRegions_.begin(),
185                                     cachedRegions_.end(),
186                                     addr,
187                                     [](const RegionInfo &r, uintptr_t val) { return r.end <= val; });
188  
189          if (it != cachedRegions_.end() && addr >= it->start && addr < it->end)
190          {
191              *out = *it;
192              last_region_index_ = std::distance(cachedRegions_.begin(), it);
193              return true;
194          }
195      }
196  
197      return false;
198  }
199  
200  void KittyPtrValidator::refreshRegionCache()
201  {
202      cachedRegions_.clear();
203      last_region_index_ = 0;
204  
205      char filePath[0xff] = {};
206      snprintf(filePath, sizeof(filePath), "/proc/%d/maps", pid_);
207  
208      FILE *fp = fopen(filePath, "r");
209      if (!fp)
210          return;
211  
212      char line[512];
213      while (fgets(line, sizeof(line), fp))
214      {
215          RegionInfo region(0, 0, false, false, false);
216  
217          if (_parseMapsLine(line, &region))
218          {
219              if (!cachedRegions_.empty() && cachedRegions_.back().canMergeWith(region))
220              {
221                  cachedRegions_.back().end = region.end;
222              }
223              else
224              {
225                  cachedRegions_.emplace_back(region);
226              }
227          }
228      }
229  
230      fclose(fp);
231  
232      if (!cachedRegions_.empty())
233      {
234          std::sort(cachedRegions_.begin(), cachedRegions_.end(), [](const RegionInfo &a, const RegionInfo &b) {
235              return a.start < b.start;
236          });
237      }
238  }
239  
240  
241  #endif
242  
243  bool KittyPtrValidator::isPtrReadable(uintptr_t ptr, size_t len)
244  {
245      if (ptr == 0 || ptr + len < ptr)
246          return false;
247  
248      ptr = KittyUtils::untagHeepPtr(ptr);
249  
250      uintptr_t end = ptr + len;
251      RegionInfo region(0, 0, false, false, false);
252      while (region.end < end)
253      {
254          if (!_findRegion(ptr, &region) || !region.readable)
255              return false;
256  
257          ptr = region.end;
258      }
259  
260      return true;
261  }
262  
263  bool KittyPtrValidator::isPtrWritable(uintptr_t ptr, size_t len)
264  {
265      if (ptr == 0 || ptr + len < ptr)
266          return false;
267  
268      ptr = KittyUtils::untagHeepPtr(ptr);
269  
270      uintptr_t end = ptr + len;
271      RegionInfo region(0, 0, false, false, false);
272      while (region.end < end)
273      {
274          if (!_findRegion(ptr, &region) || !region.writable)
275              return false;
276  
277          ptr = region.end;
278      }
279  
280      return true;
281  }
282  
283  bool KittyPtrValidator::isPtrExecutable(uintptr_t ptr, size_t len)
284  {
285      if (ptr == 0 || ptr + len < ptr)
286          return false;
287  
288      ptr = KittyUtils::untagHeepPtr(ptr);
289  
290      uintptr_t end = ptr + len;
291      RegionInfo region(0, 0, false, false, false);
292      while (region.end < end)
293      {
294          if (!_findRegion(ptr, &region) || !region.executable)
295              return false;
296  
297          ptr = region.end;
298      }
299  
300      return true;
301  }