Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions src/commons/bufutil.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,21 @@ export function base64ToBytes(b64uri) {
}

export function decodeFromBinary(b, u8) {
// if b is a u8 array, simply u16 it
if (u8) return new Uint16Array(raw(b));

// if b is a binary-string, convert it to u8
const bytes = binaryStringToBytes(b);
// ...and then to u16
return new Uint16Array(raw(bytes));
const conv = u8 ? b : binaryStringToBytes(b);

// Ensure the byte array has even length for Uint16Array
// Uint16Array requires byte length to be a multiple of 2
const ab = raw(conv);
if (ab.byteLength % 2 !== 0) {
// Pad with an extra zero byte if odd length
const padded = new Uint8Array(ab.byteLength + 1);
padded.set(new Uint8Array(ab));
padded[ab.byteLength] = 0;
return new Uint16Array(padded.buffer);
}

return new Uint16Array(ab);
}

export function decodeFromBinaryArray(b) {
Expand Down
11 changes: 9 additions & 2 deletions src/commons/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import { emptyString } from "./util.js";

const tktsz = 48;
const hkdfalgkeysz = 32; // sha256
// hex: 9f34ba3c3c9097fef97e97effbb4bda4b9afa17dbb9b02f091a25d119ac91c5f
const fixedsalt = new Uint8Array([
159, 52, 186, 60, 60, 144, 151, 254, 249, 126, 151, 239, 251, 180, 189, 164,
185, 175, 161, 125, 187, 155, 2, 240, 145, 162, 93, 17, 154, 201, 28, 95,
]);

export async function tkt48(seed, ctx) {
if (!emptyBuf(seed) && !emptyString(ctx)) {
Expand All @@ -29,8 +34,10 @@ export async function tkt48(seed, ctx) {
return t;
}

// salt for hkdf can be zero: stackoverflow.com/a/64403302
export async function gen(secret, info, salt = new Uint8Array()) {
// salt for hkdf can be zero if secret is pseudorandom
// but a fixed salt is needed for high-entropy
// but non uniform keys like outputs of DHKE
export async function gen(secret, info, salt = fixedsalt) {
if (emptyBuf(secret) || emptyBuf(info)) {
throw new Error("empty secret/info");
}
Expand Down
1 change: 1 addition & 0 deletions src/core/doh.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ function optionsRequest(request) {
}

/**
* Must not throw!
* @param {IOState} io
* @param {Error} err
*/
Expand Down
64 changes: 45 additions & 19 deletions src/core/io-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export default class IOState {
initDecodedDnsPacketIfNeeded() {
if (!this.decodedDnsPacket) {
this.decodedDnsPacket = this.emptyDecodedDnsPacket();
return true;
}
return false;
}

dnsExceptionResponse(res) {
Expand All @@ -87,23 +89,47 @@ export default class IOState {
this.exceptionFrom = res.exceptionFrom || "no-origin";
}

const qid = this.decodedDnsPacket.id;
const questions = this.decodedDnsPacket.questions;
const servfail = dnsutil.servfail(qid, questions);
const ex = {
exceptionFrom: this.exceptionFrom,
exceptionStack: this.exceptionStack,
};
this.decodedDnsPacket = dnsutil.decode(servfail);

this.logDnsPkt();
this.httpResponse = new Response(servfail, {
headers: util.concatHeaders(
this.headers(servfail),
this.debugHeaders(JSON.stringify(ex))
),
status: servfail ? 200 : 408, // rfc8484 section-4.2.1
});
try {
const qid = this.decodedDnsPacket.id; // may be null
const questions = this.decodedDnsPacket.questions; // may be null
const servfail = dnsutil.servfail(qid, questions); // may be empty
const hasServfail = !bufutil.emptyBuf(servfail);
const ex = {
exceptionFrom: this.exceptionFrom,
exceptionStack: this.exceptionStack,
};

if (hasServfail) {
// TODO: try-catch as decode may throw?
this.decodedDnsPacket = dnsutil.decode(servfail);
}

this.logDnsPkt();
this.httpResponse = new Response(servfail, {
headers: util.concatHeaders(
this.headers(servfail),
this.debugHeaders(JSON.stringify(ex))
),
status: hasServfail ? 200 : 408, // rfc8484 section-4.2.1
});
} catch (e) {
const pktjson = JSON.stringify(this.decodedDnsPacket || {});
this.log.e("dnsExceptionResponse", pktjson, e.stack);
if (
this.exceptionStack === "no-res" ||
this.exceptionStack === "no-stack"
) {
this.exceptionStack = e.stack;
this.exceptionFrom = "IOState:errorResponse";
}
this.httpResponse = new Response(null, {
headers: util.concatHeaders(
this.headers(),
this.debugHeaders(JSON.stringify(this.exceptionStack))
),
status: 503,
});
}
}

hResponse(r) {
Expand Down Expand Up @@ -158,14 +184,14 @@ export default class IOState {
}

dnsBlockResponse(blockflag) {
this.initDecodedDnsPacketIfNeeded();
this.initDecodedDnsPacketIfNeeded(); // initializes to empty obj
this.stopProcessing = true;
this.isDnsBlock = true;
this.flag = blockflag;

try {
this.assignBlockResponse();
const b = dnsutil.encode(this.decodedDnsPacket);
const b = dnsutil.encode(this.decodedDnsPacket); // may throw if empty or malformed
this.httpResponse = new Response(b, {
headers: this.headers(b),
});
Expand Down
11 changes: 5 additions & 6 deletions src/core/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { services } from "./svc.js";
import * as bufutil from "../commons/bufutil.js";
import * as dnsutil from "../commons/dnsutil.js";
import * as envutil from "../commons/envutil.js";
import * as rdnsutil from "../plugins/rdns-util.js";
import * as util from "../commons/util.js";
import IOState from "./io-state.js";
import { RResp } from "../plugins/plugin-response.js";
import * as rdnsutil from "../plugins/rdns-util.js";
import IOState from "./io-state.js";
import { services } from "./svc.js";

export default class RethinkPlugin {
/**
*
* @param {{request: Request, waitUntil: Function, respondWith: Function}} event
*/
constructor(event) {
Expand Down Expand Up @@ -161,7 +160,7 @@ export default class RethinkPlugin {

/**
* @param {RResp} response
* @param {IOState} io
* @param {Promise<IOState>} io
*/
async commandControlCallback(response, io) {
const rxid = this.ctx.get("rxid");
Expand All @@ -178,7 +177,7 @@ export default class RethinkPlugin {
* Adds "userBlocklistInfo", "userBlocklistInfo", and "dnsResolverUrl"
* to RethinkPlugin ctx.
* @param {RResp} response
* @param {IOState} io
* @param {Promise<IOState>} io
*/
async userOpCallback(response, io) {
const rxid = this.ctx.get("rxid");
Expand Down
8 changes: 7 additions & 1 deletion src/plugins/command-control/cc.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,13 @@ async function domainNameToList(
querypacket
);
const ans = await res.arrayBuffer();
const anspacket = dnsutil.decode(ans);
let anspacket;
try {
anspacket = dnsutil.decode(ans);
} catch (e) {
log.w(rxid, "malformed dns response in command-control:", e.message);
return r; // empty response
}
const ansdomains = dnsutil.extractDomains(anspacket);

for (const d of ansdomains) {
Expand Down
18 changes: 15 additions & 3 deletions src/plugins/dns-op/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
*/

import { LfuCache } from "@serverless-dns/lfu-cache";
import { CacheApi } from "./cache-api.js";
import * as bufutil from "../../commons/bufutil.js";
import * as dnsutil from "../../commons/dnsutil.js";
import * as envutil from "../../commons/envutil.js";
import * as util from "../../commons/util.js";
import * as cacheutil from "../cache-util.js";
import { CacheApi } from "./cache-api.js";

export class DnsCache {
constructor(size) {
Expand Down Expand Up @@ -136,7 +136,13 @@ export class DnsCache {
if (util.emptyObj(res)) return null;

const b = res.dnsBuffer;
const p = dnsutil.decode(b);
let p;
try {
p = dnsutil.decode(b);
} catch (e) {
this.log.w("malformed dns packet in local cache:", e.message);
return null; // return null to indicate cache miss
}
const m = res.metadata;

const cr = cacheutil.makeCacheValue(p, b, m);
Expand Down Expand Up @@ -173,7 +179,13 @@ export class DnsCache {
// 'b' shouldn't be null; but a dns question or a dns answer
const b = await response.arrayBuffer();
// when 'b' is less than dns-packet header-size, decode errs out
const p = dnsutil.decode(b);
let p;
try {
p = dnsutil.decode(b);
} catch (e) {
this.log.w("malformed dns packet in http cache:", e.message);
return null; // return null to indicate cache miss
}
// though 'm' is never empty
const m = metadata;

Expand Down
11 changes: 9 additions & 2 deletions src/plugins/dns-op/resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,14 @@ export default class DNSResolver {

const ans = await res.arrayBuffer();

const r = this.makeRdnsResponse(rxid, ans, blf, stamps);
let r;
try {
r = this.makeRdnsResponse(rxid, ans, blf, stamps);
} catch (e) {
this.log.w(rxid, "upstream returned malformed dns response:", e.message);
const pkt = dnsutil.servfail(decodedpacket.id, decodedpacket.questions);
r = pres.dnsResponse(dnsutil.decode(pkt), pkt, stamps);
}

// blockAnswer is a no-op if the ans is already quad0
// check outgoing cached dns-packet against blocklists
Expand All @@ -292,7 +299,7 @@ export default class DNSResolver {
makeRdnsResponse(rxid, raw, blf, stamps = null) {
if (!raw) throw new Error(rxid + " mk-res no upstream result");

const dnsPacket = dnsutil.decode(raw);
const dnsPacket = dnsutil.decode(raw); // may throw if malformed
// stamps are empty for domains that are not in any blocklist
// but there's no way to know if that was indeed the case as
// stamps are sent here by cache-resolver, which may or may not
Expand Down
22 changes: 16 additions & 6 deletions src/plugins/rdns-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,12 +423,22 @@ export function base64ToUintV1(b64Flag) {

/**
* @param {string} b64Flag
* @returns {Uint16Array}
* @returns {Uint16Array?}
*/
export function base32ToUintV1(flag) {
// TODO: check for empty flag
const b32 = decodeURI(flag);
return bufutil.decodeFromBinaryArray(rbase32(b32));
try {
if (util.emptyString(flag)) return null;
const b32 = decodeURI(flag);
if (util.emptyString(b32)) return null;

const decoded = rbase32(b32);
if (util.emptyString(decoded)) return null;

return bufutil.decodeFromBinaryArray(decoded);
} catch (e) {
log.w("Rdns:base32ToUintV1", "error decoding b32 flag", e);
}
return null;
}

/**
Expand Down Expand Up @@ -475,7 +485,7 @@ export function stampVersion(s) {
// TODO: The logic to parse stamps must be kept in sync with:
// github.com/celzero/website-dns/blob/8e6056bb/src/js/flag.js#L260-L425
/**
*
* May return empty BlockstampInfo if flag is invalid or empty.
* @param {string} flag
* @returns {pres.BlockstampInfo}
*/
Expand All @@ -499,7 +509,7 @@ export function unstamp(flag) {
} else if (v === "1") {
const convertor = isFlagB32 ? base32ToUintV1 : base64ToUintV1;
const f = s[1];
r.userBlocklistFlagUint = convertor(f) || null;
r.userBlocklistFlagUint = convertor(f) || null; // convertor may return null
} else {
log.w("Rdns:unstamp", "unknown blocklist stamp version in " + s);
}
Expand Down
8 changes: 4 additions & 4 deletions src/plugins/users/user-op.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { UserCache } from "./user-cache.js";
import * as pres from "../plugin-response.js";
import * as util from "../../commons/util.js";
import * as dnsutil from "../../commons/dnsutil.js";
import * as envutil from "../../commons/envutil.js";
import * as util from "../../commons/util.js";
import * as pres from "../plugin-response.js";
import * as rdnsutil from "../rdns-util.js";
import * as token from "./auth-token.js";
import * as dnsutil from "../../commons/dnsutil.js";
import { UserCache } from "./user-cache.js";

// TODO: determine an approp cache-size
const cacheSize = 20000;
Expand Down
2 changes: 1 addition & 1 deletion src/server-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,7 @@ async function handleTCPData(socket, chunk, sb, host, flag) {
// if there is any out of band data, handle it
if (!bufutil.emptyBuf(oob)) {
log.d(`tcp: pipelined, handle oob: ${oob.byteLength}`);
n += handleTCPData(socket, oob, sb, host, flag);
n += await handleTCPData(socket, oob, sb, host, flag);
}
} // continue reading from socket
return n;
Expand Down
Loading