1f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved. 2f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 3f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)// found in the LICENSE file. 4f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 5f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#include "sandbox/mac/mach_message_server.h" 6f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 7f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#include <bsm/libbsm.h> 8f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#include <servers/bootstrap.h> 9f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 10f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#include <string> 11f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 12f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#include "base/logging.h" 13f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#include "base/mac/mach_logging.h" 14f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#include "base/strings/stringprintf.h" 15116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch#include "sandbox/mac/dispatch_source_mach.h" 16f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 17f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)namespace sandbox { 18f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 19f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)MachMessageServer::MachMessageServer( 20f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) MessageDemuxer* demuxer, 216d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) mach_port_t server_receive_right, 22f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) mach_msg_size_t buffer_size) 23f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) : demuxer_(demuxer), 246d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) server_port_(server_receive_right), 25f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) buffer_size_( 26f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) mach_vm_round_page(buffer_size + sizeof(mach_msg_audit_trailer_t))), 27f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) did_forward_message_(false) { 28f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) DCHECK(demuxer_); 29f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)} 30f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 31f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)MachMessageServer::~MachMessageServer() { 32f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)} 33f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 34f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)bool MachMessageServer::Initialize() { 35f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) mach_port_t task = mach_task_self(); 36f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) kern_return_t kr; 37f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 386d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) // Allocate a port for use as a new server port if one was not passed to the 396d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) // constructor. 406d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) if (!server_port_.is_valid()) { 416d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) mach_port_t port; 426d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) if ((kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &port)) != 436d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) KERN_SUCCESS) { 446d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) MACH_LOG(ERROR, kr) << "Failed to allocate new server port."; 456d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) return false; 466d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) } 476d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles) server_port_.reset(port); 48f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) } 49f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 50f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // Allocate the message request and reply buffers. 51f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) const int kMachMsgMemoryFlags = VM_MAKE_TAG(VM_MEMORY_MACH_MSG) | 52f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) VM_FLAGS_ANYWHERE; 53f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) vm_address_t buffer = 0; 54f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 55f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) kr = vm_allocate(task, &buffer, buffer_size_, kMachMsgMemoryFlags); 56f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) if (kr != KERN_SUCCESS) { 57f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) MACH_LOG(ERROR, kr) << "Failed to allocate request buffer."; 58f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) return false; 59f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) } 60f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) request_buffer_.reset(buffer, buffer_size_); 61f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 62f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) kr = vm_allocate(task, &buffer, buffer_size_, kMachMsgMemoryFlags); 63f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) if (kr != KERN_SUCCESS) { 64f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) MACH_LOG(ERROR, kr) << "Failed to allocate reply buffer."; 65f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) return false; 66f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) } 67f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) reply_buffer_.reset(buffer, buffer_size_); 68f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 69f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // Set up the dispatch queue to service the bootstrap port. 70f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) std::string label = base::StringPrintf( 71f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) "org.chromium.sandbox.MachMessageServer.%p", demuxer_); 72116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch dispatch_source_.reset(new DispatchSourceMach( 73116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch label.c_str(), server_port_.get(), ^{ ReceiveMessage(); })); 74116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch dispatch_source_->Resume(); 75f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 76f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) return true; 77f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)} 78f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 79116680a4aac90f2aa7413d9095a592090648e557Ben Murdochpid_t MachMessageServer::GetMessageSenderPID(IPCMessage request) { 80f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // Get the PID of the task that sent this request. This requires getting at 81f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // the trailer of the message, from the header. 82f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) mach_msg_audit_trailer_t* trailer = 83f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) reinterpret_cast<mach_msg_audit_trailer_t*>( 84116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch reinterpret_cast<vm_address_t>(request.mach) + 85116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch round_msg(request.mach->msgh_size)); 86f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // TODO(rsesek): In the 10.7 SDK, there's audit_token_to_pid(). 87f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) pid_t sender_pid; 88f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) audit_token_to_au32(trailer->msgh_audit, 89f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) NULL, NULL, NULL, NULL, NULL, &sender_pid, NULL, NULL); 90f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) return sender_pid; 91f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)} 92f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 93116680a4aac90f2aa7413d9095a592090648e557Ben MurdochIPCMessage MachMessageServer::CreateReply(IPCMessage request_message) { 94116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch mach_msg_header_t* request = request_message.mach; 95116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 96116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch IPCMessage reply_message; 97116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch mach_msg_header_t* reply = reply_message.mach = 98116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch reinterpret_cast<mach_msg_header_t*>(reply_buffer_.address()); 99116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch bzero(reply, buffer_size_); 100116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 101116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch reply->msgh_bits = MACH_MSGH_BITS_REMOTE(reply->msgh_bits); 102116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch // Since mach_msg will automatically swap the request and reply ports, 103116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch // undo that. 104116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch reply->msgh_remote_port = request->msgh_remote_port; 105116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch reply->msgh_local_port = MACH_PORT_NULL; 106116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch // MIG servers simply add 100 to the request ID to generate the reply ID. 107116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch reply->msgh_id = request->msgh_id + 100; 108116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 109116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch return reply_message; 110116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch} 111116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 112116680a4aac90f2aa7413d9095a592090648e557Ben Murdochbool MachMessageServer::SendReply(IPCMessage reply) { 113116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch kern_return_t kr = mach_msg(reply.mach, MACH_SEND_MSG, 114116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch reply.mach->msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, 115116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch MACH_PORT_NULL); 116f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) 117f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) << "Unable to send intercepted reply message."; 118f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) return kr == KERN_SUCCESS; 119f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)} 120f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 121116680a4aac90f2aa7413d9095a592090648e557Ben Murdochvoid MachMessageServer::ForwardMessage(IPCMessage message, 122f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) mach_port_t destination) { 123116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch mach_msg_header_t* request = message.mach; 124f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) request->msgh_local_port = request->msgh_remote_port; 125f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) request->msgh_remote_port = destination; 126f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // Preserve the msgh_bits that do not deal with the local and remote ports. 127f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) request->msgh_bits = (request->msgh_bits & ~MACH_MSGH_BITS_PORTS_MASK) | 128f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MOVE_SEND_ONCE); 129f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) kern_return_t kr = mach_msg_send(request); 130f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) if (kr == KERN_SUCCESS) { 131f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) did_forward_message_ = true; 132f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) } else { 133f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) MACH_LOG(ERROR, kr) << "Unable to forward message to the real launchd."; 134f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) } 135f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)} 136f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 137116680a4aac90f2aa7413d9095a592090648e557Ben Murdochvoid MachMessageServer::RejectMessage(IPCMessage request, int error_code) { 138116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch IPCMessage reply = CreateReply(request); 139116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch mig_reply_error_t* error_reply = 140116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch reinterpret_cast<mig_reply_error_t*>(reply.mach); 141f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) error_reply->Head.msgh_size = sizeof(mig_reply_error_t); 142f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) error_reply->Head.msgh_bits = 143f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND_ONCE); 144f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) error_reply->NDR = NDR_record; 145f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) error_reply->RetCode = error_code; 146116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch SendReply(reply); 147116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch} 148116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 149116680a4aac90f2aa7413d9095a592090648e557Ben Murdochmach_port_t MachMessageServer::GetServerPort() const { 150116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch return server_port_.get(); 151f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)} 152f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 153f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)void MachMessageServer::ReceiveMessage() { 154f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) const mach_msg_options_t kRcvOptions = MACH_RCV_MSG | 155f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | 156f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); 157f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 158f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) mach_msg_header_t* request = 159f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) reinterpret_cast<mach_msg_header_t*>(request_buffer_.address()); 160f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) mach_msg_header_t* reply = 161f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) reinterpret_cast<mach_msg_header_t*>(reply_buffer_.address()); 162f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 163f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // Zero out the buffers from handling any previous message. 164f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) bzero(request, buffer_size_); 165f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) bzero(reply, buffer_size_); 166f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) did_forward_message_ = false; 167f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 168f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // A Mach message server-once. The system library to run a message server 169f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // cannot be used here, because some requests are conditionally forwarded 170f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // to another server. 171f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) kern_return_t kr = mach_msg(request, kRcvOptions, 0, buffer_size_, 172f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) server_port_.get(), MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); 173f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) if (kr != KERN_SUCCESS) { 174f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) MACH_LOG(ERROR, kr) << "Unable to receive message."; 175f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) return; 176f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) } 177f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 178f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // Process the message. 179116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch IPCMessage request_message = { request }; 180116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch demuxer_->DemuxMessage(request_message); 181f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 182f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // Free any descriptors in the message body. If the message was forwarded, 183f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // any descriptors would have been moved out of the process on send. If the 184f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // forwarded message was sent from the process hosting this sandbox server, 185f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // destroying the message could also destroy rights held outside the scope of 186f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) // this message server. 187f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) if (!did_forward_message_) { 188f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) mach_msg_destroy(request); 189f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) mach_msg_destroy(reply); 190f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) } 191f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)} 192f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) 193f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)} // namespace sandbox 194