Commit fe928be9 authored by serya@chromium.org's avatar serya@chromium.org

Supports DevTools socket access authentication based on Android permissions.

BUG=399567

Review URL: https://codereview.chromium.org/382143005

Cr-Commit-Position: refs/heads/master@{#288273}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@288273 0039d316-1c4b-4281-b951-d872f2087c98
parent 236ccaa1
......@@ -4,13 +4,29 @@
package org.chromium.chrome.browser;
import android.content.Context;
import android.content.pm.PackageManager;
import org.chromium.base.CalledByNative;
/**
* Controller for Remote Web Debugging (Developer Tools).
*/
public class DevToolsServer {
private static final String DEBUG_PERMISSION_SIFFIX = ".permission.DEBUG";
private long mNativeDevToolsServer = 0;
// Defines what processes may access to the socket.
public enum Security {
// Use content::CanUserConnectToDevTools to authorize access to the socket.
DEFAULT,
// In addition to default authorization allows access to an app with android permission
// named chromeAppPackageName + DEBUG_PERMISSION_SIFFIX.
ALLOW_DEBUG_PERMISSION,
}
public DevToolsServer(String socketNamePrefix) {
mNativeDevToolsServer = nativeInitRemoteDebugging(socketNamePrefix);
}
......@@ -24,12 +40,25 @@ public class DevToolsServer {
return nativeIsRemoteDebuggingEnabled(mNativeDevToolsServer);
}
public void setRemoteDebuggingEnabled(boolean enabled, Security security) {
boolean allowDebugPermission = security == Security.ALLOW_DEBUG_PERMISSION;
nativeSetRemoteDebuggingEnabled(mNativeDevToolsServer, enabled, allowDebugPermission);
}
public void setRemoteDebuggingEnabled(boolean enabled) {
nativeSetRemoteDebuggingEnabled(mNativeDevToolsServer, enabled);
setRemoteDebuggingEnabled(enabled, Security.DEFAULT);
}
private native long nativeInitRemoteDebugging(String socketNamePrefix);
private native void nativeDestroyRemoteDebugging(long devToolsServer);
private native boolean nativeIsRemoteDebuggingEnabled(long devToolsServer);
private native void nativeSetRemoteDebuggingEnabled(long devToolsServer, boolean enabled);
private native void nativeSetRemoteDebuggingEnabled(
long devToolsServer, boolean enabled, boolean allowDebugPermission);
@CalledByNative
private static boolean checkDebugPermission(Context context, int pid, int uid) {
String debugPermissionName = context.getPackageName() + DEBUG_PERMISSION_SIFFIX;
return context.checkPermission(debugPermissionName, pid, uid)
== PackageManager.PERMISSION_GRANTED;
}
}
......@@ -63,7 +63,6 @@ const char kDevToolsChannelNameFormat[] = "%s_devtools_remote";
const char kFrontEndURL[] =
"http://chrome-devtools-frontend.appspot.com/serve_rev/%s/devtools.html";
const char kDefaultSocketNamePrefix[] = "chrome";
const char kTetheringSocketName[] = "chrome_devtools_tethering_%d_%d";
const char kTargetTypePage[] = "page";
......@@ -77,6 +76,15 @@ static GURL GetFaviconURLForContents(WebContents* web_contents) {
return GURL();
}
bool AuthorizeSocketAccessWithDebugPermission(
const net::UnixDomainServerSocket::Credentials& credentials) {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_DevToolsServer_checkDebugPermission(
env, base::android::GetApplicationContext(),
credentials.process_id, credentials.user_id) ||
content::CanUserConnectToDevTools(credentials);
}
class TargetBase : public content::DevToolsTarget {
public:
// content::DevToolsTarget implementation:
......@@ -268,8 +276,10 @@ class NonTabTarget : public TargetBase {
// instance of this gets created each time devtools is enabled.
class DevToolsServerDelegate : public content::DevToolsHttpHandlerDelegate {
public:
DevToolsServerDelegate()
: last_tethering_socket_(0) {
explicit DevToolsServerDelegate(
const net::UnixDomainServerSocket::AuthCallback& auth_callback)
: last_tethering_socket_(0),
auth_callback_(auth_callback) {
}
virtual std::string GetDiscoveryPageHTML() OVERRIDE {
......@@ -371,7 +381,7 @@ class DevToolsServerDelegate : public content::DevToolsHttpHandlerDelegate {
*name,
"",
delegate,
base::Bind(&content::CanUserConnectToDevTools))
auth_callback_)
.PassAs<net::StreamListenSocket>();
}
......@@ -385,24 +395,13 @@ class DevToolsServerDelegate : public content::DevToolsHttpHandlerDelegate {
}
int last_tethering_socket_;
const net::UnixDomainServerSocket::AuthCallback auth_callback_;
DISALLOW_COPY_AND_ASSIGN(DevToolsServerDelegate);
};
} // namespace
DevToolsServer::DevToolsServer()
: socket_name_(base::StringPrintf(kDevToolsChannelNameFormat,
kDefaultSocketNamePrefix)),
protocol_handler_(NULL) {
// Override the default socket name if one is specified on the command line.
const CommandLine& command_line = *CommandLine::ForCurrentProcess();
if (command_line.HasSwitch(switches::kRemoteDebuggingSocketName)) {
socket_name_ = command_line.GetSwitchValueASCII(
switches::kRemoteDebuggingSocketName);
}
}
DevToolsServer::DevToolsServer(const std::string& socket_name_prefix)
: socket_name_(base::StringPrintf(kDevToolsChannelNameFormat,
socket_name_prefix.c_str())),
......@@ -419,17 +418,22 @@ DevToolsServer::~DevToolsServer() {
Stop();
}
void DevToolsServer::Start() {
void DevToolsServer::Start(bool allow_debug_permission) {
if (protocol_handler_)
return;
net::UnixDomainServerSocket::AuthCallback auth_callback =
allow_debug_permission ?
base::Bind(&AuthorizeSocketAccessWithDebugPermission) :
base::Bind(&content::CanUserConnectToDevTools);
protocol_handler_ = content::DevToolsHttpHandler::Start(
new net::deprecated::UnixDomainListenSocketWithAbstractNamespaceFactory(
socket_name_,
base::StringPrintf("%s_%d", socket_name_.c_str(), getpid()),
base::Bind(&content::CanUserConnectToDevTools)),
auth_callback),
base::StringPrintf(kFrontEndURL, content::GetWebKitRevision().c_str()),
new DevToolsServerDelegate(),
new DevToolsServerDelegate(auth_callback),
base::FilePath());
}
......@@ -471,10 +475,11 @@ static jboolean IsRemoteDebuggingEnabled(JNIEnv* env,
static void SetRemoteDebuggingEnabled(JNIEnv* env,
jobject obj,
jlong server,
jboolean enabled) {
jboolean enabled,
jboolean allow_debug_permission) {
DevToolsServer* devtools_server = reinterpret_cast<DevToolsServer*>(server);
if (enabled) {
devtools_server->Start();
devtools_server->Start(allow_debug_permission);
} else {
devtools_server->Stop();
}
......
......@@ -16,14 +16,11 @@ class DevToolsHttpHandler;
// This class controls Developer Tools remote debugging server.
class DevToolsServer {
public:
// TODO(mnaganov): Remove the prefixless constructor after having its
// usage removed in the Chrome for Android code.
DevToolsServer();
explicit DevToolsServer(const std::string& socket_name_prefix);
~DevToolsServer();
// Opens linux abstract socket to be ready for remote debugging.
void Start();
void Start(bool allow_debug_permission);
// Closes debugging socket, stops debugging.
void Stop();
......
......@@ -4,23 +4,28 @@
#include "content/public/browser/android/devtools_auth.h"
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
#include "base/logging.h"
namespace content {
bool CanUserConnectToDevTools(uid_t uid, gid_t gid) {
struct passwd* creds = getpwuid(uid);
bool CanUserConnectToDevTools(
const net::UnixDomainServerSocket::Credentials& credentials) {
struct passwd* creds = getpwuid(credentials.user_id);
if (!creds || !creds->pw_name) {
LOG(WARNING) << "DevTools: can't obtain creds for uid " << uid;
LOG(WARNING) << "DevTools: can't obtain creds for uid "
<< credentials.user_id;
return false;
}
if (gid == uid &&
if (credentials.group_id == credentials.user_id &&
(strcmp("root", creds->pw_name) == 0 || // For rooted devices
strcmp("shell", creds->pw_name) == 0 || // For non-rooted devices
uid == getuid())) { // From processes signed with the same key
// From processes signed with the same key
credentials.user_id == getuid())) {
return true;
}
LOG(WARNING) << "DevTools: connection attempt from " << creds->pw_name;
......
......@@ -6,14 +6,14 @@
#define CONTENT_PUBLIC_BROWSER_DEVTOOLS_AUTH_ANDROID_H_
#include "content/common/content_export.h"
#include <pwd.h>
#include "net/socket/unix_domain_server_socket_posix.h"
namespace content {
// Returns true if the given user/group pair is authorized to connect to the
// devtools server, false if not.
CONTENT_EXPORT bool CanUserConnectToDevTools(uid_t uid, gid_t gid);
// Returns true if the given peer identified by the credentials is authorized
// to connect to the devtools server, false if not.
CONTENT_EXPORT bool CanUserConnectToDevTools(
const net::UnixDomainServerSocket::Credentials& credentials);
} // namespace content
......
......@@ -21,10 +21,14 @@ namespace {
const char kSocketFilename[] = "socket_for_testing";
bool UserCanConnectCallback(bool allow_user, uid_t uid, gid_t gid) {
bool UserCanConnectCallback(
bool allow_user, const UnixDomainServerSocket::Credentials& credentials) {
// Here peers are running in same process.
EXPECT_EQ(getuid(), uid);
EXPECT_EQ(getgid(), gid);
#if defined(OS_LINUX) || defined(OS_ANDROID)
EXPECT_EQ(getpid(), credentials.process_id);
#endif
EXPECT_EQ(getuid(), credentials.user_id);
EXPECT_EQ(getgid(), credentials.group_id);
return allow_user;
}
......
......@@ -113,10 +113,9 @@ void UnixDomainListenSocket::Accept() {
SocketDescriptor conn = StreamListenSocket::AcceptSocket();
if (conn == kInvalidSocket)
return;
uid_t user_id;
gid_t group_id;
if (!UnixDomainServerSocket::GetPeerIds(conn, &user_id, &group_id) ||
!auth_callback_.Run(user_id, group_id)) {
UnixDomainServerSocket::Credentials credentials;
if (!UnixDomainServerSocket::GetPeerCredentials(conn, &credentials) ||
!auth_callback_.Run(credentials)) {
if (IGNORE_EINTR(close(conn)) < 0)
LOG(ERROR) << "close() error";
return;
......
......@@ -138,7 +138,7 @@ class TestListenSocketDelegate : public StreamListenSocket::Delegate {
bool UserCanConnectCallback(
bool allow_user, const scoped_refptr<EventManager>& event_manager,
uid_t, gid_t) {
const UnixDomainServerSocket::Credentials&) {
event_manager->Notify(
allow_user ? EVENT_AUTH_GRANTED : EVENT_AUTH_DENIED);
return allow_user;
......
......@@ -28,19 +28,20 @@ UnixDomainServerSocket::~UnixDomainServerSocket() {
}
// static
bool UnixDomainServerSocket::GetPeerIds(SocketDescriptor socket,
uid_t* user_id,
gid_t* group_id) {
bool UnixDomainServerSocket::GetPeerCredentials(SocketDescriptor socket,
Credentials* credentials) {
#if defined(OS_LINUX) || defined(OS_ANDROID)
struct ucred user_cred;
socklen_t len = sizeof(user_cred);
if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &user_cred, &len) < 0)
return false;
*user_id = user_cred.uid;
*group_id = user_cred.gid;
credentials->process_id = user_cred.pid;
credentials->user_id = user_cred.uid;
credentials->group_id = user_cred.gid;
return true;
#else
return getpeereid(socket, user_id, group_id) == 0;
return getpeereid(
socket, &credentials->user_id, &credentials->group_id) == 0;
#endif
}
......@@ -130,10 +131,9 @@ bool UnixDomainServerSocket::AuthenticateAndGetStreamSocket(
scoped_ptr<StreamSocket>* socket) {
DCHECK(accept_socket_);
uid_t user_id;
gid_t group_id;
if (!GetPeerIds(accept_socket_->socket_fd(), &user_id, &group_id) ||
!auth_callback_.Run(user_id, group_id)) {
Credentials credentials;
if (!GetPeerCredentials(accept_socket_->socket_fd(), &credentials) ||
!auth_callback_.Run(credentials)) {
accept_socket_.reset();
return false;
}
......
......@@ -25,20 +25,30 @@ class SocketLibevent;
// Linux and Android.
class NET_EXPORT UnixDomainServerSocket : public ServerSocket {
public:
// Credentials of a peer process connected to the socket.
struct NET_EXPORT Credentials {
#if defined(OS_LINUX) || defined(OS_ANDROID)
// Linux/Android API provides more information about the connected peer
// than Windows/OS X. It's useful for permission-based authorization on
// Android.
pid_t process_id;
#endif
uid_t user_id;
gid_t group_id;
};
// Callback that returns whether the already connected client, identified by
// its process |user_id| and |group_id|, is allowed to keep the connection
// open. Note that the socket is closed immediately in case the callback
// returns false.
typedef base::Callback<bool (uid_t user_id, gid_t group_id)> AuthCallback;
// its credentials, is allowed to keep the connection open. Note that
// the socket is closed immediately in case the callback returns false.
typedef base::Callback<bool (const Credentials&)> AuthCallback;
UnixDomainServerSocket(const AuthCallback& auth_callack,
bool use_abstract_namespace);
virtual ~UnixDomainServerSocket();
// Gets UID and GID of peer to check permissions.
static bool GetPeerIds(SocketDescriptor socket_fd,
uid_t* user_id,
gid_t* group_id);
// Gets credentials of peer to check permissions.
static bool GetPeerCredentials(SocketDescriptor socket_fd,
Credentials* credentials);
// ServerSocket implementation.
virtual int Listen(const IPEndPoint& address, int backlog) OVERRIDE;
......
......@@ -24,10 +24,14 @@ namespace {
const char kSocketFilename[] = "socket_for_testing";
const char kInvalidSocketPath[] = "/invalid/path";
bool UserCanConnectCallback(bool allow_user, uid_t uid, gid_t gid) {
bool UserCanConnectCallback(bool allow_user,
const UnixDomainServerSocket::Credentials& credentials) {
// Here peers are running in same process.
EXPECT_EQ(getuid(), uid);
EXPECT_EQ(getgid(), gid);
#if defined(OS_LINUX) || defined(OS_ANDROID)
EXPECT_EQ(getpid(), credentials.process_id);
#endif
EXPECT_EQ(getuid(), credentials.user_id);
EXPECT_EQ(getgid(), credentials.group_id);
return allow_user;
}
......
......@@ -53,10 +53,10 @@ class CompareSocket {
// Socket authentication function that only allows connections from callers with
// the current uid.
bool MatchUid(uid_t user_id, gid_t) {
bool allowed = user_id == getuid();
bool MatchUid(const net::UnixDomainServerSocket::Credentials& credentials) {
bool allowed = credentials.user_id == getuid();
if (!allowed)
HOST_LOG << "Refused socket connection from uid " << user_id;
HOST_LOG << "Refused socket connection from uid " << credentials.user_id;
return allowed;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment