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 @@ ...@@ -4,13 +4,29 @@
package org.chromium.chrome.browser; 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). * Controller for Remote Web Debugging (Developer Tools).
*/ */
public class DevToolsServer { public class DevToolsServer {
private static final String DEBUG_PERMISSION_SIFFIX = ".permission.DEBUG";
private long mNativeDevToolsServer = 0; 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) { public DevToolsServer(String socketNamePrefix) {
mNativeDevToolsServer = nativeInitRemoteDebugging(socketNamePrefix); mNativeDevToolsServer = nativeInitRemoteDebugging(socketNamePrefix);
} }
...@@ -24,12 +40,25 @@ public class DevToolsServer { ...@@ -24,12 +40,25 @@ public class DevToolsServer {
return nativeIsRemoteDebuggingEnabled(mNativeDevToolsServer); 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) { public void setRemoteDebuggingEnabled(boolean enabled) {
nativeSetRemoteDebuggingEnabled(mNativeDevToolsServer, enabled); setRemoteDebuggingEnabled(enabled, Security.DEFAULT);
} }
private native long nativeInitRemoteDebugging(String socketNamePrefix); private native long nativeInitRemoteDebugging(String socketNamePrefix);
private native void nativeDestroyRemoteDebugging(long devToolsServer); private native void nativeDestroyRemoteDebugging(long devToolsServer);
private native boolean nativeIsRemoteDebuggingEnabled(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"; ...@@ -63,7 +63,6 @@ const char kDevToolsChannelNameFormat[] = "%s_devtools_remote";
const char kFrontEndURL[] = const char kFrontEndURL[] =
"http://chrome-devtools-frontend.appspot.com/serve_rev/%s/devtools.html"; "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 kTetheringSocketName[] = "chrome_devtools_tethering_%d_%d";
const char kTargetTypePage[] = "page"; const char kTargetTypePage[] = "page";
...@@ -77,6 +76,15 @@ static GURL GetFaviconURLForContents(WebContents* web_contents) { ...@@ -77,6 +76,15 @@ static GURL GetFaviconURLForContents(WebContents* web_contents) {
return GURL(); 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 { class TargetBase : public content::DevToolsTarget {
public: public:
// content::DevToolsTarget implementation: // content::DevToolsTarget implementation:
...@@ -268,8 +276,10 @@ class NonTabTarget : public TargetBase { ...@@ -268,8 +276,10 @@ class NonTabTarget : public TargetBase {
// instance of this gets created each time devtools is enabled. // instance of this gets created each time devtools is enabled.
class DevToolsServerDelegate : public content::DevToolsHttpHandlerDelegate { class DevToolsServerDelegate : public content::DevToolsHttpHandlerDelegate {
public: public:
DevToolsServerDelegate() explicit DevToolsServerDelegate(
: last_tethering_socket_(0) { const net::UnixDomainServerSocket::AuthCallback& auth_callback)
: last_tethering_socket_(0),
auth_callback_(auth_callback) {
} }
virtual std::string GetDiscoveryPageHTML() OVERRIDE { virtual std::string GetDiscoveryPageHTML() OVERRIDE {
...@@ -371,7 +381,7 @@ class DevToolsServerDelegate : public content::DevToolsHttpHandlerDelegate { ...@@ -371,7 +381,7 @@ class DevToolsServerDelegate : public content::DevToolsHttpHandlerDelegate {
*name, *name,
"", "",
delegate, delegate,
base::Bind(&content::CanUserConnectToDevTools)) auth_callback_)
.PassAs<net::StreamListenSocket>(); .PassAs<net::StreamListenSocket>();
} }
...@@ -385,24 +395,13 @@ class DevToolsServerDelegate : public content::DevToolsHttpHandlerDelegate { ...@@ -385,24 +395,13 @@ class DevToolsServerDelegate : public content::DevToolsHttpHandlerDelegate {
} }
int last_tethering_socket_; int last_tethering_socket_;
const net::UnixDomainServerSocket::AuthCallback auth_callback_;
DISALLOW_COPY_AND_ASSIGN(DevToolsServerDelegate); DISALLOW_COPY_AND_ASSIGN(DevToolsServerDelegate);
}; };
} // namespace } // 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) DevToolsServer::DevToolsServer(const std::string& socket_name_prefix)
: socket_name_(base::StringPrintf(kDevToolsChannelNameFormat, : socket_name_(base::StringPrintf(kDevToolsChannelNameFormat,
socket_name_prefix.c_str())), socket_name_prefix.c_str())),
...@@ -419,17 +418,22 @@ DevToolsServer::~DevToolsServer() { ...@@ -419,17 +418,22 @@ DevToolsServer::~DevToolsServer() {
Stop(); Stop();
} }
void DevToolsServer::Start() { void DevToolsServer::Start(bool allow_debug_permission) {
if (protocol_handler_) if (protocol_handler_)
return; return;
net::UnixDomainServerSocket::AuthCallback auth_callback =
allow_debug_permission ?
base::Bind(&AuthorizeSocketAccessWithDebugPermission) :
base::Bind(&content::CanUserConnectToDevTools);
protocol_handler_ = content::DevToolsHttpHandler::Start( protocol_handler_ = content::DevToolsHttpHandler::Start(
new net::deprecated::UnixDomainListenSocketWithAbstractNamespaceFactory( new net::deprecated::UnixDomainListenSocketWithAbstractNamespaceFactory(
socket_name_, socket_name_,
base::StringPrintf("%s_%d", socket_name_.c_str(), getpid()), base::StringPrintf("%s_%d", socket_name_.c_str(), getpid()),
base::Bind(&content::CanUserConnectToDevTools)), auth_callback),
base::StringPrintf(kFrontEndURL, content::GetWebKitRevision().c_str()), base::StringPrintf(kFrontEndURL, content::GetWebKitRevision().c_str()),
new DevToolsServerDelegate(), new DevToolsServerDelegate(auth_callback),
base::FilePath()); base::FilePath());
} }
...@@ -471,10 +475,11 @@ static jboolean IsRemoteDebuggingEnabled(JNIEnv* env, ...@@ -471,10 +475,11 @@ static jboolean IsRemoteDebuggingEnabled(JNIEnv* env,
static void SetRemoteDebuggingEnabled(JNIEnv* env, static void SetRemoteDebuggingEnabled(JNIEnv* env,
jobject obj, jobject obj,
jlong server, jlong server,
jboolean enabled) { jboolean enabled,
jboolean allow_debug_permission) {
DevToolsServer* devtools_server = reinterpret_cast<DevToolsServer*>(server); DevToolsServer* devtools_server = reinterpret_cast<DevToolsServer*>(server);
if (enabled) { if (enabled) {
devtools_server->Start(); devtools_server->Start(allow_debug_permission);
} else { } else {
devtools_server->Stop(); devtools_server->Stop();
} }
......
...@@ -16,14 +16,11 @@ class DevToolsHttpHandler; ...@@ -16,14 +16,11 @@ class DevToolsHttpHandler;
// This class controls Developer Tools remote debugging server. // This class controls Developer Tools remote debugging server.
class DevToolsServer { class DevToolsServer {
public: 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); explicit DevToolsServer(const std::string& socket_name_prefix);
~DevToolsServer(); ~DevToolsServer();
// Opens linux abstract socket to be ready for remote debugging. // Opens linux abstract socket to be ready for remote debugging.
void Start(); void Start(bool allow_debug_permission);
// Closes debugging socket, stops debugging. // Closes debugging socket, stops debugging.
void Stop(); void Stop();
......
...@@ -4,23 +4,28 @@ ...@@ -4,23 +4,28 @@
#include "content/public/browser/android/devtools_auth.h" #include "content/public/browser/android/devtools_auth.h"
#include <unistd.h> #include <pwd.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h>
#include "base/logging.h" #include "base/logging.h"
namespace content { namespace content {
bool CanUserConnectToDevTools(uid_t uid, gid_t gid) { bool CanUserConnectToDevTools(
struct passwd* creds = getpwuid(uid); const net::UnixDomainServerSocket::Credentials& credentials) {
struct passwd* creds = getpwuid(credentials.user_id);
if (!creds || !creds->pw_name) { 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; return false;
} }
if (gid == uid && if (credentials.group_id == credentials.user_id &&
(strcmp("root", creds->pw_name) == 0 || // For rooted devices (strcmp("root", creds->pw_name) == 0 || // For rooted devices
strcmp("shell", creds->pw_name) == 0 || // For non-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; return true;
} }
LOG(WARNING) << "DevTools: connection attempt from " << creds->pw_name; LOG(WARNING) << "DevTools: connection attempt from " << creds->pw_name;
......
...@@ -6,14 +6,14 @@ ...@@ -6,14 +6,14 @@
#define CONTENT_PUBLIC_BROWSER_DEVTOOLS_AUTH_ANDROID_H_ #define CONTENT_PUBLIC_BROWSER_DEVTOOLS_AUTH_ANDROID_H_
#include "content/common/content_export.h" #include "content/common/content_export.h"
#include "net/socket/unix_domain_server_socket_posix.h"
#include <pwd.h>
namespace content { namespace content {
// Returns true if the given user/group pair is authorized to connect to the // Returns true if the given peer identified by the credentials is authorized
// devtools server, false if not. // to connect to the devtools server, false if not.
CONTENT_EXPORT bool CanUserConnectToDevTools(uid_t uid, gid_t gid); CONTENT_EXPORT bool CanUserConnectToDevTools(
const net::UnixDomainServerSocket::Credentials& credentials);
} // namespace content } // namespace content
......
...@@ -21,10 +21,14 @@ namespace { ...@@ -21,10 +21,14 @@ namespace {
const char kSocketFilename[] = "socket_for_testing"; 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. // Here peers are running in same process.
EXPECT_EQ(getuid(), uid); #if defined(OS_LINUX) || defined(OS_ANDROID)
EXPECT_EQ(getgid(), gid); EXPECT_EQ(getpid(), credentials.process_id);
#endif
EXPECT_EQ(getuid(), credentials.user_id);
EXPECT_EQ(getgid(), credentials.group_id);
return allow_user; return allow_user;
} }
......
...@@ -113,10 +113,9 @@ void UnixDomainListenSocket::Accept() { ...@@ -113,10 +113,9 @@ void UnixDomainListenSocket::Accept() {
SocketDescriptor conn = StreamListenSocket::AcceptSocket(); SocketDescriptor conn = StreamListenSocket::AcceptSocket();
if (conn == kInvalidSocket) if (conn == kInvalidSocket)
return; return;
uid_t user_id; UnixDomainServerSocket::Credentials credentials;
gid_t group_id; if (!UnixDomainServerSocket::GetPeerCredentials(conn, &credentials) ||
if (!UnixDomainServerSocket::GetPeerIds(conn, &user_id, &group_id) || !auth_callback_.Run(credentials)) {
!auth_callback_.Run(user_id, group_id)) {
if (IGNORE_EINTR(close(conn)) < 0) if (IGNORE_EINTR(close(conn)) < 0)
LOG(ERROR) << "close() error"; LOG(ERROR) << "close() error";
return; return;
......
...@@ -138,7 +138,7 @@ class TestListenSocketDelegate : public StreamListenSocket::Delegate { ...@@ -138,7 +138,7 @@ class TestListenSocketDelegate : public StreamListenSocket::Delegate {
bool UserCanConnectCallback( bool UserCanConnectCallback(
bool allow_user, const scoped_refptr<EventManager>& event_manager, bool allow_user, const scoped_refptr<EventManager>& event_manager,
uid_t, gid_t) { const UnixDomainServerSocket::Credentials&) {
event_manager->Notify( event_manager->Notify(
allow_user ? EVENT_AUTH_GRANTED : EVENT_AUTH_DENIED); allow_user ? EVENT_AUTH_GRANTED : EVENT_AUTH_DENIED);
return allow_user; return allow_user;
......
...@@ -28,19 +28,20 @@ UnixDomainServerSocket::~UnixDomainServerSocket() { ...@@ -28,19 +28,20 @@ UnixDomainServerSocket::~UnixDomainServerSocket() {
} }
// static // static
bool UnixDomainServerSocket::GetPeerIds(SocketDescriptor socket, bool UnixDomainServerSocket::GetPeerCredentials(SocketDescriptor socket,
uid_t* user_id, Credentials* credentials) {
gid_t* group_id) {
#if defined(OS_LINUX) || defined(OS_ANDROID) #if defined(OS_LINUX) || defined(OS_ANDROID)
struct ucred user_cred; struct ucred user_cred;
socklen_t len = sizeof(user_cred); socklen_t len = sizeof(user_cred);
if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &user_cred, &len) < 0) if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &user_cred, &len) < 0)
return false; return false;
*user_id = user_cred.uid; credentials->process_id = user_cred.pid;
*group_id = user_cred.gid; credentials->user_id = user_cred.uid;
credentials->group_id = user_cred.gid;
return true; return true;
#else #else
return getpeereid(socket, user_id, group_id) == 0; return getpeereid(
socket, &credentials->user_id, &credentials->group_id) == 0;
#endif #endif
} }
...@@ -130,10 +131,9 @@ bool UnixDomainServerSocket::AuthenticateAndGetStreamSocket( ...@@ -130,10 +131,9 @@ bool UnixDomainServerSocket::AuthenticateAndGetStreamSocket(
scoped_ptr<StreamSocket>* socket) { scoped_ptr<StreamSocket>* socket) {
DCHECK(accept_socket_); DCHECK(accept_socket_);
uid_t user_id; Credentials credentials;
gid_t group_id; if (!GetPeerCredentials(accept_socket_->socket_fd(), &credentials) ||
if (!GetPeerIds(accept_socket_->socket_fd(), &user_id, &group_id) || !auth_callback_.Run(credentials)) {
!auth_callback_.Run(user_id, group_id)) {
accept_socket_.reset(); accept_socket_.reset();
return false; return false;
} }
......
...@@ -25,20 +25,30 @@ class SocketLibevent; ...@@ -25,20 +25,30 @@ class SocketLibevent;
// Linux and Android. // Linux and Android.
class NET_EXPORT UnixDomainServerSocket : public ServerSocket { class NET_EXPORT UnixDomainServerSocket : public ServerSocket {
public: 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 // Callback that returns whether the already connected client, identified by
// its process |user_id| and |group_id|, is allowed to keep the connection // its credentials, is allowed to keep the connection open. Note that
// open. Note that the socket is closed immediately in case the callback // the socket is closed immediately in case the callback returns false.
// returns false. typedef base::Callback<bool (const Credentials&)> AuthCallback;
typedef base::Callback<bool (uid_t user_id, gid_t group_id)> AuthCallback;
UnixDomainServerSocket(const AuthCallback& auth_callack, UnixDomainServerSocket(const AuthCallback& auth_callack,
bool use_abstract_namespace); bool use_abstract_namespace);
virtual ~UnixDomainServerSocket(); virtual ~UnixDomainServerSocket();
// Gets UID and GID of peer to check permissions. // Gets credentials of peer to check permissions.
static bool GetPeerIds(SocketDescriptor socket_fd, static bool GetPeerCredentials(SocketDescriptor socket_fd,
uid_t* user_id, Credentials* credentials);
gid_t* group_id);
// ServerSocket implementation. // ServerSocket implementation.
virtual int Listen(const IPEndPoint& address, int backlog) OVERRIDE; virtual int Listen(const IPEndPoint& address, int backlog) OVERRIDE;
......
...@@ -24,10 +24,14 @@ namespace { ...@@ -24,10 +24,14 @@ namespace {
const char kSocketFilename[] = "socket_for_testing"; const char kSocketFilename[] = "socket_for_testing";
const char kInvalidSocketPath[] = "/invalid/path"; 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. // Here peers are running in same process.
EXPECT_EQ(getuid(), uid); #if defined(OS_LINUX) || defined(OS_ANDROID)
EXPECT_EQ(getgid(), gid); EXPECT_EQ(getpid(), credentials.process_id);
#endif
EXPECT_EQ(getuid(), credentials.user_id);
EXPECT_EQ(getgid(), credentials.group_id);
return allow_user; return allow_user;
} }
......
...@@ -53,10 +53,10 @@ class CompareSocket { ...@@ -53,10 +53,10 @@ class CompareSocket {
// Socket authentication function that only allows connections from callers with // Socket authentication function that only allows connections from callers with
// the current uid. // the current uid.
bool MatchUid(uid_t user_id, gid_t) { bool MatchUid(const net::UnixDomainServerSocket::Credentials& credentials) {
bool allowed = user_id == getuid(); bool allowed = credentials.user_id == getuid();
if (!allowed) if (!allowed)
HOST_LOG << "Refused socket connection from uid " << user_id; HOST_LOG << "Refused socket connection from uid " << credentials.user_id;
return allowed; 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