2024 release
This commit is contained in:
		
							parent
							
								
									b7b3a02bfe
								
							
						
					
					
						commit
						604a544978
					
				| 
						 | 
				
			
			@ -9,4 +9,4 @@
 | 
			
		|||
In this lab, students implement the techniques from our group's ISCA 2022 paper `There's Always a Bigger Fish: A Case Study of a Misunderstood Timing Side Channel`. Students will begin by implementing a seemingly familiar cache-based side channel attack in Javascript, and will then be asked to reason about why this attack works. Then, students will remove a core part of the attack, but see that the code still works.
 | 
			
		||||
 | 
			
		||||
**Setup**
 | 
			
		||||
Students can complete this lab on their own machines. MacOS, Linux, Windows all should work. Google Chrome is required for Part 4 of this lab.
 | 
			
		||||
Students can complete this lab on their own machines. MacOS, Linux, Windows all should work.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										185
									
								
								index.html
								
								
								
								
							
							
						
						
									
										185
									
								
								index.html
								
								
								
								
							| 
						 | 
				
			
			@ -1,185 +0,0 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <title>Website Fingerprinting Lab</title>
 | 
			
		||||
    <style>
 | 
			
		||||
      * {
 | 
			
		||||
        margin: 0;
 | 
			
		||||
        padding: 0;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      body {
 | 
			
		||||
        font-family: Arial, Helvetica, sans-serif;
 | 
			
		||||
        padding: 64px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      h1 {
 | 
			
		||||
        font-size: 32px;
 | 
			
		||||
        margin-bottom: 16px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      p {
 | 
			
		||||
        margin-bottom: 8px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      #buttons {
 | 
			
		||||
        margin-bottom: 16px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      button {
 | 
			
		||||
        background: #fff;
 | 
			
		||||
        border: 2px solid #3498db;
 | 
			
		||||
        border-radius: 4px;
 | 
			
		||||
        color: #3498db;
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        font-size: 16px;
 | 
			
		||||
        margin-right: 16px;
 | 
			
		||||
        padding: 8px 16px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      button.disabled {
 | 
			
		||||
        background: #ccc;
 | 
			
		||||
        border-color: #ccc;
 | 
			
		||||
        color: #666;
 | 
			
		||||
        cursor: default;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .trace {
 | 
			
		||||
        margin-bottom: 16px;
 | 
			
		||||
      }
 | 
			
		||||
    </style>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <h1>Website Fingerprinting Lab</h1>
 | 
			
		||||
    <div id="buttons">
 | 
			
		||||
      <button id="collect-trace">Collect trace</button>
 | 
			
		||||
      <button id="download-traces">Download traces</button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div id="traces"></div>
 | 
			
		||||
    <script src="https://d3js.org/d3.v6.js"></script>
 | 
			
		||||
    <script type="text/javascript">
 | 
			
		||||
      const worker = new Worker("worker.js");
 | 
			
		||||
 | 
			
		||||
      const collectTraceButton = document.getElementById("collect-trace");
 | 
			
		||||
      const downloadTracesButton = document.getElementById("download-traces");
 | 
			
		||||
 | 
			
		||||
      // Default values for when the automation script isn't being used. When
 | 
			
		||||
      // the script is in use, these values will get overwritten.
 | 
			
		||||
      window.trace_length = 5000;
 | 
			
		||||
      window.using_automation_script = false;
 | 
			
		||||
 | 
			
		||||
      window.recording = false;
 | 
			
		||||
      window.traces = [];
 | 
			
		||||
 | 
			
		||||
      let traceIds = [];
 | 
			
		||||
 | 
			
		||||
      worker.onmessage = (e) => {
 | 
			
		||||
        window.recording = false;
 | 
			
		||||
 | 
			
		||||
        const trace = JSON.parse(e.data);
 | 
			
		||||
        window.traces.push(trace);
 | 
			
		||||
 | 
			
		||||
        if (window.using_automation_script) {
 | 
			
		||||
          // Don't display traces when automation script is in use
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Create new trace div
 | 
			
		||||
        const parent = document.getElementById("traces");
 | 
			
		||||
        const div = document.createElement("div");
 | 
			
		||||
        const traceId = "a" + Math.random().toString().substring(2, 10);
 | 
			
		||||
        div.setAttribute("id", traceId);
 | 
			
		||||
        div.className = "trace";
 | 
			
		||||
        parent.appendChild(div);
 | 
			
		||||
        traceIds.push(traceId);
 | 
			
		||||
 | 
			
		||||
        // Trace dimensions
 | 
			
		||||
        const width = parent.getBoundingClientRect().width;
 | 
			
		||||
        const height = 64;
 | 
			
		||||
 | 
			
		||||
        // Create div for new trace
 | 
			
		||||
        const svg = d3
 | 
			
		||||
          .select("#" + traceId)
 | 
			
		||||
          .append("svg")
 | 
			
		||||
          .attr("width", width)
 | 
			
		||||
          .attr("height", height);
 | 
			
		||||
 | 
			
		||||
        // Find largest value across all traces
 | 
			
		||||
        const maxVal = d3.max(window.traces, (d) => d3.max(d));
 | 
			
		||||
 | 
			
		||||
        for (let i = 0; i < window.traces.length; i++) {
 | 
			
		||||
          // Re-visualize all traces each time in case maxVal changes
 | 
			
		||||
          const x = d3
 | 
			
		||||
            .scaleLinear()
 | 
			
		||||
            .domain([0, window.traces[i].length])
 | 
			
		||||
            .range([0, width]);
 | 
			
		||||
 | 
			
		||||
          const color = d3
 | 
			
		||||
            .scaleQuantize()
 | 
			
		||||
            .range(["#0d0887", "#7e03a8", "#cc4778", "#f89540", "#f0f921"])
 | 
			
		||||
            .domain([0, maxVal]);
 | 
			
		||||
 | 
			
		||||
          svg
 | 
			
		||||
            .selectAll()
 | 
			
		||||
            .data(window.traces[i].map((x, i) => ({ index: i, value: x })))
 | 
			
		||||
            .join("rect")
 | 
			
		||||
            .attr("x", (d) => x(d.index))
 | 
			
		||||
            .attr("y", 0)
 | 
			
		||||
            .attr("width", x(1))
 | 
			
		||||
            .attr("height", height)
 | 
			
		||||
            .style("fill", (d) => color(d.value));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Reset UI
 | 
			
		||||
        collectTraceButton.innerText = "Collect trace";
 | 
			
		||||
        collectTraceButton.className = "";
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      function collectTrace() {
 | 
			
		||||
        collectTraceButton.innerText = "Collecting trace...";
 | 
			
		||||
        collectTraceButton.className = "disabled";
 | 
			
		||||
        window.recording = true;
 | 
			
		||||
 | 
			
		||||
        worker.postMessage({
 | 
			
		||||
          type: "start",
 | 
			
		||||
          trace_length: window.trace_length,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      collectTraceButton.onclick = () => {
 | 
			
		||||
        if (window.recording) return;
 | 
			
		||||
 | 
			
		||||
        window.recording = true;
 | 
			
		||||
        collectTraceButton.innerText = "Starting in 3...";
 | 
			
		||||
        collectTraceButton.className = "disabled";
 | 
			
		||||
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          collectTraceButton.innerText = "Starting in 2...";
 | 
			
		||||
 | 
			
		||||
          setTimeout(() => {
 | 
			
		||||
            collectTraceButton.innerText = "Starting in 1...";
 | 
			
		||||
            setTimeout(collectTrace, 1000);
 | 
			
		||||
          }, 1000);
 | 
			
		||||
        }, 1000);
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      downloadTracesButton.onclick = () => {
 | 
			
		||||
        const blob = new Blob([JSON.stringify({ traces: window.traces })], {
 | 
			
		||||
          type: "application/json",
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const url = URL.createObjectURL(blob);
 | 
			
		||||
 | 
			
		||||
        const elem = document.createElement("a");
 | 
			
		||||
        elem.href = url;
 | 
			
		||||
        elem.download = "traces.json";
 | 
			
		||||
        document.body.appendChild(elem);
 | 
			
		||||
 | 
			
		||||
        elem.click();
 | 
			
		||||
        document.body.removeChild(elem);
 | 
			
		||||
      };
 | 
			
		||||
    </script>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
const runs = 10;
 | 
			
		||||
 | 
			
		||||
function measureOneLine() {
 | 
			
		||||
  const LINE_SIZE = 16; // 64/sizeof(int)
 | 
			
		||||
  const LINE_SIZE = 32; // 128/sizeof(int)
 | 
			
		||||
  let result = [];
 | 
			
		||||
 | 
			
		||||
  // Fill with -1 to ensure allocation
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,22 +6,22 @@ from sklearn.metrics import classification_report
 | 
			
		|||
from sklearn.model_selection import train_test_split
 | 
			
		||||
 | 
			
		||||
def eval():
 | 
			
		||||
	y_pred_full, y_test_full = [], []
 | 
			
		||||
    y_pred_full, y_test_full = [], []
 | 
			
		||||
 | 
			
		||||
	# Re-train 10 times in order to reduce effects of randomness
 | 
			
		||||
	for i in range(10):
 | 
			
		||||
		### TODO: Exercise 2-4
 | 
			
		||||
		### 1. Load data from traces file
 | 
			
		||||
		### 2. Split data into X_train, X_test, y_train, y_test with train_test_split
 | 
			
		||||
		### 3. Train classifier with X_train and y_train
 | 
			
		||||
		### 4. Use classifier to make predictions on X_test. Save the result to a variable called y_pred
 | 
			
		||||
    # Re-train 10 times in order to reduce effects of randomness
 | 
			
		||||
    for i in range(10):
 | 
			
		||||
        ### TODO: Exercise 2-5
 | 
			
		||||
        ### 1. Load data from traces file
 | 
			
		||||
        ### 2. Split data into X_train, X_test, y_train, y_test with train_test_split
 | 
			
		||||
        ### 3. Train classifier with X_train and y_train
 | 
			
		||||
        ### 4. Use classifier to make predictions on X_test. Save the result to a variable called y_pred
 | 
			
		||||
 | 
			
		||||
		# Do not modify the next two lines
 | 
			
		||||
		y_test_full.extend(y_test)
 | 
			
		||||
		y_pred_full.extend(y_pred)
 | 
			
		||||
        # Do not modify the next two lines
 | 
			
		||||
        y_test_full.extend(y_test)
 | 
			
		||||
        y_pred_full.extend(y_pred)
 | 
			
		||||
 | 
			
		||||
	### TODO: Exercise 2-4 (continued)
 | 
			
		||||
	### 5. Print classification report using y_test_full and y_pred_full
 | 
			
		||||
    ### TODO: Exercise 2-5 (continued)
 | 
			
		||||
    ### 5. Print classification report using y_test_full and y_pred_full
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
	eval()
 | 
			
		||||
    eval()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
// Number of sweep counts
 | 
			
		||||
// TODO: Choose an appropriate value!
 | 
			
		||||
let P;
 | 
			
		||||
// TODO (Exercise 2-1): Choose an appropriate value!
 | 
			
		||||
let P = 1000;
 | 
			
		||||
 | 
			
		||||
// Number of elements in your trace
 | 
			
		||||
let K = 5 * 1000 / P; 
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +21,7 @@ function record() {
 | 
			
		|||
  // Save start timestamp
 | 
			
		||||
  start = performance.now();
 | 
			
		||||
 | 
			
		||||
  // TODO: Record data for 5 seconds and save values to T.
 | 
			
		||||
  // TODO (Exercise 2-1): Record data for 5 seconds and save values to T.
 | 
			
		||||
 | 
			
		||||
  // Once done recording, send result to main thread
 | 
			
		||||
  postMessage(JSON.stringify(T));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,22 +6,22 @@ from sklearn.metrics import classification_report
 | 
			
		|||
from sklearn.model_selection import train_test_split
 | 
			
		||||
 | 
			
		||||
def eval():
 | 
			
		||||
	y_pred_full, y_test_full = [], []
 | 
			
		||||
    y_pred_full, y_test_full = [], []
 | 
			
		||||
 | 
			
		||||
	# Re-train 10 times in order to reduce effects of randomness
 | 
			
		||||
	for i in range(10):
 | 
			
		||||
		### TODO: Exercise 5
 | 
			
		||||
		### 1. Load data from traces file
 | 
			
		||||
		### 2. Split data into X_train, X_test, y_train, y_test with train_test_split
 | 
			
		||||
		### 3. Train classifier with X_train and y_train
 | 
			
		||||
		### 4. Use classifier to make predictions on X_test. Save the result to a variable called y_pred
 | 
			
		||||
    # Re-train 10 times in order to reduce effects of randomness
 | 
			
		||||
    for i in range(10):
 | 
			
		||||
        ### TODO: Exercise 2-5
 | 
			
		||||
        ### 1. Load data from traces file
 | 
			
		||||
        ### 2. Split data into X_train, X_test, y_train, y_test with train_test_split
 | 
			
		||||
        ### 3. Train classifier with X_train and y_train
 | 
			
		||||
        ### 4. Use classifier to make predictions on X_test. Save the result to a variable called y_pred
 | 
			
		||||
 | 
			
		||||
		# Do not modify the next two lines
 | 
			
		||||
		y_test_full.extend(y_test)
 | 
			
		||||
		y_pred_full.extend(y_pred)
 | 
			
		||||
        # Do not modify the next two lines
 | 
			
		||||
        y_test_full.extend(y_test)
 | 
			
		||||
        y_pred_full.extend(y_pred)
 | 
			
		||||
 | 
			
		||||
	### TODO: Exercise 5 (continued)
 | 
			
		||||
	### 5. Print classification report using y_test_full and y_pred_full
 | 
			
		||||
    ### TODO: Exercise 2-5 (continued)
 | 
			
		||||
    ### 5. Print classification report using y_test_full and y_pred_full
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
	eval()
 | 
			
		||||
    eval()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
// Number of sweep counts
 | 
			
		||||
// TODO: Choose an appropriate value!
 | 
			
		||||
let P;
 | 
			
		||||
// TODO (Exercise 3-1): Choose an appropriate value!
 | 
			
		||||
let P = 1000;
 | 
			
		||||
 | 
			
		||||
// Number of elements in your trace
 | 
			
		||||
let K = 5 * 1000 / P; 
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +21,7 @@ function record() {
 | 
			
		|||
  // Save start timestamp
 | 
			
		||||
  start = performance.now();
 | 
			
		||||
 | 
			
		||||
  // TODO: Record data for 5 seconds and save values to T.
 | 
			
		||||
  // TODO (Exercise 3-1): Record data for 5 seconds and save values to T.
 | 
			
		||||
 | 
			
		||||
  // Once done recording, send result to main thread
 | 
			
		||||
  postMessage(JSON.stringify(T));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,89 @@
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
## Optional
 | 
			
		||||
 | 
			
		||||
**Report your browser version, CPU type, cache size, RAM amount, and OS. We use this information to learn about the attack’s behavior on different machines.**
 | 
			
		||||
 | 
			
		||||
- Browser:
 | 
			
		||||
- CPU:
 | 
			
		||||
- Cache sizes:
 | 
			
		||||
- RAM:
 | 
			
		||||
- OS:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 1-2
 | 
			
		||||
 | 
			
		||||
**Use the values printed on the webpage to find the median access time and report your results as follows.**
 | 
			
		||||
 | 
			
		||||
| Number of Cache Lines | Median Access Latency (ms) |
 | 
			
		||||
| --------------------- | -------------------------- |
 | 
			
		||||
| 1                     |                            |
 | 
			
		||||
| 10                    |                            |
 | 
			
		||||
| 100                   |                            |
 | 
			
		||||
| 1,000                 |                            |
 | 
			
		||||
| 10,000                |                            |
 | 
			
		||||
| 100,000               |                            |
 | 
			
		||||
| 1,000,000             |                            |
 | 
			
		||||
| 10,000,000            |                            |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 1-3
 | 
			
		||||
 | 
			
		||||
**According to your measurement results, what is the resolution of your `performance.now()`? In order to measure differences in time with `performance.now()``, approximately how many cache accesses need to be performed?**
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 2-2
 | 
			
		||||
 | 
			
		||||
