Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-6359291

java.awt.Robot.checkValidRect should be more thorough

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: P4 P4
    • None
    • 5.0
    • client-libs
    • Cause Known
    • x86
    • linux

      FULL PRODUCT VERSION :
      java version "1.5.0_03"
      Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_03-b07)
      Java HotSpot(TM) Client VM (build 1.5.0_03-b07, mixed mode, sharing)


      ADDITIONAL OS VERSION INFORMATION :
      Linux bertha 2.6.8-1-686-smp #1 SMP Thu Nov 25 04:55:00 UTC 2004 i686 GNU/Linux


      A DESCRIPTION OF THE PROBLEM :
      Robot contains the following check on Rectangles passed to createScreenCapture:

          private static void checkValidRect(Rectangle rect) {
              if (rect.width <= 0 || rect.height <= 0) {
                  throw new IllegalArgumentException("Rectangle width and height must be > 0");
              }
          }

      on Mac OS, if rect.y < 0, the Java VM crashes.

      on all platforms, values of rect.x or rect.y less than 0 give undefined results, which doesn't seem compatible with the Java Way.

      on all platforms, values of rect.x+rect.width greater than the screen's width (and likewise for the height) cause the Robot to grab off-screen pixels.

      i guess there are potentially security implications here, though an exploit would probably be quite hard to come by.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      run this:

      import java.awt.*;
      import java.awt.event.*;
      import java.awt.geom.*;
      import java.awt.image.*;
      import java.util.*;
      import javax.swing.*;
      import javax.swing.Timer;
      import javax.swing.event.*;

      public class RobotBug extends JFrame {
          private Robot robot;
          private Timer timer;
          private ScaledImagePanel scaledImagePanel;
          private int scaleFactor;
          
          public RobotBug() {
              super("RobotBug");
              setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
              try {
                  robot = new Robot();
              } catch (AWTException ex) {
                  ex.printStackTrace();
                  System.exit(0);
              }
              timer = new Timer(50, new MouseTracker());
              setSize(new Dimension(250, 300));
              setContentPane(makeUi());
              timer.start();
          }
          
          private JComponent makeUi() {
              JPanel result = new JPanel(new BorderLayout());
              result.add(scaledImagePanel = new ScaledImagePanel(), BorderLayout.CENTER);
              result.add(makeControlPanel(), BorderLayout.SOUTH);
              return result;
          }
          
          private JComponent makeControlPanel() {
              JPanel panel = new JPanel(new BorderLayout());
              panel.add(makeScaleSlider(), BorderLayout.CENTER);
              panel.add(makeShowGridCheckBox(), BorderLayout.EAST);
              panel.setBorder(new javax.swing.border.EmptyBorder(0, 12, 4, 12));
              return panel;
          }
          
          private JCheckBox makeShowGridCheckBox() {
              final JCheckBox checkBox = new JCheckBox("Show Grid");
              checkBox.addItemListener(new ItemListener() {
                  public void itemStateChanged(ItemEvent e) {
                      scaledImagePanel.setShowGrid(checkBox.isSelected());
                  }
              });
              checkBox.setSelected(false);
              return checkBox;
          }
          
          private JSlider makeScaleSlider() {
              final JSlider scaleSlider = new JSlider(1, 4);
              scaleSlider.addChangeListener(new ChangeListener() {
                  public void stateChanged(ChangeEvent e) {
                      scaleFactor = (1 << scaleSlider.getValue());
                      repaint();
                  }
              });
              Hashtable<Integer, JComponent> labels = new Hashtable<Integer, JComponent>();
              for (int i = scaleSlider.getMinimum(); i <= scaleSlider.getMaximum(); ++i) {
                  labels.put(i, new JLabel(Integer.toString(1 << i) + "x"));
              }
              scaleSlider.setLabelTable(labels);
              scaleSlider.setPaintLabels(true);
              scaleSlider.setPaintTicks(true);
              scaleSlider.setSnapToTicks(true);
              scaleSlider.setValue(1);
              return scaleSlider;
          }
          
          private class ScaledImagePanel extends JComponent {
              private Image image;
              private boolean showGrid;
              
              public void setImage(Image image) {
                  this.image = image;
                  repaint();
              }
              
              public void setShowGrid(boolean showGrid) {
                  this.showGrid = showGrid;
                  repaint();
              }
              
              public void paintComponent(Graphics g) {
                  g.drawImage(image, 0, 0, null);
                  paintGridLines(g);
              }
              
              private void paintGridLines(Graphics g) {
                  if (showGrid == false) {
                      return;
                  }
                  g.setColor(Color.BLACK);
                  for (int x = scaleFactor; x < getWidth(); x += scaleFactor) {
                      g.drawLine(x, 0, x, getHeight());
                  }
                  for (int y = scaleFactor; y < getHeight(); y += scaleFactor) {
                      g.drawLine(0, y, getWidth(), y);
                  }
              }
          }
          
          private class MouseTracker implements ActionListener {
              private Point lastPosition = null;
              
              public void actionPerformed(ActionEvent e) {
                  if (scaledImagePanel.isShowing() == false) {
                      return;
                  }
                  
                  PointerInfo pointerInfo = MouseInfo.getPointerInfo();
                  Point center = pointerInfo.getLocation();
                  if (lastPosition != null && lastPosition.equals(center)) {
                      return;
                  }
                  lastPosition = center;
                  
                  Rectangle screenCaptureBounds = getScreenCaptureBounds(center);
                  BufferedImage capturedImage = robot.createScreenCapture(screenCaptureBounds);
                  Image scaledImage = capturedImage.getScaledInstance(scaledImagePanel.getWidth(), scaledImagePanel.getHeight(), Image.SCALE_REPLICATE);
                  scaledImagePanel.setImage(scaledImage);
              }
              
              private Rectangle getScreenCaptureBounds(Point center) {
                  Point topLeft = new Point(center.x - scaledImagePanel.getWidth() / (2 * scaleFactor), center.y - scaledImagePanel.getHeight() / (2 * scaleFactor));
                  Rectangle result = new Rectangle(topLeft, scaledImagePanel.getSize());
                  result.width /= scaleFactor;
                  result.height /= scaleFactor;
                  
                  // Constrain the capture to the display.
                  // Apple's 1.5 VM crashes if you don't.
      // FIXME
      // result.x = Math.max(result.x, 0);
      // result.y = Math.max(result.y, 0);
                  Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
                  if (result.x + result.width > screenSize.width) {
                      result.x = screenSize.width - result.width;
                  }
                  if (result.y + result.height > screenSize.height) {
                      result.y = screenSize.height - result.height;
                  }
                  return result;
              }
          }
          
          public static void main(String[] args) {
              new RobotBug().setVisible(true);
          }
      }


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      near the edge of the screen, with the lines commented out near "FIXME", i'd expect an exception to be thrown because of the invalid rectangle.
      ACTUAL -
      weird funky effects as i get to see off-screen display memory.

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      see above!
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      don't pass a Rectangle that asks for off-screen pixels.

            Unassigned Unassigned
            rmandalasunw Ranjith Mandala (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Imported:
              Indexed: