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