**Report important parameters used in your attack. For each sweep operation, you access N addresses, and you count the number of sweep operations within a time interval P ms. What values of N and P do you use? How do you choose N? Why do not you choose P to be larger or smaller?**
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 2-3
 | 
			
		||||
 | 
			
		||||
**Take screenshots of the three traces generated by your attack code and include them in the lab report.**
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 2-4
 | 
			
		||||
 | 
			
		||||
**Use the Python code we provided in Part 2.1 to analyze simple statistics (mean, median, etc.) on the traces from google.com and nytimes.com. Report the statistic numbers.**
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 2-6
 | 
			
		||||
 | 
			
		||||
**Include your classification results in your report.**
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 3-2
 | 
			
		||||
 | 
			
		||||
**Include your new accuracy results for the modified attack code in your report.**
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 3-3
 | 
			
		||||
 | 
			
		||||
**Compare your accuracy numbers between Part 2 and 3. Does the accuracy decrease in Part 3? Do you think that our “cache-occupancy” attack actually exploits a cache side channel? If not, take a guess as to possible root causes of the modified attack.**
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										48
									
								
								update.sh
								
								
								
								
							
							
						
						
									
										48
									
								
								update.sh
								
								
								
								
							| 
						 | 
				
			
			@ -1,48 +0,0 @@
 | 
			
		|||
