1 /*
   2  * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 #include <sys/types.h>
  26 #include <sys/stat.h>
  27 #include <door.h>
  28 #include <stdlib.h>
  29 #include <unistd.h>
  30 #include <signal.h>
  31 #include <string.h>
  32 #include <fcntl.h>
  33 #include <errno.h>
  34 #include <limits.h>
  35 
  36 #include "jni.h"
  37 #include "jni_util.h"
  38 #include "jvm.h"
  39 
  40 #include "sun_tools_attach_VirtualMachineImpl.h"
  41 
  42 #define RESTARTABLE(_cmd, _result) do { \
  43   do { \
  44     _result = _cmd; \
  45   } while((_result == -1) && (errno == EINTR)); \
  46 } while(0)
  47 
  48 /*
  49  * Declare library specific JNI_Onload entry if static build
  50  */
  51 DEF_STATIC_JNI_OnLoad
  52 
  53 /*
  54  * Class:     sun_tools_attach_VirtualMachineImpl
  55  * Method:    open
  56  * Signature: (Ljava/lang/String;)I
  57  */
  58 JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_open
  59   (JNIEnv *env, jclass cls, jstring path)
  60 {
  61     jboolean isCopy;
  62     const char* p = GetStringPlatformChars(env, path, &isCopy);
  63     if (p == NULL) {
  64         return 0;
  65     } else {
  66         int fd;
  67         int err = 0;
  68 
  69         fd = open(p, O_RDWR);
  70         if (fd == -1) {
  71             err = errno;
  72         }
  73 
  74         if (isCopy) {
  75             JNU_ReleaseStringPlatformChars(env, path, p);
  76         }
  77 
  78         if (fd == -1) {
  79             if (err == ENOENT) {
  80                 JNU_ThrowByName(env, "java/io/FileNotFoundException", NULL);
  81             } else {
  82                 char* msg = strdup(strerror(err));
  83                 JNU_ThrowIOException(env, msg);
  84                 if (msg != NULL) {
  85                     free(msg);
  86                 }
  87             }
  88         }
  89         return fd;
  90     }
  91 }
  92 
  93 /*
  94  * Class:     sun_tools_attach_VirtualMachineImpl
  95  * Method:    checkPermissions
  96  * Signature: (Ljava/lang/String;)V
  97  */
  98 JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions
  99   (JNIEnv *env, jclass cls, jstring path)
 100 {
 101     jboolean isCopy;
 102     const char* p = GetStringPlatformChars(env, path, &isCopy);
 103     if (p != NULL) {
 104         struct stat64 sb;
 105         uid_t uid, gid;
 106         int res;
 107 
 108         /*
 109          * Check that the path is owned by the effective uid/gid of this
 110          * process. Also check that group/other access is not allowed.
 111          */
 112         uid = geteuid();
 113         gid = getegid();
 114 
 115         res = stat64(p, &sb);
 116         if (res != 0) {
 117             /* save errno */
 118             res = errno;
 119         }
 120 
 121         if (res == 0) {
 122             char msg[100];
 123             jboolean isError = JNI_FALSE;
 124             if (sb.st_uid != uid) {
 125                 jio_snprintf(msg, sizeof(msg)-1,
 126                     "file should be owned by the current user (which is %d) but is owned by %d", uid, sb.st_uid);
 127                 isError = JNI_TRUE;
 128             } else if (sb.st_gid != gid) {
 129                 jio_snprintf(msg, sizeof(msg)-1,
 130                     "file's group should be the current group (which is %d) but the group is %d", gid, sb.st_gid);
 131                 isError = JNI_TRUE;
 132             } else if ((sb.st_mode & (S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) != 0) {
 133                 jio_snprintf(msg, sizeof(msg)-1,
 134                     "file should only be readable and writable by the owner but has 0%03o access", sb.st_mode & 0777);
 135                 isError = JNI_TRUE;
 136             }
 137             if (isError) {
 138                 char buf[256];
 139                 jio_snprintf(buf, sizeof(buf)-1, "well-known file %s is not secure: %s", p, msg);
 140                 JNU_ThrowIOException(env, buf);
 141             }
 142         } else {
 143             char* msg = strdup(strerror(res));
 144             JNU_ThrowIOException(env, msg);
 145             if (msg != NULL) {
 146                 free(msg);
 147             }
 148         }
 149 
 150         if (isCopy) {
 151             JNU_ReleaseStringPlatformChars(env, path, p);
 152         }
 153     }
 154 }
 155 
 156 /*
 157  * Class:     sun_tools_attach_VirtualMachineImpl
 158  * Method:    close
 159  * Signature: (I)V
 160  */
 161 JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_close
 162   (JNIEnv *env, jclass cls, jint fd)
 163 {
 164     int ret;
 165     RESTARTABLE(close(fd), ret);
 166 }
 167 
 168 /*
 169  * Class:     sun_tools_attach_VirtualMachineImpl
 170  * Method:    read
 171  * Signature: (I[BI)I
 172  */
 173 JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_read
 174   (JNIEnv *env, jclass cls, jint fd, jbyteArray ba, jint off, jint baLen)
 175 {
 176     unsigned char buf[128];
 177     size_t len = sizeof(buf);
 178     ssize_t n;
 179 
 180     size_t remaining = (size_t)(baLen - off);
 181     if (len > remaining) {
 182         len = remaining;
 183     }
 184 
 185     RESTARTABLE(read(fd, buf, len), n);
 186     if (n == -1) {
 187         JNU_ThrowIOExceptionWithLastError(env, "read");
 188     } else {
 189         if (n == 0) {
 190             n = -1;     // EOF
 191         } else {
 192             (*env)->SetByteArrayRegion(env, ba, off, (jint)n, (jbyte *)(buf));
 193         }
 194     }
 195     return n;
 196 }
 197 
 198 /*
 199  * Class:     sun_tools_attach_VirtualMachineImpl
 200  * Method:    sigquit
 201  * Signature: (I)V
 202  */
 203 JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_sigquit
 204   (JNIEnv *env, jclass cls, jint pid)
 205 {
 206     if (kill((pid_t)pid, SIGQUIT) == -1) {
 207         JNU_ThrowIOExceptionWithLastError(env, "kill");
 208     }
 209 }
 210 
 211 /*
 212  * A simple table to translate some known errors into reasonable
 213  * error messages
 214  */
 215 static struct {
 216     jint err;
 217     const char* msg;
 218 } const error_messages[] = {
 219     { 100,      "Bad request" },
 220     { 101,      "Protocol mismatch" },
 221     { 102,      "Resource failure" },
 222     { 103,      "Internal error" },
 223     { 104,      "Permission denied" },
 224 };
 225 
 226 /*
 227  * Lookup the given error code and return the appropriate
 228  * message. If not found return NULL.
 229  */
 230 static const char* translate_error(jint err) {
 231     int table_size = sizeof(error_messages) / sizeof(error_messages[0]);
 232     int i;
 233 
 234     for (i=0; i<table_size; i++) {
 235         if (err == error_messages[i].err) {
 236             return error_messages[i].msg;
 237         }
 238     }
 239     return NULL;
 240 }
 241 
 242 /*
 243  * Current protocol version
 244  */
 245 static const char* PROTOCOL_VERSION = "1";
 246 
 247 /*
 248  * Class:     sun_tools_attach_VirtualMachineImpl
 249  * Method:    enqueue
 250  * Signature: (JILjava/lang/String;[Ljava/lang/Object;)V
 251  */
 252 JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_enqueue
 253   (JNIEnv *env, jclass cls, jint fd, jstring cmd, jobjectArray args)
 254 {
 255     jint arg_count, i;
 256     size_t size;
 257     jboolean isCopy;
 258     door_arg_t door_args;
 259     char res_buffer[128];
 260     jint result = -1;
 261     int rc;
 262     const char* cstr;
 263     char* buf;
 264 
 265     /*
 266      * First we get the command string and create the start of the
 267      * argument string to send to the target VM:
 268      * <ver>\0<cmd>\0
 269      */
 270     cstr = JNU_GetStringPlatformChars(env, cmd, &isCopy);
 271     if (cstr == NULL) {
 272         return -1;              /* pending exception */
 273     }
 274     size = strlen(PROTOCOL_VERSION) + strlen(cstr) + 2;
 275     buf = (char*)malloc(size);
 276     if (buf != NULL) {
 277         char* pos = buf;
 278         strcpy(buf, PROTOCOL_VERSION);
 279         pos += strlen(PROTOCOL_VERSION)+1;
 280         strcpy(pos, cstr);
 281     }
 282     if (isCopy) {
 283         JNU_ReleaseStringPlatformChars(env, cmd, cstr);
 284     }
 285     if (buf == NULL) {
 286         JNU_ThrowOutOfMemoryError(env, "malloc failed");
 287         return -1;
 288     }
 289 
 290     /*
 291      * Next we iterate over the arguments and extend the buffer
 292      * to include them.
 293      */
 294     arg_count = (*env)->GetArrayLength(env, args);
 295 
 296     for (i=0; i<arg_count; i++) {
 297         jobject obj = (*env)->GetObjectArrayElement(env, args, i);
 298         if (obj != NULL) {
 299             cstr = JNU_GetStringPlatformChars(env, obj, &isCopy);
 300             if (cstr != NULL) {
 301                 size_t len = strlen(cstr);
 302                 char* newbuf = (char*)realloc(buf, size+len+1);
 303                 if (newbuf != NULL) {
 304                     buf = newbuf;
 305                     strcpy(buf+size, cstr);
 306                     size += len+1;
 307                 }
 308                 if (isCopy) {
 309                     JNU_ReleaseStringPlatformChars(env, obj, cstr);
 310                 }
 311                 if (newbuf == NULL) {
 312                     free(buf);
 313                     JNU_ThrowOutOfMemoryError(env, "realloc failed");
 314                     return -1;
 315                 }
 316             }
 317         }
 318         if ((*env)->ExceptionOccurred(env)) {
 319             free(buf);
 320             return -1;
 321         }
 322     }
 323 
 324     /*
 325      * The arguments to the door function are in 'buf' so we now
 326      * do the door call
 327      */
 328     door_args.data_ptr = buf;
 329     door_args.data_size = size;
 330     door_args.desc_ptr = NULL;
 331     door_args.desc_num = 0;
 332     door_args.rbuf = (char*)&res_buffer;
 333     door_args.rsize = sizeof(res_buffer);
 334 
 335     RESTARTABLE(door_call(fd, &door_args), rc);
 336 
 337     /*
 338      * door_call failed
 339      */
 340     if (rc == -1) {
 341         JNU_ThrowIOExceptionWithLastError(env, "door_call");
 342     } else {
 343         /*
 344          * door_call succeeded but the call didn't return the expected jint.
 345          */
 346         if (door_args.data_size < sizeof(jint)) {
 347             JNU_ThrowIOException(env, "Enqueue error - reason unknown as result is truncated!");
 348         } else {
 349             jint* res = (jint*)(door_args.data_ptr);
 350             if (*res != JNI_OK) {
 351                 const char* msg = translate_error(*res);
 352                 char buf[255];
 353                 if (msg == NULL) {
 354                     sprintf(buf, "Unable to enqueue command to target VM: %d", *res);
 355                 } else {
 356                     sprintf(buf, "Unable to enqueue command to target VM: %s", msg);
 357                 }
 358                 JNU_ThrowIOException(env, buf);
 359             } else {
 360                 /*
 361                  * The door call should return a file descriptor to one end of
 362                  * a socket pair
 363                  */
 364                 if ((door_args.desc_ptr != NULL) &&
 365                     (door_args.desc_num == 1) &&
 366                     (door_args.desc_ptr->d_attributes & DOOR_DESCRIPTOR)) {
 367                     result = door_args.desc_ptr->d_data.d_desc.d_descriptor;
 368                 } else {
 369                     JNU_ThrowIOException(env, "Reply from enqueue missing descriptor!");
 370                 }
 371             }
 372         }
 373     }
 374 
 375     free(buf);
 376     return result;
 377 }