mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 15:12:45 +00:00 
			
		
		
		
	Kernel: Use Process::credentials() and remove user ID/group ID helpers
Move away from using the group ID/user ID helpers in the process to allow for us to take advantage of the immutable credentials instead.
This commit is contained in:
		
							parent
							
								
									8026d8926c
								
							
						
					
					
						commit
						f86b671de2
					
				
					 27 changed files with 109 additions and 94 deletions
				
			
		|  | @ -62,7 +62,8 @@ ErrorOr<void> InodeFile::ioctl(OpenFileDescription& description, unsigned reques | ||||||
| { | { | ||||||
|     switch (request) { |     switch (request) { | ||||||
|     case FIBMAP: { |     case FIBMAP: { | ||||||
|         if (!Process::current().is_superuser()) |         auto current_process_credentials = Process::current().credentials(); | ||||||
|  |         if (!current_process_credentials->is_superuser()) | ||||||
|             return EPERM; |             return EPERM; | ||||||
| 
 | 
 | ||||||
|         auto user_block_number = static_ptr_cast<int*>(arg); |         auto user_block_number = static_ptr_cast<int*>(arg); | ||||||
|  |  | ||||||
|  | @ -150,7 +150,8 @@ private: | ||||||
|             TRY(obj.add("bytes_in"sv, socket.bytes_in())); |             TRY(obj.add("bytes_in"sv, socket.bytes_in())); | ||||||
|             TRY(obj.add("packets_out"sv, socket.packets_out())); |             TRY(obj.add("packets_out"sv, socket.packets_out())); | ||||||
|             TRY(obj.add("bytes_out"sv, socket.bytes_out())); |             TRY(obj.add("bytes_out"sv, socket.bytes_out())); | ||||||
|             if (Process::current().is_superuser() || Process::current().uid() == socket.origin_uid()) { |             auto current_process_credentials = Process::current().credentials(); | ||||||
|  |             if (current_process_credentials->is_superuser() || current_process_credentials->uid() == socket.origin_uid()) { | ||||||
|                 TRY(obj.add("origin_pid"sv, socket.origin_pid().value())); |                 TRY(obj.add("origin_pid"sv, socket.origin_pid().value())); | ||||||
|                 TRY(obj.add("origin_uid"sv, socket.origin_uid().value())); |                 TRY(obj.add("origin_uid"sv, socket.origin_uid().value())); | ||||||
|                 TRY(obj.add("origin_gid"sv, socket.origin_gid().value())); |                 TRY(obj.add("origin_gid"sv, socket.origin_gid().value())); | ||||||
|  | @ -206,7 +207,8 @@ private: | ||||||
|             auto peer_address = TRY(socket.peer_address().to_string()); |             auto peer_address = TRY(socket.peer_address().to_string()); | ||||||
|             TRY(obj.add("peer_address"sv, peer_address->view())); |             TRY(obj.add("peer_address"sv, peer_address->view())); | ||||||
|             TRY(obj.add("peer_port"sv, socket.peer_port())); |             TRY(obj.add("peer_port"sv, socket.peer_port())); | ||||||
|             if (Process::current().is_superuser() || Process::current().uid() == socket.origin_uid()) { |             auto current_process_credentials = Process::current().credentials(); | ||||||
|  |             if (current_process_credentials->is_superuser() || current_process_credentials->uid() == socket.origin_uid()) { | ||||||
|                 TRY(obj.add("origin_pid"sv, socket.origin_pid().value())); |                 TRY(obj.add("origin_pid"sv, socket.origin_pid().value())); | ||||||
|                 TRY(obj.add("origin_uid"sv, socket.origin_uid().value())); |                 TRY(obj.add("origin_uid"sv, socket.origin_uid().value())); | ||||||
|                 TRY(obj.add("origin_gid"sv, socket.origin_gid().value())); |                 TRY(obj.add("origin_gid"sv, socket.origin_gid().value())); | ||||||
|  | @ -525,8 +527,9 @@ private: | ||||||
|             TRY(process_object.add("pgid"sv, process.tty() ? process.tty()->pgid().value() : 0)); |             TRY(process_object.add("pgid"sv, process.tty() ? process.tty()->pgid().value() : 0)); | ||||||
|             TRY(process_object.add("pgp"sv, process.pgid().value())); |             TRY(process_object.add("pgp"sv, process.pgid().value())); | ||||||
|             TRY(process_object.add("sid"sv, process.sid().value())); |             TRY(process_object.add("sid"sv, process.sid().value())); | ||||||
|             TRY(process_object.add("uid"sv, process.uid().value())); |             auto credentials = process.credentials(); | ||||||
|             TRY(process_object.add("gid"sv, process.gid().value())); |             TRY(process_object.add("uid"sv, credentials->uid().value())); | ||||||
|  |             TRY(process_object.add("gid"sv, credentials->gid().value())); | ||||||
|             TRY(process_object.add("ppid"sv, process.ppid().value())); |             TRY(process_object.add("ppid"sv, process.ppid().value())); | ||||||
|             if (process.tty()) { |             if (process.tty()) { | ||||||
|                 auto tty_pseudo_name = TRY(process.tty()->pseudo_name()); |                 auto tty_pseudo_name = TRY(process.tty()->pseudo_name()); | ||||||
|  | @ -822,7 +825,8 @@ private: | ||||||
| 
 | 
 | ||||||
|     virtual ErrorOr<void> try_generate(KBufferBuilder& builder) override |     virtual ErrorOr<void> try_generate(KBufferBuilder& builder) override | ||||||
|     { |     { | ||||||
|         if (!Process::current().is_superuser()) |         auto current_process_credentials = Process::current().credentials(); | ||||||
|  |         if (!current_process_credentials->is_superuser()) | ||||||
|             return EPERM; |             return EPERM; | ||||||
|         return builder.appendff("{}", kernel_load_base); |         return builder.appendff("{}", kernel_load_base); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -625,7 +625,8 @@ ErrorOr<void> IPv4Socket::ioctl(OpenFileDescription&, unsigned request, Userspac | ||||||
| 
 | 
 | ||||||
|         switch (request) { |         switch (request) { | ||||||
|         case SIOCADDRT: { |         case SIOCADDRT: { | ||||||
|             if (!Process::current().is_superuser()) |             auto current_process_credentials = Process::current().credentials(); | ||||||
|  |             if (!current_process_credentials->is_superuser()) | ||||||
|                 return EPERM; |                 return EPERM; | ||||||
|             if (route.rt_gateway.sa_family != AF_INET) |             if (route.rt_gateway.sa_family != AF_INET) | ||||||
|                 return EAFNOSUPPORT; |                 return EAFNOSUPPORT; | ||||||
|  | @ -639,7 +640,8 @@ ErrorOr<void> IPv4Socket::ioctl(OpenFileDescription&, unsigned request, Userspac | ||||||
|             return update_routing_table(destination, gateway, genmask, route.rt_flags, adapter, UpdateTable::Set); |             return update_routing_table(destination, gateway, genmask, route.rt_flags, adapter, UpdateTable::Set); | ||||||
|         } |         } | ||||||
|         case SIOCDELRT: |         case SIOCDELRT: | ||||||
|             if (!Process::current().is_superuser()) |             auto current_process_credentials = Process::current().credentials(); | ||||||
|  |             if (!current_process_credentials->is_superuser()) | ||||||
|                 return EPERM; |                 return EPERM; | ||||||
|             if (route.rt_gateway.sa_family != AF_INET) |             if (route.rt_gateway.sa_family != AF_INET) | ||||||
|                 return EAFNOSUPPORT; |                 return EAFNOSUPPORT; | ||||||
|  | @ -659,9 +661,11 @@ ErrorOr<void> IPv4Socket::ioctl(OpenFileDescription&, unsigned request, Userspac | ||||||
|         arpreq arp_req; |         arpreq arp_req; | ||||||
|         TRY(copy_from_user(&arp_req, user_req)); |         TRY(copy_from_user(&arp_req, user_req)); | ||||||
| 
 | 
 | ||||||
|  |         auto current_process_credentials = Process::current().credentials(); | ||||||
|  | 
 | ||||||
|         switch (request) { |         switch (request) { | ||||||
|         case SIOCSARP: |         case SIOCSARP: | ||||||
|             if (!Process::current().is_superuser()) |             if (!current_process_credentials->is_superuser()) | ||||||
|                 return EPERM; |                 return EPERM; | ||||||
|             if (arp_req.arp_pa.sa_family != AF_INET) |             if (arp_req.arp_pa.sa_family != AF_INET) | ||||||
|                 return EAFNOSUPPORT; |                 return EAFNOSUPPORT; | ||||||
|  | @ -669,7 +673,7 @@ ErrorOr<void> IPv4Socket::ioctl(OpenFileDescription&, unsigned request, Userspac | ||||||
|             return {}; |             return {}; | ||||||
| 
 | 
 | ||||||
|         case SIOCDARP: |         case SIOCDARP: | ||||||
|             if (!Process::current().is_superuser()) |             if (!current_process_credentials->is_superuser()) | ||||||
|                 return EPERM; |                 return EPERM; | ||||||
|             if (arp_req.arp_pa.sa_family != AF_INET) |             if (arp_req.arp_pa.sa_family != AF_INET) | ||||||
|                 return EAFNOSUPPORT; |                 return EAFNOSUPPORT; | ||||||
|  | @ -693,9 +697,11 @@ ErrorOr<void> IPv4Socket::ioctl(OpenFileDescription&, unsigned request, Userspac | ||||||
|         if (!adapter) |         if (!adapter) | ||||||
|             return ENODEV; |             return ENODEV; | ||||||
| 
 | 
 | ||||||
|  |         auto current_process_credentials = Process::current().credentials(); | ||||||
|  | 
 | ||||||
|         switch (request) { |         switch (request) { | ||||||
|         case SIOCSIFADDR: |         case SIOCSIFADDR: | ||||||
|             if (!Process::current().is_superuser()) |             if (!current_process_credentials->is_superuser()) | ||||||
|                 return EPERM; |                 return EPERM; | ||||||
|             if (ifr.ifr_addr.sa_family != AF_INET) |             if (ifr.ifr_addr.sa_family != AF_INET) | ||||||
|                 return EAFNOSUPPORT; |                 return EAFNOSUPPORT; | ||||||
|  | @ -703,7 +709,7 @@ ErrorOr<void> IPv4Socket::ioctl(OpenFileDescription&, unsigned request, Userspac | ||||||
|             return {}; |             return {}; | ||||||
| 
 | 
 | ||||||
|         case SIOCSIFNETMASK: |         case SIOCSIFNETMASK: | ||||||
|             if (!Process::current().is_superuser()) |             if (!current_process_credentials->is_superuser()) | ||||||
|                 return EPERM; |                 return EPERM; | ||||||
|             if (ifr.ifr_addr.sa_family != AF_INET) |             if (ifr.ifr_addr.sa_family != AF_INET) | ||||||
|                 return EAFNOSUPPORT; |                 return EAFNOSUPPORT; | ||||||
|  |  | ||||||
|  | @ -206,7 +206,8 @@ ErrorOr<void> PerformanceEventBuffer::to_json_impl(Serializer& object) const | ||||||
|         TRY(strings.finish()); |         TRY(strings.finish()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     bool show_kernel_addresses = Process::current().is_superuser(); |     auto current_process_credentials = Process::current().credentials(); | ||||||
|  |     bool show_kernel_addresses = current_process_credentials->is_superuser(); | ||||||
|     auto array = TRY(object.add_array("events"sv)); |     auto array = TRY(object.add_array("events"sv)); | ||||||
|     bool seen_first_sample = false; |     bool seen_first_sample = false; | ||||||
|     for (size_t i = 0; i < m_count; ++i) { |     for (size_t i = 0; i < m_count; ++i) { | ||||||
|  |  | ||||||
|  | @ -522,10 +522,11 @@ Time kgettimeofday() | ||||||
| 
 | 
 | ||||||
| siginfo_t Process::wait_info() const | siginfo_t Process::wait_info() const | ||||||
| { | { | ||||||
|  |     auto credentials = this->credentials(); | ||||||
|     siginfo_t siginfo {}; |     siginfo_t siginfo {}; | ||||||
|     siginfo.si_signo = SIGCHLD; |     siginfo.si_signo = SIGCHLD; | ||||||
|     siginfo.si_pid = pid().value(); |     siginfo.si_pid = pid().value(); | ||||||
|     siginfo.si_uid = uid().value(); |     siginfo.si_uid = credentials->uid().value(); | ||||||
| 
 | 
 | ||||||
|     with_protected_data([&](auto& protected_data) { |     with_protected_data([&](auto& protected_data) { | ||||||
|         if (protected_data.termination_signal != 0) { |         if (protected_data.termination_signal != 0) { | ||||||
|  | @ -941,36 +942,6 @@ ErrorOr<void> Process::require_promise(Pledge promise) | ||||||
|     return EPROMISEVIOLATION; |     return EPROMISEVIOLATION; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| UserID Process::uid() const |  | ||||||
| { |  | ||||||
|     return credentials()->uid(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| GroupID Process::gid() const |  | ||||||
| { |  | ||||||
|     return credentials()->gid(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| UserID Process::euid() const |  | ||||||
| { |  | ||||||
|     return credentials()->euid(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| GroupID Process::egid() const |  | ||||||
| { |  | ||||||
|     return credentials()->egid(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| UserID Process::suid() const |  | ||||||
| { |  | ||||||
|     return credentials()->suid(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| GroupID Process::sgid() const |  | ||||||
| { |  | ||||||
|     return credentials()->sgid(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| NonnullRefPtr<Credentials> Process::credentials() const | NonnullRefPtr<Credentials> Process::credentials() const | ||||||
| { | { | ||||||
|     return with_protected_data([&](auto& protected_data) -> NonnullRefPtr<Credentials> { |     return with_protected_data([&](auto& protected_data) -> NonnullRefPtr<Credentials> { | ||||||
|  |  | ||||||
|  | @ -235,13 +235,6 @@ public: | ||||||
| 
 | 
 | ||||||
|     NonnullRefPtr<Credentials> credentials() const; |     NonnullRefPtr<Credentials> credentials() const; | ||||||
| 
 | 
 | ||||||
|     UserID euid() const; |  | ||||||
|     GroupID egid() const; |  | ||||||
|     UserID uid() const; |  | ||||||
|     GroupID gid() const; |  | ||||||
|     UserID suid() const; |  | ||||||
|     GroupID sgid() const; |  | ||||||
| 
 |  | ||||||
|     bool is_dumpable() const |     bool is_dumpable() const | ||||||
|     { |     { | ||||||
|         return with_protected_data([](auto& protected_data) { return protected_data.dumpable; }); |         return with_protected_data([](auto& protected_data) { return protected_data.dumpable; }); | ||||||
|  | @ -476,8 +469,6 @@ public: | ||||||
| 
 | 
 | ||||||
|     ErrorOr<LoadResult> load(NonnullLockRefPtr<OpenFileDescription> main_program_description, LockRefPtr<OpenFileDescription> interpreter_description, const ElfW(Ehdr) & main_program_header); |     ErrorOr<LoadResult> load(NonnullLockRefPtr<OpenFileDescription> main_program_description, LockRefPtr<OpenFileDescription> interpreter_description, const ElfW(Ehdr) & main_program_header); | ||||||
| 
 | 
 | ||||||
|     bool is_superuser() const { return euid() == 0; } |  | ||||||
| 
 |  | ||||||
|     void terminate_due_to_signal(u8 signal); |     void terminate_due_to_signal(u8 signal); | ||||||
|     ErrorOr<void> send_signal(u8 signal, Process* sender); |     ErrorOr<void> send_signal(u8 signal, Process* sender); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,7 +15,8 @@ UserID Process::ProcessProcFSTraits::owner_user() const | ||||||
|     if (!process) |     if (!process) | ||||||
|         return 0; |         return 0; | ||||||
| 
 | 
 | ||||||
|     return process->uid(); |     auto credentials = process->credentials(); | ||||||
|  |     return credentials->uid(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| GroupID Process::ProcessProcFSTraits::owner_group() const | GroupID Process::ProcessProcFSTraits::owner_group() const | ||||||
|  | @ -24,7 +25,8 @@ GroupID Process::ProcessProcFSTraits::owner_group() const | ||||||
|     if (!process) |     if (!process) | ||||||
|         return 0; |         return 0; | ||||||
| 
 | 
 | ||||||
|     return process->gid(); |     auto credentials = process->credentials(); | ||||||
|  |     return credentials->gid(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| InodeIndex Process::ProcessProcFSTraits::component_index() const | InodeIndex Process::ProcessProcFSTraits::component_index() const | ||||||
|  |  | ||||||
|  | @ -25,7 +25,8 @@ ErrorOr<void> Process::procfs_get_thread_stack(ThreadID thread_id, KBufferBuilde | ||||||
|     auto thread = Thread::from_tid(thread_id); |     auto thread = Thread::from_tid(thread_id); | ||||||
|     if (!thread) |     if (!thread) | ||||||
|         return ESRCH; |         return ESRCH; | ||||||
|     bool show_kernel_addresses = Process::current().is_superuser(); |     auto current_process_credentials = Process::current().credentials(); | ||||||
|  |     bool show_kernel_addresses = current_process_credentials->is_superuser(); | ||||||
|     bool kernel_address_added = false; |     bool kernel_address_added = false; | ||||||
|     for (auto address : TRY(Processor::capture_stack_trace(*thread, 1024))) { |     for (auto address : TRY(Processor::capture_stack_trace(*thread, 1024))) { | ||||||
|         if (!show_kernel_addresses && !Memory::is_user_address(VirtualAddress { address })) { |         if (!show_kernel_addresses && !Memory::is_user_address(VirtualAddress { address })) { | ||||||
|  | @ -269,7 +270,8 @@ ErrorOr<void> Process::procfs_get_virtual_memory_stats(KBufferBuilder& builder) | ||||||
|     { |     { | ||||||
|         SpinlockLocker lock(address_space().get_lock()); |         SpinlockLocker lock(address_space().get_lock()); | ||||||
|         for (auto const& region : address_space().regions()) { |         for (auto const& region : address_space().regions()) { | ||||||
|             if (!region.is_user() && !Process::current().is_superuser()) |             auto current_process_credentials = Process::current().credentials(); | ||||||
|  |             if (!region.is_user() && !current_process_credentials->is_superuser()) | ||||||
|                 continue; |                 continue; | ||||||
|             auto region_object = TRY(array.add_object()); |             auto region_object = TRY(array.add_object()); | ||||||
|             TRY(region_object.add("readable"sv, region.is_readable())); |             TRY(region_object.add("readable"sv, region.is_readable())); | ||||||
|  |  | ||||||
|  | @ -38,7 +38,8 @@ ErrorOr<FlatPtr> Process::sys$clock_settime(clockid_t clock_id, Userspace<timesp | ||||||
|     VERIFY_NO_PROCESS_BIG_LOCK(this); |     VERIFY_NO_PROCESS_BIG_LOCK(this); | ||||||
|     TRY(require_promise(Pledge::settime)); |     TRY(require_promise(Pledge::settime)); | ||||||
| 
 | 
 | ||||||
|     if (!is_superuser()) |     auto credentials = this->credentials(); | ||||||
|  |     if (!credentials->is_superuser()) | ||||||
|         return EPERM; |         return EPERM; | ||||||
| 
 | 
 | ||||||
|     auto time = TRY(copy_time_from_user(user_ts)); |     auto time = TRY(copy_time_from_user(user_ts)); | ||||||
|  | @ -120,7 +121,8 @@ ErrorOr<FlatPtr> Process::sys$adjtime(Userspace<timeval const*> user_delta, User | ||||||
| 
 | 
 | ||||||
|     if (user_delta) { |     if (user_delta) { | ||||||
|         TRY(require_promise(Pledge::settime)); |         TRY(require_promise(Pledge::settime)); | ||||||
|         if (!is_superuser()) |         auto credentials = this->credentials(); | ||||||
|  |         if (!credentials->is_superuser()) | ||||||
|             return EPERM; |             return EPERM; | ||||||
|         auto delta = TRY(copy_time_from_user(user_delta)); |         auto delta = TRY(copy_time_from_user(user_delta)); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -598,7 +598,8 @@ ErrorOr<void> Process::do_exec(NonnullLockRefPtr<OpenFileDescription> main_progr | ||||||
|     } |     } | ||||||
|     VERIFY(new_main_thread); |     VERIFY(new_main_thread); | ||||||
| 
 | 
 | ||||||
|     auto auxv = generate_auxiliary_vector(load_result.load_base, load_result.entry_eip, uid(), euid(), gid(), egid(), path->view(), main_program_fd_allocation); |     auto credentials = this->credentials(); | ||||||
|  |     auto auxv = generate_auxiliary_vector(load_result.load_base, load_result.entry_eip, credentials->uid(), credentials->euid(), credentials->gid(), credentials->egid(), path->view(), main_program_fd_allocation); | ||||||
| 
 | 
 | ||||||
|     // NOTE: We create the new stack before disabling interrupts since it will zero-fault
 |     // NOTE: We create the new stack before disabling interrupts since it will zero-fault
 | ||||||
|     //       and we don't want to deal with faults after this point.
 |     //       and we don't want to deal with faults after this point.
 | ||||||
|  |  | ||||||
|  | @ -28,7 +28,8 @@ ErrorOr<FlatPtr> Process::sys$fork(RegisterState& regs) | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     auto child_name = TRY(m_name->try_clone()); |     auto child_name = TRY(m_name->try_clone()); | ||||||
|     auto child = TRY(Process::try_create(child_first_thread, move(child_name), uid(), gid(), pid(), m_is_kernel_process, current_directory(), executable(), m_tty, this)); |     auto credentials = this->credentials(); | ||||||
|  |     auto child = TRY(Process::try_create(child_first_thread, move(child_name), credentials->uid(), credentials->gid(), pid(), m_is_kernel_process, current_directory(), executable(), m_tty, this)); | ||||||
| 
 | 
 | ||||||
|     // NOTE: All user processes have a leaked ref on them. It's balanced by Thread::WaitBlockerSet::finalize().
 |     // NOTE: All user processes have a leaked ref on them. It's balanced by Thread::WaitBlockerSet::finalize().
 | ||||||
|     child->ref(); |     child->ref(); | ||||||
|  |  | ||||||
|  | @ -12,28 +12,32 @@ ErrorOr<FlatPtr> Process::sys$getuid() | ||||||
| { | { | ||||||
|     VERIFY_NO_PROCESS_BIG_LOCK(this); |     VERIFY_NO_PROCESS_BIG_LOCK(this); | ||||||
|     TRY(require_promise(Pledge::stdio)); |     TRY(require_promise(Pledge::stdio)); | ||||||
|     return uid().value(); |     auto credentials = this->credentials(); | ||||||
|  |     return credentials->uid().value(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ErrorOr<FlatPtr> Process::sys$getgid() | ErrorOr<FlatPtr> Process::sys$getgid() | ||||||
| { | { | ||||||
|     VERIFY_NO_PROCESS_BIG_LOCK(this); |     VERIFY_NO_PROCESS_BIG_LOCK(this); | ||||||
|     TRY(require_promise(Pledge::stdio)); |     TRY(require_promise(Pledge::stdio)); | ||||||
|     return gid().value(); |     auto credentials = this->credentials(); | ||||||
|  |     return credentials->gid().value(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ErrorOr<FlatPtr> Process::sys$geteuid() | ErrorOr<FlatPtr> Process::sys$geteuid() | ||||||
| { | { | ||||||
|     VERIFY_NO_PROCESS_BIG_LOCK(this); |     VERIFY_NO_PROCESS_BIG_LOCK(this); | ||||||
|     TRY(require_promise(Pledge::stdio)); |     TRY(require_promise(Pledge::stdio)); | ||||||
|     return euid().value(); |     auto credentials = this->credentials(); | ||||||
|  |     return credentials->euid().value(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ErrorOr<FlatPtr> Process::sys$getegid() | ErrorOr<FlatPtr> Process::sys$getegid() | ||||||
| { | { | ||||||
|     VERIFY_NO_PROCESS_BIG_LOCK(this); |     VERIFY_NO_PROCESS_BIG_LOCK(this); | ||||||
|     TRY(require_promise(Pledge::stdio)); |     TRY(require_promise(Pledge::stdio)); | ||||||
|     return egid().value(); |     auto credentials = this->credentials(); | ||||||
|  |     return credentials->egid().value(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ErrorOr<FlatPtr> Process::sys$getresuid(Userspace<UserID*> user_ruid, Userspace<UserID*> user_euid, Userspace<UserID*> user_suid) | ErrorOr<FlatPtr> Process::sys$getresuid(Userspace<UserID*> user_ruid, Userspace<UserID*> user_euid, Userspace<UserID*> user_suid) | ||||||
|  |  | ||||||
|  | @ -27,7 +27,8 @@ ErrorOr<FlatPtr> Process::sys$sethostname(Userspace<char const*> buffer, size_t | ||||||
|     VERIFY_NO_PROCESS_BIG_LOCK(this); |     VERIFY_NO_PROCESS_BIG_LOCK(this); | ||||||
|     TRY(require_no_promises()); |     TRY(require_no_promises()); | ||||||
| 
 | 
 | ||||||
|     if (!is_superuser()) |     auto credentials = this->credentials(); | ||||||
|  |     if (!credentials->is_superuser()) | ||||||
|         return EPERM; |         return EPERM; | ||||||
|     if (length > 64) |     if (length > 64) | ||||||
|         return ENAMETOOLONG; |         return ENAMETOOLONG; | ||||||
|  |  | ||||||
|  | @ -16,7 +16,8 @@ ErrorOr<FlatPtr> Process::sys$setkeymap(Userspace<Syscall::SC_setkeymap_params c | ||||||
|     VERIFY_NO_PROCESS_BIG_LOCK(this); |     VERIFY_NO_PROCESS_BIG_LOCK(this); | ||||||
|     TRY(require_promise(Pledge::setkeymap)); |     TRY(require_promise(Pledge::setkeymap)); | ||||||
| 
 | 
 | ||||||
|     if (!is_superuser()) |     auto credentials = this->credentials(); | ||||||
|  |     if (!credentials->is_superuser()) | ||||||
|         return EPERM; |         return EPERM; | ||||||
| 
 | 
 | ||||||
|     auto params = TRY(copy_typed_from_user(user_params)); |     auto params = TRY(copy_typed_from_user(user_params)); | ||||||
|  |  | ||||||
|  | @ -13,7 +13,9 @@ ErrorOr<void> Process::do_kill(Process& process, int signal) | ||||||
| { | { | ||||||
|     // FIXME: Allow sending SIGCONT to everyone in the process group.
 |     // FIXME: Allow sending SIGCONT to everyone in the process group.
 | ||||||
|     // FIXME: Should setuid processes have some special treatment here?
 |     // FIXME: Should setuid processes have some special treatment here?
 | ||||||
|     if (!is_superuser() && euid() != process.uid() && uid() != process.uid()) |     auto credentials = this->credentials(); | ||||||
|  |     auto kill_process_credentials = process.credentials(); | ||||||
|  |     if (!credentials->is_superuser() && credentials->euid() != kill_process_credentials->uid() && credentials->uid() != kill_process_credentials->uid()) | ||||||
|         return EPERM; |         return EPERM; | ||||||
|     if (process.is_kernel_process()) { |     if (process.is_kernel_process()) { | ||||||
|         dbgln("Attempted to send signal {} to kernel process {} ({})", signal, process.name(), process.pid()); |         dbgln("Attempted to send signal {} to kernel process {} ({})", signal, process.name(), process.pid()); | ||||||
|  |  | ||||||
|  | @ -16,10 +16,11 @@ ErrorOr<FlatPtr> Process::sys$mknod(Userspace<Syscall::SC_mknod_params const*> u | ||||||
|     TRY(require_promise(Pledge::dpath)); |     TRY(require_promise(Pledge::dpath)); | ||||||
|     auto params = TRY(copy_typed_from_user(user_params)); |     auto params = TRY(copy_typed_from_user(user_params)); | ||||||
| 
 | 
 | ||||||
|     if (!is_superuser() && !is_regular_file(params.mode) && !is_fifo(params.mode) && !is_socket(params.mode)) |     auto credentials = this->credentials(); | ||||||
|  |     if (!credentials->is_superuser() && !is_regular_file(params.mode) && !is_fifo(params.mode) && !is_socket(params.mode)) | ||||||
|         return EPERM; |         return EPERM; | ||||||
|     auto path = TRY(get_syscall_path_argument(params.path)); |     auto path = TRY(get_syscall_path_argument(params.path)); | ||||||
|     TRY(VirtualFileSystem::the().mknod(credentials(), path->view(), params.mode & ~umask(), params.dev, current_directory())); |     TRY(VirtualFileSystem::the().mknod(credentials, path->view(), params.mode & ~umask(), params.dev, current_directory())); | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -70,7 +70,8 @@ ErrorOr<FlatPtr> Process::sys$mount(Userspace<Syscall::SC_mount_params const*> u | ||||||
| { | { | ||||||
|     VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this); |     VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this); | ||||||
|     TRY(require_no_promises()); |     TRY(require_no_promises()); | ||||||
|     if (!is_superuser()) |     auto credentials = this->credentials(); | ||||||
|  |     if (!credentials->is_superuser()) | ||||||
|         return EPERM; |         return EPERM; | ||||||
| 
 | 
 | ||||||
|     auto params = TRY(copy_typed_from_user(user_params)); |     auto params = TRY(copy_typed_from_user(user_params)); | ||||||
|  | @ -86,7 +87,7 @@ ErrorOr<FlatPtr> Process::sys$mount(Userspace<Syscall::SC_mount_params const*> u | ||||||
|     else |     else | ||||||
|         dbgln("mount {} @ {}", fs_type, target); |         dbgln("mount {} @ {}", fs_type, target); | ||||||
| 
 | 
 | ||||||
|     auto target_custody = TRY(VirtualFileSystem::the().resolve_path(credentials(), target->view(), current_directory())); |     auto target_custody = TRY(VirtualFileSystem::the().resolve_path(credentials, target->view(), current_directory())); | ||||||
| 
 | 
 | ||||||
|     if (params.flags & MS_REMOUNT) { |     if (params.flags & MS_REMOUNT) { | ||||||
|         // We're not creating a new mount, we're updating an existing one!
 |         // We're not creating a new mount, we're updating an existing one!
 | ||||||
|  | @ -126,13 +127,14 @@ ErrorOr<FlatPtr> Process::sys$mount(Userspace<Syscall::SC_mount_params const*> u | ||||||
| ErrorOr<FlatPtr> Process::sys$umount(Userspace<char const*> user_mountpoint, size_t mountpoint_length) | ErrorOr<FlatPtr> Process::sys$umount(Userspace<char const*> user_mountpoint, size_t mountpoint_length) | ||||||
| { | { | ||||||
|     VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this); |     VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this); | ||||||
|     if (!is_superuser()) |     auto credentials = this->credentials(); | ||||||
|  |     if (!credentials->is_superuser()) | ||||||
|         return EPERM; |         return EPERM; | ||||||
| 
 | 
 | ||||||
|     TRY(require_no_promises()); |     TRY(require_no_promises()); | ||||||
| 
 | 
 | ||||||
|     auto mountpoint = TRY(get_syscall_path_argument(user_mountpoint, mountpoint_length)); |     auto mountpoint = TRY(get_syscall_path_argument(user_mountpoint, mountpoint_length)); | ||||||
|     auto custody = TRY(VirtualFileSystem::the().resolve_path(credentials(), mountpoint->view(), current_directory())); |     auto custody = TRY(VirtualFileSystem::the().resolve_path(credentials, mountpoint->view(), current_directory())); | ||||||
|     auto& guest_inode = custody->inode(); |     auto& guest_inode = custody->inode(); | ||||||
|     TRY(VirtualFileSystem::the().unmount(guest_inode)); |     TRY(VirtualFileSystem::the().unmount(guest_inode)); | ||||||
|     return 0; |     return 0; | ||||||
|  |  | ||||||
|  | @ -19,7 +19,8 @@ ErrorOr<FlatPtr> Process::sys$pipe(Userspace<int*> pipefd, int flags) | ||||||
|         return EINVAL; |         return EINVAL; | ||||||
| 
 | 
 | ||||||
|     u32 fd_flags = (flags & O_CLOEXEC) ? FD_CLOEXEC : 0; |     u32 fd_flags = (flags & O_CLOEXEC) ? FD_CLOEXEC : 0; | ||||||
|     auto fifo = TRY(FIFO::try_create(uid())); |     auto credentials = this->credentials(); | ||||||
|  |     auto fifo = TRY(FIFO::try_create(credentials->uid())); | ||||||
| 
 | 
 | ||||||
|     auto reader_description = TRY(fifo->open_direction(FIFO::Direction::Reader)); |     auto reader_description = TRY(fifo->open_direction(FIFO::Direction::Reader)); | ||||||
|     auto writer_description = TRY(fifo->open_direction(FIFO::Direction::Writer)); |     auto writer_description = TRY(fifo->open_direction(FIFO::Direction::Writer)); | ||||||
|  |  | ||||||
|  | @ -26,7 +26,8 @@ ErrorOr<FlatPtr> Process::sys$profiling_enable(pid_t pid, Userspace<u64 const*> | ||||||
|     auto const event_mask = TRY(copy_typed_from_user(userspace_event_mask)); |     auto const event_mask = TRY(copy_typed_from_user(userspace_event_mask)); | ||||||
| 
 | 
 | ||||||
|     if (pid == -1) { |     if (pid == -1) { | ||||||
|         if (!is_superuser()) |         auto credentials = this->credentials(); | ||||||
|  |         if (!credentials->is_superuser()) | ||||||
|             return EPERM; |             return EPERM; | ||||||
|         ScopedCritical critical; |         ScopedCritical critical; | ||||||
|         g_profiling_event_mask = PERF_EVENT_PROCESS_CREATE | PERF_EVENT_THREAD_CREATE | PERF_EVENT_MMAP; |         g_profiling_event_mask = PERF_EVENT_PROCESS_CREATE | PERF_EVENT_THREAD_CREATE | PERF_EVENT_MMAP; | ||||||
|  | @ -58,7 +59,9 @@ ErrorOr<FlatPtr> Process::sys$profiling_enable(pid_t pid, Userspace<u64 const*> | ||||||
|         return ESRCH; |         return ESRCH; | ||||||
|     if (process->is_dead()) |     if (process->is_dead()) | ||||||
|         return ESRCH; |         return ESRCH; | ||||||
|     if (!is_superuser() && process->uid() != euid()) |     auto credentials = this->credentials(); | ||||||
|  |     auto profile_process_credentials = process->credentials(); | ||||||
|  |     if (!credentials->is_superuser() && profile_process_credentials->uid() != credentials->euid()) | ||||||
|         return EPERM; |         return EPERM; | ||||||
|     SpinlockLocker lock(g_profiling_lock); |     SpinlockLocker lock(g_profiling_lock); | ||||||
|     g_profiling_event_mask = PERF_EVENT_PROCESS_CREATE | PERF_EVENT_THREAD_CREATE | PERF_EVENT_MMAP; |     g_profiling_event_mask = PERF_EVENT_PROCESS_CREATE | PERF_EVENT_THREAD_CREATE | PERF_EVENT_MMAP; | ||||||
|  | @ -81,7 +84,8 @@ ErrorOr<FlatPtr> Process::sys$profiling_disable(pid_t pid) | ||||||
|     TRY(require_no_promises()); |     TRY(require_no_promises()); | ||||||
| 
 | 
 | ||||||
|     if (pid == -1) { |     if (pid == -1) { | ||||||
|         if (!is_superuser()) |         auto credentials = this->credentials(); | ||||||
|  |         if (!credentials->is_superuser()) | ||||||
|             return EPERM; |             return EPERM; | ||||||
|         ScopedCritical critical; |         ScopedCritical critical; | ||||||
|         if (!TimeManagement::the().disable_profile_timer()) |         if (!TimeManagement::the().disable_profile_timer()) | ||||||
|  | @ -93,7 +97,9 @@ ErrorOr<FlatPtr> Process::sys$profiling_disable(pid_t pid) | ||||||
|     auto process = Process::from_pid(pid); |     auto process = Process::from_pid(pid); | ||||||
|     if (!process) |     if (!process) | ||||||
|         return ESRCH; |         return ESRCH; | ||||||
|     if (!is_superuser() && process->uid() != euid()) |     auto credentials = this->credentials(); | ||||||
|  |     auto profile_process_credentials = process->credentials(); | ||||||
|  |     if (!credentials->is_superuser() && profile_process_credentials->uid() != credentials->euid()) | ||||||
|         return EPERM; |         return EPERM; | ||||||
|     SpinlockLocker lock(g_profiling_lock); |     SpinlockLocker lock(g_profiling_lock); | ||||||
|     if (!process->is_profiling()) |     if (!process->is_profiling()) | ||||||
|  | @ -111,7 +117,8 @@ ErrorOr<FlatPtr> Process::sys$profiling_free_buffer(pid_t pid) | ||||||
|     TRY(require_no_promises()); |     TRY(require_no_promises()); | ||||||
| 
 | 
 | ||||||
|     if (pid == -1) { |     if (pid == -1) { | ||||||
|         if (!is_superuser()) |         auto credentials = this->credentials(); | ||||||
|  |         if (!credentials->is_superuser()) | ||||||
|             return EPERM; |             return EPERM; | ||||||
| 
 | 
 | ||||||
|         OwnPtr<PerformanceEventBuffer> perf_events; |         OwnPtr<PerformanceEventBuffer> perf_events; | ||||||
|  | @ -129,7 +136,9 @@ ErrorOr<FlatPtr> Process::sys$profiling_free_buffer(pid_t pid) | ||||||
|     auto process = Process::from_pid(pid); |     auto process = Process::from_pid(pid); | ||||||
|     if (!process) |     if (!process) | ||||||
|         return ESRCH; |         return ESRCH; | ||||||
|     if (!is_superuser() && process->uid() != euid()) |     auto credentials = this->credentials(); | ||||||
|  |     auto profile_process_credentials = process->credentials(); | ||||||
|  |     if (!credentials->is_superuser() && profile_process_credentials->uid() != credentials->euid()) | ||||||
|         return EPERM; |         return EPERM; | ||||||
|     SpinlockLocker lock(g_profiling_lock); |     SpinlockLocker lock(g_profiling_lock); | ||||||
|     if (process->is_profiling()) |     if (process->is_profiling()) | ||||||
|  |  | ||||||
|  | @ -40,8 +40,10 @@ static ErrorOr<FlatPtr> handle_ptrace(Kernel::Syscall::SC_ptrace_params const& p | ||||||
| 
 | 
 | ||||||
|     MutexLocker ptrace_locker(peer->process().ptrace_lock()); |     MutexLocker ptrace_locker(peer->process().ptrace_lock()); | ||||||
| 
 | 
 | ||||||
|     if ((peer->process().uid() != caller.euid()) |     auto peer_credentials = peer->process().credentials(); | ||||||
|         || (peer->process().uid() != peer->process().euid())) // Disallow tracing setuid processes
 |     auto caller_credentials = caller.credentials(); | ||||||
|  |     if ((peer_credentials->uid() != caller_credentials->euid()) | ||||||
|  |         || (peer_credentials->uid() != peer_credentials->euid())) // Disallow tracing setuid processes
 | ||||||
|         return EACCES; |         return EACCES; | ||||||
| 
 | 
 | ||||||
|     if (!peer->process().is_dumpable()) |     if (!peer->process().is_dumpable()) | ||||||
|  |  | ||||||
|  | @ -16,7 +16,8 @@ ErrorOr<FlatPtr> Process::sys$purge(int mode) | ||||||
| { | { | ||||||
|     VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this); |     VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this); | ||||||
|     TRY(require_no_promises()); |     TRY(require_no_promises()); | ||||||
|     if (!is_superuser()) |     auto credentials = this->credentials(); | ||||||
|  |     if (!credentials->is_superuser()) | ||||||
|         return EPERM; |         return EPERM; | ||||||
|     size_t purged_page_count = 0; |     size_t purged_page_count = 0; | ||||||
|     if (mode & PURGE_ALL_VOLATILE) { |     if (mode & PURGE_ALL_VOLATILE) { | ||||||
|  |  | ||||||
|  | @ -34,7 +34,9 @@ ErrorOr<FlatPtr> Process::sys$sched_setparam(int pid, Userspace<const struct sch | ||||||
|     if (!peer) |     if (!peer) | ||||||
|         return ESRCH; |         return ESRCH; | ||||||
| 
 | 
 | ||||||
|     if (!is_superuser() && euid() != peer->process().uid() && uid() != peer->process().uid()) |     auto credentials = this->credentials(); | ||||||
|  |     auto peer_credentials = peer->process().credentials(); | ||||||
|  |     if (!credentials->is_superuser() && credentials->euid() != peer_credentials->uid() && credentials->uid() != peer_credentials->uid()) | ||||||
|         return EPERM; |         return EPERM; | ||||||
| 
 | 
 | ||||||
|     peer->set_priority((u32)param.sched_priority); |     peer->set_priority((u32)param.sched_priority); | ||||||
|  | @ -58,7 +60,9 @@ ErrorOr<FlatPtr> Process::sys$sched_getparam(pid_t pid, Userspace<struct sched_p | ||||||
|         if (!peer) |         if (!peer) | ||||||
|             return ESRCH; |             return ESRCH; | ||||||
| 
 | 
 | ||||||
|         if (!is_superuser() && euid() != peer->process().uid() && uid() != peer->process().uid()) |         auto credentials = this->credentials(); | ||||||
|  |         auto peer_credentials = peer->process().credentials(); | ||||||
|  |         if (!credentials->is_superuser() && credentials->euid() != peer_credentials->uid() && credentials->uid() != peer_credentials->uid()) | ||||||
|             return EPERM; |             return EPERM; | ||||||
| 
 | 
 | ||||||
|         priority = (int)peer->priority(); |         priority = (int)peer->priority(); | ||||||
|  |  | ||||||
|  | @ -36,7 +36,8 @@ ErrorOr<FlatPtr> Process::sys$socket(int domain, int type, int protocol) | ||||||
|     VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this); |     VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this); | ||||||
|     REQUIRE_PROMISE_FOR_SOCKET_DOMAIN(domain); |     REQUIRE_PROMISE_FOR_SOCKET_DOMAIN(domain); | ||||||
| 
 | 
 | ||||||
|     if ((type & SOCK_TYPE_MASK) == SOCK_RAW && !is_superuser()) |     auto credentials = this->credentials(); | ||||||
|  |     if ((type & SOCK_TYPE_MASK) == SOCK_RAW && !credentials->is_superuser()) | ||||||
|         return EACCES; |         return EACCES; | ||||||
| 
 | 
 | ||||||
|     return m_fds.with_exclusive([&](auto& fds) -> ErrorOr<FlatPtr> { |     return m_fds.with_exclusive([&](auto& fds) -> ErrorOr<FlatPtr> { | ||||||
|  |  | ||||||
|  | @ -33,8 +33,9 @@ MasterPTY::MasterPTY(unsigned index, NonnullOwnPtr<DoubleBuffer> buffer) | ||||||
|     , m_buffer(move(buffer)) |     , m_buffer(move(buffer)) | ||||||
| { | { | ||||||
|     auto& process = Process::current(); |     auto& process = Process::current(); | ||||||
|     set_uid(process.uid()); |     auto credentials = process.credentials(); | ||||||
|     set_gid(process.gid()); |     set_uid(credentials->uid()); | ||||||
|  |     set_gid(credentials->gid()); | ||||||
| 
 | 
 | ||||||
|     m_buffer->set_unblock_callback([this]() { |     m_buffer->set_unblock_callback([this]() { | ||||||
|         if (m_slave) |         if (m_slave) | ||||||
|  |  | ||||||
|  | @ -42,8 +42,9 @@ SlavePTY::SlavePTY(MasterPTY& master, unsigned index) | ||||||
|     , m_index(index) |     , m_index(index) | ||||||
| { | { | ||||||
|     auto& process = Process::current(); |     auto& process = Process::current(); | ||||||
|     set_uid(process.uid()); |     auto credentials = process.credentials(); | ||||||
|     set_gid(process.gid()); |     set_uid(credentials->uid()); | ||||||
|  |     set_gid(credentials->gid()); | ||||||
|     set_size(80, 25); |     set_size(80, 25); | ||||||
| 
 | 
 | ||||||
|     SlavePTY::all_instances().with([&](auto& list) { list.append(*this); }); |     SlavePTY::all_instances().with([&](auto& list) { list.append(*this); }); | ||||||
|  |  | ||||||
|  | @ -1131,7 +1131,7 @@ DispatchSignalResult Thread::dispatch_signal(u8 signal) | ||||||
|             // Set for SI_TIMER, we don't have the data here.
 |             // Set for SI_TIMER, we don't have the data here.
 | ||||||
|             .si_errno = 0, |             .si_errno = 0, | ||||||
|             .si_pid = sender_pid.value(), |             .si_pid = sender_pid.value(), | ||||||
|             .si_uid = sender ? sender->uid().value() : 0, |             .si_uid = sender ? sender->credentials()->uid().value() : 0, | ||||||
|             // Set for SIGILL, SIGFPE, SIGSEGV and SIGBUS
 |             // Set for SIGILL, SIGFPE, SIGSEGV and SIGBUS
 | ||||||
|             // FIXME: We don't generate these signals in a way that can be handled.
 |             // FIXME: We don't generate these signals in a way that can be handled.
 | ||||||
|             .si_addr = 0, |             .si_addr = 0, | ||||||
|  | @ -1346,7 +1346,8 @@ static ErrorOr<bool> symbolicate(RecognizedSymbol const& symbol, Process& proces | ||||||
|     if (symbol.address == 0) |     if (symbol.address == 0) | ||||||
|         return false; |         return false; | ||||||
| 
 | 
 | ||||||
|     bool mask_kernel_addresses = !process.is_superuser(); |     auto credentials = process.credentials(); | ||||||
|  |     bool mask_kernel_addresses = !credentials->is_superuser(); | ||||||
|     if (!symbol.symbol) { |     if (!symbol.symbol) { | ||||||
|         if (!Memory::is_user_address(VirtualAddress(symbol.address))) { |         if (!Memory::is_user_address(VirtualAddress(symbol.address))) { | ||||||
|             TRY(builder.try_append("0xdeadc0de\n"sv)); |             TRY(builder.try_append("0xdeadc0de\n"sv)); | ||||||
|  |  | ||||||
|  | @ -783,10 +783,11 @@ bool Thread::WaitBlocker::unblock(Process& process, UnblockFlags flags, u8 signa | ||||||
|         siginfo_t siginfo {}; |         siginfo_t siginfo {}; | ||||||
|         { |         { | ||||||
|             SpinlockLocker lock(g_scheduler_lock); |             SpinlockLocker lock(g_scheduler_lock); | ||||||
|  |             auto credentials = process.credentials(); | ||||||
|             // We need to gather the information before we release the scheduler lock!
 |             // We need to gather the information before we release the scheduler lock!
 | ||||||
|             siginfo.si_signo = SIGCHLD; |             siginfo.si_signo = SIGCHLD; | ||||||
|             siginfo.si_pid = process.pid().value(); |             siginfo.si_pid = process.pid().value(); | ||||||
|             siginfo.si_uid = process.uid().value(); |             siginfo.si_uid = credentials->uid().value(); | ||||||
|             siginfo.si_status = signal; |             siginfo.si_status = signal; | ||||||
| 
 | 
 | ||||||
|             switch (flags) { |             switch (flags) { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Anthony Iacono
						Anthony Iacono