#!/bin/bash
 | 
			
		||||
# Updates repository to latest starter code
 | 
			
		||||
#
 | 
			
		||||
# Adapted from Oliver Beckstein's ASU-CompMethodsPhysics-PHY494 course 2016-2020 placed into the public domain
 | 
			
		||||
 | 
			
		||||
# With GitHub template repositories one needs to use --allow-unrelated-histories
 | 
			
		||||
# at least once. https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template
 | 
			
		||||
 | 
			
		||||
progname="$0"
 | 
			
		||||
REMOTE_NAME="startercode"
 | 
			
		||||
REMOTE_URL="https://github.com/CSAIL-Arch-Sec/SHD-WebsiteFingerprintingLab.git"
 | 
			
		||||
 | 
			
		||||
# progname, from top dir
 | 
			
		||||
UPDATESH="./deploy/$(basename $progname)"
 | 
			
		||||
 | 
			
		||||
CONTACT_MESSAGE="Contact the instructor and TA with a screen shot of ALL output from running $0."
 | 
			
		||||
 | 
			
		||||
function die () {
 | 
			
		||||
    local msg="$1" err=${2:-1}
 | 
			
		||||
    echo "ERROR: ${msg}."
 | 
			
		||||
    exit $err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# ensure everything relative to top dir
 | 
			
		||||
topdir="$(git rev-parse --show-toplevel)" || die "Failed to get rootdir"
 | 
			
		||||
cd "${topdir}" || die "Failed to get to the git root dir ${rootdir}"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# first time
 | 
			
		||||
# 1. set remote repo
 | 
			
		||||
# 2. merge histories between student (template) and remote skeleton
 | 
			
		||||
 | 
			
		||||
if ! git remote get-url ${REMOTE_NAME} >/dev/null 2>&1; then
 | 
			
		||||
    echo "Adding remote repository '${REMOTE_NAME}'."
 | 
			
		||||
    git remote add ${REMOTE_NAME} ${REMOTE_URL}
 | 
			
		||||
 | 
			
		||||
    echo "Merging histories for the first time..."
 | 
			
		||||
    set -x
 | 
			
		||||
    git pull --allow-unrelated-histories -s recursive -X theirs --no-edit  ${REMOTE_NAME} main || \
 | 
			
		||||
	{ git rev-list -1 MERGE_HEAD >/dev/null 2>&1 && git merge --abort ; \
 | 
			
		||||
	  git remote rm ${REMOTE_NAME}; \
 | 
			
		||||
	  die "Failed to merge histories. ${CONTACT_MESSAGE}" $?; }
 | 
			
		||||
 | 
			
		||||
    set +x
 | 
			
		||||
fi    
 | 
			
		||||
 | 
			
		||||
echo "updating repository... git pull from ${REMOTE_NAME}"
 | 
			
		||||
git pull --no-edit ${REMOTE_NAME} main || die "Failed to pull from ${REMOTE_NAME}. ${CONTACT_MESSAGE}"
 | 
			
		||||
		Loading…
	
		Reference in New Issue